This commit is contained in:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-config"
|
||||
version = "0.6.5a0+1015"
|
||||
version = "0.6.6a0+1015"
|
||||
description = "Profile-based configuration compiler for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
|
||||
@@ -36,4 +36,4 @@ __all__ = [
|
||||
"write_default_profile",
|
||||
]
|
||||
|
||||
__version__ = "0.6.5a0+1015"
|
||||
__version__ = "0.6.6a0+1015"
|
||||
|
||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-extension-host"
|
||||
version = "0.6.5a0+1015"
|
||||
version = "0.6.6a0+1015"
|
||||
description = "Single-release, single-invocation process host for JWeBot extensions"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-sdk==0.6.5a0+1015",
|
||||
"jwebot-kernel==0.6.5a0+1015",
|
||||
"jwebot-sdk==0.6.6a0+1015",
|
||||
"jwebot-kernel==0.6.6a0+1015",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -16,4 +16,4 @@ __all__ = [
|
||||
"process_request",
|
||||
]
|
||||
|
||||
__version__ = "0.6.5a0+1015"
|
||||
__version__ = "0.6.6a0+1015"
|
||||
|
||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-kernel"
|
||||
version = "0.6.5a0+1015"
|
||||
version = "0.6.6a0+1015"
|
||||
description = "Stable routing and extension authority for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-config==0.6.5a0+1015",
|
||||
"jwebot-sdk==0.6.5a0+1015",
|
||||
"jwebot-official-extensions==0.6.5a0+1015",
|
||||
"jwebot-config==0.6.6a0+1015",
|
||||
"jwebot-sdk==0.6.6a0+1015",
|
||||
"jwebot-official-extensions==0.6.6a0+1015",
|
||||
"tomli>=2; python_version < '3.11'",
|
||||
]
|
||||
|
||||
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"verify_installed_release_signature",
|
||||
]
|
||||
|
||||
__version__ = "0.6.5a0+1015"
|
||||
__version__ = "0.6.6a0+1015"
|
||||
|
||||
@@ -61,6 +61,8 @@ from jwebot_official_extensions import (
|
||||
extension_for_kind,
|
||||
local_command_parts,
|
||||
local_extension_command_token,
|
||||
pack_reply_parts,
|
||||
unpack_reply_parts,
|
||||
)
|
||||
from jwebot_official_extensions.steam import (
|
||||
DEFAULT_STEAM_MIHOMO_PROXY_URL,
|
||||
@@ -3760,7 +3762,16 @@ class Ledger:
|
||||
or not isinstance(state.get("players"), dict)
|
||||
):
|
||||
raise LocalCommandError("自走棋赛事结构无效")
|
||||
if status in {"active", "completed"}:
|
||||
phase = str(state.get("phase", ""))
|
||||
roster_required = (
|
||||
status == "completed"
|
||||
or (
|
||||
status == "active"
|
||||
and not phase.startswith("draft_")
|
||||
and phase != "registration"
|
||||
)
|
||||
)
|
||||
if roster_required:
|
||||
players = state["players"].values()
|
||||
if any(
|
||||
not isinstance(player, dict)
|
||||
@@ -4979,7 +4990,11 @@ class EdgeApplication:
|
||||
error_type=type(error).__name__,
|
||||
)
|
||||
raise EdgeError("隔离插件暂时不可用") from error
|
||||
if result.reply.kind is not PlanKind.TEXT:
|
||||
if result.reply.kind is PlanKind.MULTI_TEXT:
|
||||
reply_message = pack_reply_parts(list(result.reply.messages))
|
||||
elif result.reply.kind is PlanKind.TEXT:
|
||||
reply_message = result.reply.message
|
||||
else:
|
||||
raise EdgeError("隔离插件没有返回可见回复")
|
||||
self.telemetry.elapsed(
|
||||
"edge.extension.execute",
|
||||
@@ -4989,7 +5004,7 @@ class EdgeApplication:
|
||||
plugin_id=owner.extension_id,
|
||||
mode="host",
|
||||
)
|
||||
return result.reply.message
|
||||
return reply_message
|
||||
if (
|
||||
self.config.extension_mode == "host-only"
|
||||
or (
|
||||
@@ -5078,13 +5093,18 @@ class EdgeApplication:
|
||||
"duplicate": True,
|
||||
}
|
||||
if row["response_text"]:
|
||||
return {
|
||||
parts = unpack_reply_parts(row["response_text"])
|
||||
text = parts[0] if parts is not None else row["response_text"]
|
||||
response = {
|
||||
"status": "reply",
|
||||
"route": row["route"],
|
||||
"execution_path": stored_execution_path,
|
||||
"text": row["response_text"],
|
||||
"text": text,
|
||||
"duplicate": True,
|
||||
}
|
||||
if parts is not None:
|
||||
response["texts"] = parts
|
||||
return response
|
||||
return {
|
||||
"status": "fallback_cloud",
|
||||
"reason": "existing_incomplete_event",
|
||||
@@ -5206,6 +5226,13 @@ class EdgeApplication:
|
||||
status="local_completed",
|
||||
response_text=reply,
|
||||
)
|
||||
parts = unpack_reply_parts(reply)
|
||||
visible_reply = parts[0] if parts is not None else reply
|
||||
reply_chars = (
|
||||
sum(len(part) for part in parts)
|
||||
if parts is not None
|
||||
else len(reply)
|
||||
)
|
||||
self.telemetry.elapsed(
|
||||
"edge.local.complete",
|
||||
started,
|
||||
@@ -5214,14 +5241,18 @@ class EdgeApplication:
|
||||
route=route,
|
||||
reason=reason,
|
||||
execution_path=execution_path,
|
||||
chars=len(reply),
|
||||
chars=reply_chars,
|
||||
messages=len(parts) if parts is not None else 1,
|
||||
)
|
||||
return {
|
||||
response = {
|
||||
"status": "reply",
|
||||
"route": route,
|
||||
"execution_path": execution_path,
|
||||
"text": reply,
|
||||
"text": visible_reply,
|
||||
}
|
||||
if parts is not None:
|
||||
response["texts"] = parts
|
||||
return response
|
||||
alias_reply = self.ledger.local_alias_identity_reply(
|
||||
str(row["group_fingerprint"]),
|
||||
str(row["question"]),
|
||||
|
||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-official-extensions"
|
||||
version = "0.6.5a0+1015"
|
||||
version = "0.6.6a0+1015"
|
||||
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-sdk==0.6.5a0+1015",
|
||||
"jwebot-sdk==0.6.6a0+1015",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from .cs_autochess import (
|
||||
CARD_BY_ID,
|
||||
COACH_BONUSES,
|
||||
COACH_POOL,
|
||||
MAX_PLAYERS,
|
||||
MIN_PLAYERS,
|
||||
PLAYER_POOL,
|
||||
@@ -32,6 +33,8 @@ from .local_commands import (
|
||||
LocalCommandError,
|
||||
deterministic_int,
|
||||
local_command_parts,
|
||||
pack_reply_parts,
|
||||
unpack_reply_parts,
|
||||
)
|
||||
from .runtime import (
|
||||
LocalExtensionError,
|
||||
@@ -49,6 +52,7 @@ from .steam import (
|
||||
__all__ = [
|
||||
"CARD_BY_ID",
|
||||
"COACH_BONUSES",
|
||||
"COACH_POOL",
|
||||
"CsMajorAutochess",
|
||||
"DICE_CHALLENGE_TIMEOUT_SECONDS",
|
||||
"DEFAULT_STEAM_MIHOMO_PROXY_URL",
|
||||
@@ -79,9 +83,11 @@ __all__ = [
|
||||
"local_extension_command_token",
|
||||
"lineup_power",
|
||||
"matchup_modifier",
|
||||
"pack_reply_parts",
|
||||
"parse_autochess_action",
|
||||
"unpack_reply_parts",
|
||||
"extension_for_kind",
|
||||
"render_feature_list",
|
||||
]
|
||||
|
||||
__version__ = "0.6.5a0+1015"
|
||||
__version__ = "0.6.6a0+1015"
|
||||
|
||||
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+8
@@ -21,6 +21,14 @@ def _handler(context: SingleGroupContext, command: str):
|
||||
"event": event.to_mapping(),
|
||||
},
|
||||
)
|
||||
texts = response.get("texts")
|
||||
if isinstance(texts, list) and texts and all(
|
||||
isinstance(item, str) and item.strip() for item in texts
|
||||
):
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.multi_text(texts),
|
||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
|
||||
+1032
-472
File diff suppressed because it is too large
Load Diff
+36
-1
@@ -25,6 +25,7 @@ MAX_RPS_PLAYERS = 8
|
||||
BEIJING_TIMEZONE = ZoneInfo("Asia/Shanghai")
|
||||
DICE_PATTERN = re.compile(r"^(?:(\d{1,2})[dD](\d{1,3}))?$")
|
||||
RANDOM_NUMBER_PATTERN = re.compile(r"^(-?\d{1,6})\s+(-?\d{1,6})$")
|
||||
MULTI_REPLY_MARKER = "\x1eJWEBOT_MULTI_REPLY_V1\x1e"
|
||||
|
||||
|
||||
class LocalCommandError(RuntimeError):
|
||||
@@ -53,6 +54,36 @@ def deterministic_int(seed: str, lower: int, upper: int) -> int:
|
||||
return lower + value % (upper - lower + 1)
|
||||
|
||||
|
||||
def pack_reply_parts(parts: list[str] | tuple[str, ...]) -> str:
|
||||
cleaned = [str(part).strip() for part in parts if str(part).strip()]
|
||||
if not cleaned:
|
||||
raise LocalCommandError("本地命令没有可发送回复")
|
||||
if len(cleaned) == 1:
|
||||
return cleaned[0]
|
||||
return MULTI_REPLY_MARKER + json.dumps(
|
||||
cleaned,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
|
||||
|
||||
def unpack_reply_parts(value: object) -> list[str] | None:
|
||||
text = str(value or "")
|
||||
if not text.startswith(MULTI_REPLY_MARKER):
|
||||
return None
|
||||
try:
|
||||
payload = json.loads(text[len(MULTI_REPLY_MARKER) :])
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
if (
|
||||
not isinstance(payload, list)
|
||||
or not payload
|
||||
or any(not isinstance(item, str) or not item.strip() for item in payload)
|
||||
):
|
||||
return None
|
||||
return [item.strip() for item in payload]
|
||||
|
||||
|
||||
def local_command_parts(question: str) -> tuple[str, str]:
|
||||
normalized = question.lstrip()
|
||||
if not normalized.startswith("/"):
|
||||
@@ -131,7 +162,11 @@ class LocalCommandEngine:
|
||||
}:
|
||||
return self.reply_cleaner(self.players.handle(row, kind, arguments))
|
||||
if kind == "cs_autochess":
|
||||
return self.reply_cleaner(self.autochess.handle(row))
|
||||
parts = [
|
||||
self.reply_cleaner(part)
|
||||
for part in self.autochess.handle_parts(row)
|
||||
]
|
||||
return pack_reply_parts(parts)
|
||||
if kind == "dice":
|
||||
return self.reply_cleaner(self.dice(row, arguments))
|
||||
if kind == "dice_challenge":
|
||||
|
||||
@@ -14,6 +14,8 @@ from .local_commands import (
|
||||
LocalCommandEngine,
|
||||
local_command_parts,
|
||||
utc_now,
|
||||
pack_reply_parts,
|
||||
unpack_reply_parts,
|
||||
)
|
||||
|
||||
|
||||
@@ -74,7 +76,11 @@ class LocalServiceBroker:
|
||||
"question": f"{command} {arguments}".rstrip(),
|
||||
"created_at": str(event.get("observed_at") or utc_now()),
|
||||
}
|
||||
return {"text": self.engine.handle(row, event)}
|
||||
text = self.engine.handle(row, event)
|
||||
parts = unpack_reply_parts(text)
|
||||
if parts is not None:
|
||||
return {"text": parts[0], "texts": parts}
|
||||
return {"text": text}
|
||||
|
||||
|
||||
class _Context:
|
||||
@@ -162,6 +168,8 @@ class LocalExtensionRuntime:
|
||||
result = handler(sdk_event, arguments)
|
||||
if not isinstance(result, ExtensionResult):
|
||||
raise LocalExtensionError("local adapter returned invalid result")
|
||||
if result.reply.kind is PlanKind.MULTI_TEXT:
|
||||
return pack_reply_parts(list(result.reply.messages))
|
||||
if result.reply.kind is not PlanKind.TEXT:
|
||||
raise LocalExtensionError("local command returned no visible reply")
|
||||
return result.reply.message
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-sdk"
|
||||
version = "0.6.5a0+1015"
|
||||
version = "0.6.6a0+1015"
|
||||
description = "Typed, capability-limited extension API for JWeBot plugins"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = []
|
||||
|
||||
@@ -38,4 +38,4 @@ __all__ = [
|
||||
"SingleGroupContext",
|
||||
]
|
||||
|
||||
__version__ = "0.6.5a0+1015"
|
||||
__version__ = "0.6.6a0+1015"
|
||||
|
||||
@@ -20,6 +20,7 @@ class ContractViolation(ValueError):
|
||||
|
||||
class PlanKind(str, Enum):
|
||||
TEXT = "text"
|
||||
MULTI_TEXT = "multi_text"
|
||||
NO_REPLY = "no_reply"
|
||||
|
||||
|
||||
@@ -156,6 +157,7 @@ class ReplyPlan:
|
||||
|
||||
kind: PlanKind
|
||||
message: str = ""
|
||||
messages: tuple[str, ...] = ()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.kind is PlanKind.TEXT:
|
||||
@@ -168,13 +170,40 @@ class ReplyPlan:
|
||||
limit=MAX_REPLY_CHARS,
|
||||
),
|
||||
)
|
||||
if self.messages:
|
||||
raise ContractViolation("text plan cannot contain multiple messages")
|
||||
elif self.kind is PlanKind.MULTI_TEXT:
|
||||
if self.message:
|
||||
raise ContractViolation("multi_text plan cannot contain text")
|
||||
if not self.messages:
|
||||
raise ContractViolation("multi_text plan requires messages")
|
||||
if len(self.messages) > 8:
|
||||
raise ContractViolation("multi_text plan exceeds 8 messages")
|
||||
object.__setattr__(
|
||||
self,
|
||||
"messages",
|
||||
tuple(
|
||||
_required_text(
|
||||
message,
|
||||
name="reply message",
|
||||
limit=MAX_REPLY_CHARS,
|
||||
)
|
||||
for message in self.messages
|
||||
),
|
||||
)
|
||||
elif self.message:
|
||||
raise ContractViolation("no_reply plan cannot contain text")
|
||||
elif self.messages:
|
||||
raise ContractViolation("no_reply plan cannot contain messages")
|
||||
|
||||
@classmethod
|
||||
def text(cls, message: str) -> "ReplyPlan":
|
||||
return cls(kind=PlanKind.TEXT, message=message)
|
||||
|
||||
@classmethod
|
||||
def multi_text(cls, messages: tuple[str, ...] | list[str]) -> "ReplyPlan":
|
||||
return cls(kind=PlanKind.MULTI_TEXT, messages=tuple(messages))
|
||||
|
||||
@classmethod
|
||||
def no_reply(cls) -> "ReplyPlan":
|
||||
return cls(kind=PlanKind.NO_REPLY)
|
||||
@@ -189,14 +218,23 @@ class ReplyPlan:
|
||||
raise ContractViolation("reply plan kind is invalid") from error
|
||||
if kind is PlanKind.TEXT:
|
||||
return cls.text(str(value.get("text", "")))
|
||||
if kind is PlanKind.MULTI_TEXT:
|
||||
messages = value.get("texts", ())
|
||||
if not isinstance(messages, list):
|
||||
raise ContractViolation("multi_text plan messages must be a list")
|
||||
return cls.multi_text([str(message) for message in messages])
|
||||
if value.get("text"):
|
||||
raise ContractViolation("no_reply plan cannot contain text")
|
||||
if value.get("texts"):
|
||||
raise ContractViolation("no_reply plan cannot contain messages")
|
||||
return cls.no_reply()
|
||||
|
||||
def to_mapping(self) -> dict[str, str]:
|
||||
def to_mapping(self) -> dict[str, Any]:
|
||||
value = {"kind": self.kind.value}
|
||||
if self.kind is PlanKind.TEXT:
|
||||
value["text"] = self.message
|
||||
if self.kind is PlanKind.MULTI_TEXT:
|
||||
value["texts"] = list(self.messages)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user