@@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## beta 2.0.0.1021 - 2026-10-21 16:57:39
|
||||||
|
|
||||||
|
- Beta 2.0 架构更新完成:插件 SDK 统一服务适配器上线,本地命令插件联动改为受控服务调用,私聊 shadow 测试保持只观察不发送,安全边界和时延打点一起收口。
|
||||||
|
- 新增公开 SDK `ServiceCommandAdapter`,生成插件不再重复手写 service-broker
|
||||||
|
转发逻辑;插件仍只能返回结构化 `ReplyPlan` 和受控 metadata。
|
||||||
|
- 同步更新官方本地命令适配器生成器,所有 reviewed command adapter 统一走
|
||||||
|
`ServiceCommandAdapter`,避免插件联动接口漂移。
|
||||||
|
- 私聊 shadow 测试继续 fail-closed:只接受显式配置的会话标题哈希,生成
|
||||||
|
`shadow_only=true`、`dispatch_allowed=false` 观察事件,Edge 在路由和账本写入前拒绝。
|
||||||
|
- 保留现有多条消息发送策略:Kernel 统一拆分 `multi_text`,逐条视觉确认,并在消息间随机等待
|
||||||
|
0.5 到 1.0 秒,插件不能绕过 GUI 发送链路。
|
||||||
|
- 发布前后保留性能基线对比要求,重点观察本地命令、chat ack、direct queue 和
|
||||||
|
extension-host 打点,避免架构封装影响运行时延。
|
||||||
|
|
||||||
## beta 1.2.8.1019 - 2026-10-19 22:49:30
|
## beta 1.2.8.1019 - 2026-10-19 22:49:30
|
||||||
|
|
||||||
- 冷门知识问答题库整体重写:改为更偏游戏内机制、数值、版本和活动细节的深度题。
|
- 冷门知识问答题库整体重写:改为更偏游戏内机制、数值、版本和活动细节的深度题。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
|||||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||||
并通过带视觉确认的 GUI 发送链路回复。
|
并通过带视觉确认的 GUI 发送链路回复。
|
||||||
|
|
||||||
当前版本:`beta 1.2.8.1019`,更新时间:2026-10-19 22:49:30。
|
当前版本:`beta 2.0.0.1021`,更新时间:2026-10-21 16:57:39。
|
||||||
|
|
||||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||||
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
||||||
@@ -193,9 +193,10 @@ sudo ./deploy/scripts/deploy-jwebot-production.sh /path/to/JWeBot jwebot
|
|||||||
|
|
||||||
部署必须通过微信窗口就绪、队列清空、Edge 健康、SQLite 完整性和自适应
|
部署必须通过微信窗口就绪、队列清空、Edge 健康、SQLite 完整性和自适应
|
||||||
录制服务重启稳定性检查。切换成功后,部署器按版本幂等地向每个既有账户
|
录制服务重启稳定性检查。切换成功后,部署器按版本幂等地向每个既有账户
|
||||||
发放 200 摩拉、写入账户流水,并使用当前 changelog 的第一条摘要发布群公告;
|
发放摩拉、写入账户流水,并使用当前 changelog 的第一条摘要发布群公告。
|
||||||
`beta 1.0.5.1016` 与 `beta 1.1.0.1016` 额外发放 1 原石;
|
普通小版本发放 200 摩拉;`beta 1.0.5.1016`、`beta 1.1.0.1016`
|
||||||
后续普通小版本不额外发放原石。
|
和 `beta 1.2.0.1019` 额外发放 1 原石;`beta 2.0.0.1021`
|
||||||
|
作为架构级大版本发放 400 摩拉和 2 原石。
|
||||||
旧 OCR 服务保留安装,但保持禁用。
|
旧 OCR 服务保留安装,但保持禁用。
|
||||||
|
|
||||||
### 仓库结构
|
### 仓库结构
|
||||||
@@ -270,10 +271,10 @@ visually confirmed GUI bridge.
|
|||||||
Current version:
|
Current version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
beta 1.2.8.1019
|
beta 2.0.0.1021
|
||||||
```
|
```
|
||||||
|
|
||||||
Updated: 2026-10-19 22:49:30.
|
Updated: 2026-10-21 16:57:39.
|
||||||
|
|
||||||
The 1.0 Beta baseline consolidates the stable local command set, person and
|
The 1.0 Beta baseline consolidates the stable local command set, person and
|
||||||
context indexes, Steam presence/playtime queries, virtual coin games,
|
context indexes, Steam presence/playtime queries, virtual coin games,
|
||||||
@@ -460,10 +461,11 @@ sudo ./deploy/scripts/deploy-jwebot-production.sh /path/to/JWeBot jwebot
|
|||||||
The deployer refuses cutover unless the ready-window, empty-queue, Edge health,
|
The deployer refuses cutover unless the ready-window, empty-queue, Edge health,
|
||||||
SQLite integrity, and adaptive restart-stability gates pass. Legacy OCR units
|
SQLite integrity, and adaptive restart-stability gates pass. Legacy OCR units
|
||||||
remain installed but disabled. After cutover, version-idempotent release
|
remain installed but disabled. After cutover, version-idempotent release
|
||||||
settlement grants 200 Mora to every existing account, records the movement,
|
settlement grants Mora to every existing account, records the movement, and
|
||||||
and sends the first changelog bullet as the guarded group announcement.
|
sends the first changelog bullet as the guarded group announcement. Ordinary
|
||||||
For `beta 1.0.5.1016` and `beta 1.1.0.1016`, the settlement also grants
|
patch releases grant 200 Mora. `beta 1.0.5.1016`, `beta 1.1.0.1016`, and
|
||||||
1 primogem once; ordinary later patch releases do not grant extra primogems.
|
`beta 1.2.0.1019` also grant 1 primogem once. `beta 2.0.0.1021` is the
|
||||||
|
architecture-level major release and grants 400 Mora plus 2 primogems.
|
||||||
|
|
||||||
## Repository Map
|
## Repository Map
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# JWeBot Command Catalog
|
# JWeBot Command Catalog
|
||||||
|
|
||||||
Current baseline: `beta 1.2.8.1019`.
|
Current baseline: `beta 2.0.0.1021`.
|
||||||
|
|
||||||
This catalog records the reviewed commands shipped with the built-in local
|
This catalog records the reviewed commands shipped with the built-in local
|
||||||
extensions. Runtime ownership is frozen at startup from the generated
|
extensions. Runtime ownership is frozen at startup from the generated
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Private Conversation Shadow Testing
|
||||||
|
|
||||||
|
This prototype observes one manually selected private conversation without
|
||||||
|
changing the production group workflow.
|
||||||
|
|
||||||
|
It is intentionally unable to switch conversations or send replies:
|
||||||
|
|
||||||
|
- The recorder never enqueues matching shadow events.
|
||||||
|
- Every observation carries `shadow_only=true` and `dispatch_allowed=false`.
|
||||||
|
- JWeBot Edge ignores marked observations before routing or persistence.
|
||||||
|
- The shadow registry accepts private targets only. The main group cannot be
|
||||||
|
added to it.
|
||||||
|
- No setting in the shadow registry can enable delivery.
|
||||||
|
|
||||||
|
## Local Configuration
|
||||||
|
|
||||||
|
Keep the real file outside the repository:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.config/jwebot/conversation-shadow.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with placeholder values:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"key": "test-direct",
|
||||||
|
"kind": "direct",
|
||||||
|
"title_fingerprint": "<64 lowercase SHA-256 characters>",
|
||||||
|
"sender": "<local test label>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `JWEBOT_CONVERSATION_SHADOW_CONFIG` to that absolute path only in a local
|
||||||
|
test process. Without the variable, the feature is not imported or enabled.
|
||||||
|
|
||||||
|
The title fingerprint is the SHA-256 value returned by
|
||||||
|
`title_fingerprint(image)` for a screenshot where the intended conversation is
|
||||||
|
selected. It is an opaque routing value, not a trusted contact identity.
|
||||||
|
|
||||||
|
## Controlled Test
|
||||||
|
|
||||||
|
1. Keep production services unchanged.
|
||||||
|
2. Run the recorder in a separate local test state directory.
|
||||||
|
3. Select the allowlisted private conversation manually.
|
||||||
|
4. Let the first capture establish a baseline.
|
||||||
|
5. Send `/状态` or `JWeBot: 测试` from the other account.
|
||||||
|
6. Inspect the local history for `shadow_only=true`.
|
||||||
|
7. Assert that the OCR queue remains empty and no outgoing bubble appears.
|
||||||
|
|
||||||
|
Ordinary private messages are observed but do not set `mention_bot`. Only slash
|
||||||
|
commands and the configured wake word are treated as potential triggers.
|
||||||
|
|
||||||
|
Do not point this prototype at production state paths and do not add the
|
||||||
|
environment variable to a deployed runtime profile.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-loan."""
|
"""Generated reviewed adapter for coin-loan."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-loan'
|
PROFILE = 'coin-loan'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '贷款'
|
DESCRIPTION = '贷款'
|
||||||
HELP_ENTRIES = ('/贷款;/还款',)
|
HELP_ENTRIES = ('/贷款;/还款',)
|
||||||
COMMANDS = ('/贷款', '/借款', '/loan', '/borrow', '/还款', '/repay')
|
COMMANDS = ('/贷款', '/借款', '/loan', '/borrow', '/还款', '/repay')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-ranking."""
|
"""Generated reviewed adapter for coin-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-ranking'
|
PROFILE = 'coin-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '富榜'
|
DESCRIPTION = '富榜'
|
||||||
HELP_ENTRIES = ('/大富翁榜',)
|
HELP_ENTRIES = ('/大富翁榜',)
|
||||||
COMMANDS = ('/摩拉排行', '/摩拉排行榜', '/金币排行', '/金币排行榜', '/大富翁榜', '/richlist')
|
COMMANDS = ('/摩拉排行', '/摩拉排行榜', '/金币排行', '/金币排行榜', '/大富翁榜', '/richlist')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-taffy."""
|
"""Generated reviewed adapter for coin-taffy."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-taffy'
|
PROFILE = 'coin-taffy'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = 'Taffy摩拉'
|
DESCRIPTION = 'Taffy摩拉'
|
||||||
HELP_ENTRIES = ('/Taffy摩拉(兼容 /Taffy金币)',)
|
HELP_ENTRIES = ('/Taffy摩拉(兼容 /Taffy金币)',)
|
||||||
COMMANDS = ('/Taffy摩拉', '/Taffy金币', '/taffycoins')
|
COMMANDS = ('/Taffy摩拉', '/Taffy金币', '/taffycoins')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-transfer."""
|
"""Generated reviewed adapter for coin-transfer."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-transfer'
|
PROFILE = 'coin-transfer'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '摩拉转账'
|
DESCRIPTION = '摩拉转账'
|
||||||
HELP_ENTRIES = ('/转账 对方 金额;/transfer',)
|
HELP_ENTRIES = ('/转账 对方 金额;/transfer',)
|
||||||
COMMANDS = ('/转账', '/transfer')
|
COMMANDS = ('/转账', '/transfer')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin."""
|
"""Generated reviewed adapter for coin."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin'
|
PROFILE = 'coin'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '抛一枚硬币'
|
DESCRIPTION = '抛一枚硬币'
|
||||||
HELP_ENTRIES = ('/翻硬币',)
|
HELP_ENTRIES = ('/翻硬币',)
|
||||||
COMMANDS = ('/翻硬币',)
|
COMMANDS = ('/翻硬币',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for cs-major-autochess."""
|
"""Generated reviewed adapter for cs-major-autochess."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'cs-major-autochess'
|
PROFILE = 'cs-major-autochess'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '自走棋'
|
DESCRIPTION = '自走棋'
|
||||||
HELP_ENTRIES = ('/自走棋 开始|报名|选',)
|
HELP_ENTRIES = ('/自走棋 开始|报名|选',)
|
||||||
COMMANDS = ('/自走棋', '/cs自走棋', '/autochess', '/选', '/刷新', '/锁定', '/战术', '/升级教练', '/上阵', '/下阵', '/出售', '/阵容', '/赛程', '/结束回合')
|
COMMANDS = ('/自走棋', '/cs自走棋', '/autochess', '/选', '/刷新', '/锁定', '/战术', '/升级教练', '/上阵', '/下阵', '/出售', '/阵容', '/赛程', '/结束回合')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for daily-fortune."""
|
"""Generated reviewed adapter for daily-fortune."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'daily-fortune'
|
PROFILE = 'daily-fortune'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '生成当天稳定一致的运势'
|
DESCRIPTION = '生成当天稳定一致的运势'
|
||||||
HELP_ENTRIES = ('/今日运势',)
|
HELP_ENTRIES = ('/今日运势',)
|
||||||
COMMANDS = ('/今日运势',)
|
COMMANDS = ('/今日运势',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for dice."""
|
"""Generated reviewed adapter for dice."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'dice'
|
PROFILE = 'dice'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '骰子和多人骰子挑战'
|
DESCRIPTION = '骰子和多人骰子挑战'
|
||||||
HELP_ENTRIES = ('/丢骰子 [NdM]', '/丢骰子挑战 [5d6|5d20|状态|结算|战绩|结束]')
|
HELP_ENTRIES = ('/丢骰子 [NdM]', '/丢骰子挑战 [5d6|5d20|状态|结算|战绩|结束]')
|
||||||
COMMANDS = ('/丢骰子', '/骰子', '/丢骰子挑战', '/骰子挑战')
|
COMMANDS = ('/丢骰子', '/骰子', '/丢骰子挑战', '/骰子挑战')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for draw-lot."""
|
"""Generated reviewed adapter for draw-lot."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'draw-lot'
|
PROFILE = 'draw-lot'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '抽取一条本地签文'
|
DESCRIPTION = '抽取一条本地签文'
|
||||||
HELP_ENTRIES = ('/抽签',)
|
HELP_ENTRIES = ('/抽签',)
|
||||||
COMMANDS = ('/抽签',)
|
COMMANDS = ('/抽签',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for game-history."""
|
"""Generated reviewed adapter for game-history."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'game-history'
|
PROFILE = 'game-history'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '游戏记录'
|
DESCRIPTION = '游戏记录'
|
||||||
HELP_ENTRIES = ('/游戏记录 /个人记录 /gamehistory',)
|
HELP_ENTRIES = ('/游戏记录 /个人记录 /gamehistory',)
|
||||||
COMMANDS = ('/游戏记录', '/个人记录', '/gamehistory')
|
COMMANDS = ('/游戏记录', '/个人记录', '/gamehistory')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for guess-number."""
|
"""Generated reviewed adapter for guess-number."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'guess-number'
|
PROFILE = 'guess-number'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '群内持久化猜数字游戏'
|
DESCRIPTION = '群内持久化猜数字游戏'
|
||||||
HELP_ENTRIES = ('/猜数字 开始 [上限];/猜 数字;/猜数字 [状态|结束]',)
|
HELP_ENTRIES = ('/猜数字 开始 [上限];/猜 数字;/猜数字 [状态|结束]',)
|
||||||
COMMANDS = ('/猜数字', '/猜')
|
COMMANDS = ('/猜数字', '/猜')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for history-search."""
|
"""Generated reviewed adapter for history-search."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'history-search'
|
PROFILE = 'history-search'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '搜索本地群聊文字记录'
|
DESCRIPTION = '搜索本地群聊文字记录'
|
||||||
HELP_ENTRIES = ('/查记录 关键词',)
|
HELP_ENTRIES = ('/查记录 关键词',)
|
||||||
COMMANDS = ('/查记录', '/历史搜索')
|
COMMANDS = ('/查记录', '/历史搜索')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for primogem-economy."""
|
"""Generated reviewed adapter for primogem-economy."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'primogem-economy'
|
PROFILE = 'primogem-economy'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '原石系统'
|
DESCRIPTION = '原石系统'
|
||||||
HELP_ENTRIES = ('/原石祈愿 /原石榜;/扫码加权 人;/Taffy发话 [定时HH:MM] 内容',)
|
HELP_ENTRIES = ('/原石祈愿 /原石榜;/扫码加权 人;/Taffy发话 [定时HH:MM] 内容',)
|
||||||
COMMANDS = ('/原石祈愿', '/祈愿', '/wish', '/原石榜', '/primogem榜', '/扫码加权', '/扫码概率', '/Taffy发话', '/taffysay')
|
COMMANDS = ('/原石祈愿', '/祈愿', '/wish', '/原石榜', '/primogem榜', '/扫码加权', '/扫码概率', '/Taffy发话', '/taffysay')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for random-number."""
|
"""Generated reviewed adapter for random-number."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'random-number'
|
PROFILE = 'random-number'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '在指定整数范围内取随机数'
|
DESCRIPTION = '在指定整数范围内取随机数'
|
||||||
HELP_ENTRIES = ('/随机数 最小值 最大值',)
|
HELP_ENTRIES = ('/随机数 最小值 最大值',)
|
||||||
COMMANDS = ('/随机数',)
|
COMMANDS = ('/随机数',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for repeat-ranking."""
|
"""Generated reviewed adapter for repeat-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'repeat-ranking'
|
PROFILE = 'repeat-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '查看北京时间今日复读排行'
|
DESCRIPTION = '查看北京时间今日复读排行'
|
||||||
HELP_ENTRIES = ('/复读榜',)
|
HELP_ENTRIES = ('/复读榜',)
|
||||||
COMMANDS = ('/复读榜', '/复读')
|
COMMANDS = ('/复读榜', '/复读')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for rock-paper-scissors."""
|
"""Generated reviewed adapter for rock-paper-scissors."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'rock-paper-scissors'
|
PROFILE = 'rock-paper-scissors'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '多人石头剪刀布游戏'
|
DESCRIPTION = '多人石头剪刀布游戏'
|
||||||
HELP_ENTRIES = ('/石头剪刀布 [开始|加入|出 选择|状态|结算|结束]',)
|
HELP_ENTRIES = ('/石头剪刀布 [开始|加入|出 选择|状态|结算|结束]',)
|
||||||
COMMANDS = ('/石头剪刀布',)
|
COMMANDS = ('/石头剪刀布',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for scan-ranking."""
|
"""Generated reviewed adapter for scan-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'scan-ranking'
|
PROFILE = 'scan-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '查看北京时间今日扫码排行'
|
DESCRIPTION = '查看北京时间今日扫码排行'
|
||||||
HELP_ENTRIES = ('/扫码排行',)
|
HELP_ENTRIES = ('/扫码排行',)
|
||||||
COMMANDS = ('/扫码排行', '/扫码榜', '/今日扫码')
|
COMMANDS = ('/扫码排行', '/扫码榜', '/今日扫码')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for single-player-games."""
|
"""Generated reviewed adapter for single-player-games."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'single-player-games'
|
PROFILE = 'single-player-games'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '摩拉游戏'
|
DESCRIPTION = '摩拉游戏'
|
||||||
HELP_ENTRIES = ('/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助',)
|
HELP_ENTRIES = ('/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助',)
|
||||||
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for steam-presence."""
|
"""Generated reviewed adapter for steam-presence."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'steam-presence'
|
PROFILE = 'steam-presence'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = 'Steam 状态与时长'
|
DESCRIPTION = 'Steam 状态与时长'
|
||||||
HELP_ENTRIES = ('/steam;/steam时长 游戏[、玩家]',)
|
HELP_ENTRIES = ('/steam;/steam时长 游戏[、玩家]',)
|
||||||
COMMANDS = ('/steam',)
|
COMMANDS = ('/steam',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for trivia-quiz."""
|
"""Generated reviewed adapter for trivia-quiz."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'trivia-quiz'
|
PROFILE = 'trivia-quiz'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '冷门知识问答'
|
DESCRIPTION = '冷门知识问答'
|
||||||
HELP_ENTRIES = ('/回答 A;/answer A',)
|
HELP_ENTRIES = ('/回答 A;/answer A',)
|
||||||
COMMANDS = ('/回答', '/answer')
|
COMMANDS = ('/回答', '/answer')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for context-summary."""
|
"""Generated reviewed adapter for context-summary."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'context-summary'
|
PROFILE = 'context-summary'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '上下文总结'
|
DESCRIPTION = '上下文总结'
|
||||||
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
||||||
COMMANDS = ('/上下文', '/context')
|
COMMANDS = ('/上下文', '/context')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for debug-telemetry."""
|
"""Generated reviewed adapter for debug-telemetry."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'debug-telemetry'
|
PROFILE = 'debug-telemetry'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '埋点 p50/p95'
|
DESCRIPTION = '埋点 p50/p95'
|
||||||
HELP_ENTRIES = ('/debug',)
|
HELP_ENTRIES = ('/debug',)
|
||||||
COMMANDS = ('/debug',)
|
COMMANDS = ('/debug',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for jwebot-version."""
|
"""Generated reviewed adapter for jwebot-version."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'jwebot-version'
|
PROFILE = 'jwebot-version'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = 'JWeBot 版本'
|
DESCRIPTION = 'JWeBot 版本'
|
||||||
HELP_ENTRIES = ('/version /版本',)
|
HELP_ENTRIES = ('/version /版本',)
|
||||||
COMMANDS = ('/版本', '/version')
|
COMMANDS = ('/版本', '/version')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for person-directory."""
|
"""Generated reviewed adapter for person-directory."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'person-directory'
|
PROFILE = 'person-directory'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '人物索引'
|
DESCRIPTION = '人物索引'
|
||||||
HELP_ENTRIES = ('/人物列表 [页码] /people;/人物 [名称或别名] /person',)
|
HELP_ENTRIES = ('/人物列表 [页码] /people;/人物 [名称或别名] /person',)
|
||||||
COMMANDS = ('/人物列表', '/别名列表', '/people', '/人物', '/个人属性', '/person')
|
COMMANDS = ('/人物列表', '/别名列表', '/people', '/人物', '/个人属性', '/person')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for person-memory."""
|
"""Generated reviewed adapter for person-memory."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'person-memory'
|
PROFILE = 'person-memory'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '管理个人和同群人物记忆'
|
DESCRIPTION = '管理个人和同群人物记忆'
|
||||||
HELP_ENTRIES = ('/记住 内容;/我的记忆;/忘记 关键词或全部;/人物记忆 [显示名]',)
|
HELP_ENTRIES = ('/记住 内容;/我的记忆;/忘记 关键词或全部;/人物记忆 [显示名]',)
|
||||||
COMMANDS = ('/记住', '/我的记忆', '/忘记', '/人物记忆', '/记忆帮助')
|
COMMANDS = ('/记住', '/我的记忆', '/忘记', '/人物记忆', '/记忆帮助')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for server-status."""
|
"""Generated reviewed adapter for server-status."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'server-status'
|
PROFILE = 'server-status'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '服务器状态'
|
DESCRIPTION = '服务器状态'
|
||||||
HELP_ENTRIES = ('/状态;/status',)
|
HELP_ENTRIES = ('/状态;/status',)
|
||||||
COMMANDS = ('/状态', '/status')
|
COMMANDS = ('/状态', '/status')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+31
-14
@@ -32,10 +32,14 @@ from jwebot_official_extensions.player_games import ( # noqa: E402
|
|||||||
|
|
||||||
|
|
||||||
COMPENSATION_AMOUNT = 200
|
COMPENSATION_AMOUNT = 200
|
||||||
|
MORA_COMPENSATION_RELEASES = {
|
||||||
|
"beta 2.0.0.1021": 400,
|
||||||
|
}
|
||||||
PRIMOGEM_COMPENSATION_RELEASES = {
|
PRIMOGEM_COMPENSATION_RELEASES = {
|
||||||
"beta 1.0.5.1016": 1,
|
"beta 1.0.5.1016": 1,
|
||||||
"beta 1.1.0.1016": 1,
|
"beta 1.1.0.1016": 1,
|
||||||
"beta 1.2.0.1019": 1,
|
"beta 1.2.0.1019": 1,
|
||||||
|
"beta 2.0.0.1021": 2,
|
||||||
}
|
}
|
||||||
MAX_BALANCE = 1_000_000_000
|
MAX_BALANCE = 1_000_000_000
|
||||||
MAX_SUMMARY_CHARS = 300
|
MAX_SUMMARY_CHARS = 300
|
||||||
@@ -127,13 +131,14 @@ def read_changelog_summary(path: Path, release: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def build_announcement(release: str, summary: str) -> str:
|
def build_announcement(release: str, summary: str) -> str:
|
||||||
|
mora = mora_compensation_amount(release)
|
||||||
primogems = primogem_compensation_amount(release)
|
primogems = primogem_compensation_amount(release)
|
||||||
extra = f",原石 x{primogems}" if primogems else ""
|
extra = f",原石 x{primogems}" if primogems else ""
|
||||||
announcement = (
|
announcement = (
|
||||||
"亲爱的群友:\n"
|
"亲爱的群友:\n"
|
||||||
f"{release} 更新现已完成。\n"
|
f"{release} 更新现已完成。\n"
|
||||||
f"更新内容:{summary}\n"
|
f"更新内容:{summary}\n"
|
||||||
f"更新补偿:摩拉 x{COMPENSATION_AMOUNT}{extra}\n"
|
f"更新补偿:摩拉 x{mora}{extra}\n"
|
||||||
"补偿范围:截至本公告发布前已创建摩拉账户的玩家\n"
|
"补偿范围:截至本公告发布前已创建摩拉账户的玩家\n"
|
||||||
"发放方式:补偿已直接到账,无需领取\n"
|
"发放方式:补偿已直接到账,无需领取\n"
|
||||||
"感谢大家对 Taffy 的支持与理解,祝大家游戏愉快!"
|
"感谢大家对 Taffy 的支持与理解,祝大家游戏愉快!"
|
||||||
@@ -145,10 +150,16 @@ def build_announcement(release: str, summary: str) -> str:
|
|||||||
|
|
||||||
def build_lifecycle_message(release: str, stage: str) -> str:
|
def build_lifecycle_message(release: str, stage: str) -> str:
|
||||||
if stage == "shutdown":
|
if stage == "shutdown":
|
||||||
message = (
|
if release == "beta 2.0.0.1021":
|
||||||
f"Taffy 准备停机更新到 {release},会短暂离线一下;"
|
message = (
|
||||||
"别慌,我很快回来喵"
|
"Taffy 准备停机更新,本次更新是架构级大版本更新,"
|
||||||
)
|
f"预期版本{release},离线的时候保持耐心迎接Taffy的归来哦~"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
message = (
|
||||||
|
f"Taffy 准备停机更新到 {release},会短暂离线一下;"
|
||||||
|
"别慌,我很快回来喵"
|
||||||
|
)
|
||||||
elif stage == "startup":
|
elif stage == "startup":
|
||||||
message = (
|
message = (
|
||||||
f"Taffy 刚上线,正在做 {release} 的最后自检;"
|
f"Taffy 刚上线,正在做 {release} 的最后自检;"
|
||||||
@@ -165,6 +176,10 @@ def compensation_key(release: str) -> str:
|
|||||||
return f"release_compensation:{release}"
|
return f"release_compensation:{release}"
|
||||||
|
|
||||||
|
|
||||||
|
def mora_compensation_amount(release: str) -> int:
|
||||||
|
return int(MORA_COMPENSATION_RELEASES.get(release, COMPENSATION_AMOUNT))
|
||||||
|
|
||||||
|
|
||||||
def primogem_compensation_amount(release: str) -> int:
|
def primogem_compensation_amount(release: str) -> int:
|
||||||
return int(PRIMOGEM_COMPENSATION_RELEASES.get(release, 0))
|
return int(PRIMOGEM_COMPENSATION_RELEASES.get(release, 0))
|
||||||
|
|
||||||
@@ -196,6 +211,7 @@ def release_groups(database_path: Path) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def checked_marker(raw: str, release: str) -> dict[str, Any]:
|
def checked_marker(raw: str, release: str) -> dict[str, Any]:
|
||||||
|
expected_mora = mora_compensation_amount(release)
|
||||||
expected_primogems = primogem_compensation_amount(release)
|
expected_primogems = primogem_compensation_amount(release)
|
||||||
try:
|
try:
|
||||||
marker = json.loads(raw)
|
marker = json.loads(raw)
|
||||||
@@ -204,8 +220,8 @@ def checked_marker(raw: str, release: str) -> dict[str, Any]:
|
|||||||
if (
|
if (
|
||||||
not isinstance(marker, dict)
|
not isinstance(marker, dict)
|
||||||
or marker.get("release") != release
|
or marker.get("release") != release
|
||||||
or marker.get("amount") != COMPENSATION_AMOUNT
|
or marker.get("amount") != expected_mora
|
||||||
or marker.get("mora_amount") not in {None, COMPENSATION_AMOUNT}
|
or marker.get("mora_amount") not in {None, expected_mora}
|
||||||
or marker.get("primogem_amount") not in {None, expected_primogems}
|
or marker.get("primogem_amount") not in {None, expected_primogems}
|
||||||
or not isinstance(marker.get("group_fingerprints"), list)
|
or not isinstance(marker.get("group_fingerprints"), list)
|
||||||
):
|
):
|
||||||
@@ -287,6 +303,7 @@ def apply_compensation(
|
|||||||
groups = sorted({str(row["group_fingerprint"]) for row in rows})
|
groups = sorted({str(row["group_fingerprint"]) for row in rows})
|
||||||
old_balance_sum = 0
|
old_balance_sum = 0
|
||||||
old_primogem_sum = 0
|
old_primogem_sum = 0
|
||||||
|
mora_amount = mora_compensation_amount(release)
|
||||||
primogem_amount = primogem_compensation_amount(release)
|
primogem_amount = primogem_compensation_amount(release)
|
||||||
timestamp = isoformat_utc(applied_at)
|
timestamp = isoformat_utc(applied_at)
|
||||||
event_reason = f"{release} 版本更新补偿"
|
event_reason = f"{release} 版本更新补偿"
|
||||||
@@ -302,14 +319,14 @@ def apply_compensation(
|
|||||||
balance = state.get("balance")
|
balance = state.get("balance")
|
||||||
if (
|
if (
|
||||||
type(balance) is not int
|
type(balance) is not int
|
||||||
or not 0 <= balance <= MAX_BALANCE - COMPENSATION_AMOUNT
|
or not 0 <= balance <= MAX_BALANCE - mora_amount
|
||||||
):
|
):
|
||||||
raise ReleaseUpdateError("Mora account balance is invalid")
|
raise ReleaseUpdateError("Mora account balance is invalid")
|
||||||
old_balance_sum += balance
|
old_balance_sum += balance
|
||||||
old_primogem_sum += int(state["primogems"])
|
old_primogem_sum += int(state["primogems"])
|
||||||
state["balance"] = balance + COMPENSATION_AMOUNT
|
state["balance"] = balance + mora_amount
|
||||||
state["primogems"] += primogem_amount
|
state["primogems"] += primogem_amount
|
||||||
append_coin_event(state, COMPENSATION_AMOUNT, event_reason)
|
append_coin_event(state, mora_amount, event_reason)
|
||||||
state["coin_events"][-1]["created_at"] = applied_at.timestamp()
|
state["coin_events"][-1]["created_at"] = applied_at.timestamp()
|
||||||
if primogem_amount:
|
if primogem_amount:
|
||||||
append_primogem_event(
|
append_primogem_event(
|
||||||
@@ -335,14 +352,14 @@ def apply_compensation(
|
|||||||
)
|
)
|
||||||
marker = {
|
marker = {
|
||||||
"release": release,
|
"release": release,
|
||||||
"amount": COMPENSATION_AMOUNT,
|
"amount": mora_amount,
|
||||||
"mora_amount": COMPENSATION_AMOUNT,
|
"mora_amount": mora_amount,
|
||||||
"primogem_amount": primogem_amount,
|
"primogem_amount": primogem_amount,
|
||||||
"accounts": len(rows),
|
"accounts": len(rows),
|
||||||
"group_fingerprints": groups,
|
"group_fingerprints": groups,
|
||||||
"old_balance_sum": old_balance_sum,
|
"old_balance_sum": old_balance_sum,
|
||||||
"new_balance_sum": (
|
"new_balance_sum": (
|
||||||
old_balance_sum + COMPENSATION_AMOUNT * len(rows)
|
old_balance_sum + mora_amount * len(rows)
|
||||||
),
|
),
|
||||||
"old_primogem_sum": old_primogem_sum,
|
"old_primogem_sum": old_primogem_sum,
|
||||||
"new_primogem_sum": (
|
"new_primogem_sum": (
|
||||||
@@ -594,7 +611,7 @@ def run_release_update(
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"release": release,
|
"release": release,
|
||||||
"compensation": compensation_state,
|
"compensation": compensation_state,
|
||||||
"amount_each": COMPENSATION_AMOUNT,
|
"amount_each": mora_compensation_amount(release),
|
||||||
"primogems_each": primogem_compensation_amount(release),
|
"primogems_each": primogem_compensation_amount(release),
|
||||||
"accounts": int(marker["accounts"]),
|
"accounts": int(marker["accounts"]),
|
||||||
"groups": len(groups),
|
"groups": len(groups),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-config"
|
name = "jwebot-config"
|
||||||
version = "1.2.8b1+1019"
|
version = "2.0.0b1+1021"
|
||||||
description = "Profile-based configuration compiler for JWeBot"
|
description = "Profile-based configuration compiler for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -36,4 +36,4 @@ __all__ = [
|
|||||||
"write_default_profile",
|
"write_default_profile",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.8b1+1019"
|
__version__ = "2.0.0b1+1021"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-extension-host"
|
name = "jwebot-extension-host"
|
||||||
version = "1.2.8b1+1019"
|
version = "2.0.0b1+1021"
|
||||||
description = "Single-release, single-invocation process host for JWeBot extensions"
|
description = "Single-release, single-invocation process host for JWeBot extensions"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.2.8b1+1019",
|
"jwebot-sdk==2.0.0b1+1021",
|
||||||
"jwebot-kernel==1.2.8b1+1019",
|
"jwebot-kernel==2.0.0b1+1021",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ __all__ = [
|
|||||||
"process_request",
|
"process_request",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.8b1+1019"
|
__version__ = "2.0.0b1+1021"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-kernel"
|
name = "jwebot-kernel"
|
||||||
version = "1.2.8b1+1019"
|
version = "2.0.0b1+1021"
|
||||||
description = "Stable routing and extension authority for JWeBot"
|
description = "Stable routing and extension authority for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-config==1.2.8b1+1019",
|
"jwebot-config==2.0.0b1+1021",
|
||||||
"jwebot-sdk==1.2.8b1+1019",
|
"jwebot-sdk==2.0.0b1+1021",
|
||||||
"jwebot-official-extensions==1.2.8b1+1019",
|
"jwebot-official-extensions==2.0.0b1+1021",
|
||||||
"tomli>=2; python_version < '3.11'",
|
"tomli>=2; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -107,4 +107,4 @@ __all__ = [
|
|||||||
"verify_installed_release_signature",
|
"verify_installed_release_signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.8b1+1019"
|
__version__ = "2.0.0b1+1021"
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
"""Fail-closed conversation identities for local OCR shadow testing."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Mapping
|
||||||
|
|
||||||
|
|
||||||
|
SHADOW_CONFIG_ENV = "JWEBOT_CONVERSATION_SHADOW_CONFIG"
|
||||||
|
SHADOW_CONFIG_VERSION = 1
|
||||||
|
MAX_SHADOW_TARGETS = 8
|
||||||
|
MAX_EVENT_TEXT = 5_000
|
||||||
|
CONVERSATION_KEY = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$")
|
||||||
|
TITLE_FINGERPRINT = re.compile(r"^[0-9a-f]{64}$")
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationConfigError(ValueError):
|
||||||
|
"""Raised when an opt-in shadow conversation registry is invalid."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationKind(str, Enum):
|
||||||
|
GROUP = "group"
|
||||||
|
DIRECT = "direct"
|
||||||
|
|
||||||
|
|
||||||
|
def _required_text(value: object, *, name: str, limit: int) -> str:
|
||||||
|
text = str(value or "").strip()
|
||||||
|
if not text:
|
||||||
|
raise ConversationConfigError(f"{name} is required")
|
||||||
|
if len(text) > limit:
|
||||||
|
raise ConversationConfigError(f"{name} exceeds {limit} characters")
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _trigger_question(text: str, *, bot_mention: str, direct: bool) -> str:
|
||||||
|
mention_prefix = r"(?:[@@]\s*)?" if direct else r"[@@]\s*"
|
||||||
|
mention = re.compile(
|
||||||
|
rf"^\s*{mention_prefix}{re.escape(bot_mention)}"
|
||||||
|
r"(?:\s*[::,,])?\s*(\S.*)$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
for line in text.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if direct and stripped.startswith("/"):
|
||||||
|
return stripped
|
||||||
|
match = mention.match(line)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ConversationTarget:
|
||||||
|
"""One explicitly registered title fingerprint in shadow mode."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
kind: ConversationKind
|
||||||
|
title_fingerprint: str
|
||||||
|
sender: str = ""
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if not CONVERSATION_KEY.fullmatch(self.key):
|
||||||
|
raise ConversationConfigError("conversation key must be an opaque slug")
|
||||||
|
if not TITLE_FINGERPRINT.fullmatch(self.title_fingerprint):
|
||||||
|
raise ConversationConfigError(
|
||||||
|
"title_fingerprint must be a lowercase SHA-256 digest"
|
||||||
|
)
|
||||||
|
if self.kind is ConversationKind.DIRECT:
|
||||||
|
_required_text(self.sender, name="direct sender", limit=120)
|
||||||
|
elif self.sender:
|
||||||
|
raise ConversationConfigError("group targets cannot define a fixed sender")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mapping(cls, value: Mapping[str, Any]) -> "ConversationTarget":
|
||||||
|
if not isinstance(value, Mapping):
|
||||||
|
raise ConversationConfigError("conversation target must be an object")
|
||||||
|
unknown = set(value) - {
|
||||||
|
"key",
|
||||||
|
"kind",
|
||||||
|
"title_fingerprint",
|
||||||
|
"sender",
|
||||||
|
}
|
||||||
|
if unknown:
|
||||||
|
raise ConversationConfigError("conversation target has unknown fields")
|
||||||
|
try:
|
||||||
|
kind = ConversationKind(str(value.get("kind", "")))
|
||||||
|
except ValueError as error:
|
||||||
|
raise ConversationConfigError("conversation kind is invalid") from error
|
||||||
|
return cls(
|
||||||
|
key=_required_text(value.get("key"), name="conversation key", limit=64),
|
||||||
|
kind=kind,
|
||||||
|
title_fingerprint=_required_text(
|
||||||
|
value.get("title_fingerprint"),
|
||||||
|
name="title_fingerprint",
|
||||||
|
limit=64,
|
||||||
|
).casefold(),
|
||||||
|
sender=str(value.get("sender", "")).strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def question(self, text: str, *, bot_mention: str) -> str:
|
||||||
|
return _trigger_question(
|
||||||
|
text,
|
||||||
|
bot_mention=bot_mention,
|
||||||
|
direct=self.kind is ConversationKind.DIRECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ShadowConversationRegistry:
|
||||||
|
"""A registry that can observe but can never authorize delivery."""
|
||||||
|
|
||||||
|
enabled: bool = False
|
||||||
|
targets: tuple[ConversationTarget, ...] = ()
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
keys = [target.key for target in self.targets]
|
||||||
|
fingerprints = [target.title_fingerprint for target in self.targets]
|
||||||
|
if len(keys) != len(set(keys)):
|
||||||
|
raise ConversationConfigError("conversation keys must be unique")
|
||||||
|
if len(fingerprints) != len(set(fingerprints)):
|
||||||
|
raise ConversationConfigError("title fingerprints must be unique")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def disabled(cls) -> "ShadowConversationRegistry":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mapping(cls, value: Mapping[str, Any]) -> "ShadowConversationRegistry":
|
||||||
|
if not isinstance(value, Mapping):
|
||||||
|
raise ConversationConfigError("shadow registry must be an object")
|
||||||
|
unknown = set(value) - {"format_version", "enabled", "targets"}
|
||||||
|
if unknown:
|
||||||
|
raise ConversationConfigError("shadow registry has unknown fields")
|
||||||
|
if value.get("format_version") != SHADOW_CONFIG_VERSION:
|
||||||
|
raise ConversationConfigError("unsupported shadow registry version")
|
||||||
|
enabled = value.get("enabled", False)
|
||||||
|
if not isinstance(enabled, bool):
|
||||||
|
raise ConversationConfigError("shadow registry enabled must be boolean")
|
||||||
|
raw_targets = value.get("targets", [])
|
||||||
|
if not isinstance(raw_targets, list):
|
||||||
|
raise ConversationConfigError("shadow registry targets must be a list")
|
||||||
|
if len(raw_targets) > MAX_SHADOW_TARGETS:
|
||||||
|
raise ConversationConfigError("shadow registry has too many targets")
|
||||||
|
targets = tuple(ConversationTarget.from_mapping(item) for item in raw_targets)
|
||||||
|
if any(target.kind is not ConversationKind.DIRECT for target in targets):
|
||||||
|
raise ConversationConfigError("shadow registry accepts direct targets only")
|
||||||
|
return cls(enabled=enabled, targets=targets)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_environment(
|
||||||
|
cls,
|
||||||
|
environment: Mapping[str, str] | None = None,
|
||||||
|
) -> "ShadowConversationRegistry":
|
||||||
|
source = environment if environment is not None else os.environ
|
||||||
|
raw_path = str(source.get(SHADOW_CONFIG_ENV, "")).strip()
|
||||||
|
if not raw_path:
|
||||||
|
return cls.disabled()
|
||||||
|
path = Path(raw_path).expanduser()
|
||||||
|
if path.is_symlink() or not path.is_file():
|
||||||
|
raise ConversationConfigError("shadow registry path must be a regular file")
|
||||||
|
try:
|
||||||
|
value = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError) as error:
|
||||||
|
raise ConversationConfigError("shadow registry cannot be read") from error
|
||||||
|
if not isinstance(value, Mapping):
|
||||||
|
raise ConversationConfigError("shadow registry must be an object")
|
||||||
|
return cls.from_mapping(value)
|
||||||
|
|
||||||
|
def target_for_fingerprint(
|
||||||
|
self,
|
||||||
|
fingerprint: str,
|
||||||
|
) -> ConversationTarget | None:
|
||||||
|
if not self.enabled:
|
||||||
|
return None
|
||||||
|
normalized = str(fingerprint).casefold()
|
||||||
|
return next(
|
||||||
|
(
|
||||||
|
target
|
||||||
|
for target in self.targets
|
||||||
|
if target.title_fingerprint == normalized
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_shadow_event(
|
||||||
|
target: ConversationTarget,
|
||||||
|
*,
|
||||||
|
observed_at: str,
|
||||||
|
text: str,
|
||||||
|
body_confidence: float,
|
||||||
|
visual_signature: str,
|
||||||
|
bot_mention: str,
|
||||||
|
observed_sender: str = "",
|
||||||
|
sender_confidence: float = 0.0,
|
||||||
|
ordinal: int = 0,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Build a non-dispatchable observation for synthetic and shadow tests."""
|
||||||
|
|
||||||
|
content = _required_text(text, name="event text", limit=MAX_EVENT_TEXT)
|
||||||
|
question = target.question(content, bot_mention=bot_mention)
|
||||||
|
sender = (
|
||||||
|
target.sender
|
||||||
|
if target.kind is ConversationKind.DIRECT
|
||||||
|
else _required_text(observed_sender, name="observed sender", limit=120)
|
||||||
|
)
|
||||||
|
effective_sender_confidence = (
|
||||||
|
1.0
|
||||||
|
if target.kind is ConversationKind.DIRECT
|
||||||
|
else max(0.0, min(1.0, float(sender_confidence)))
|
||||||
|
)
|
||||||
|
effective_body_confidence = max(0.0, min(1.0, float(body_confidence)))
|
||||||
|
seed = (
|
||||||
|
f"{target.key}\n{observed_at}\n{ordinal}\n{visual_signature}\n"
|
||||||
|
f"{sender}\n{content}"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"version": 1,
|
||||||
|
"event_id": f"shadow-{hashlib.sha256(seed.encode()).hexdigest()[:24]}",
|
||||||
|
"observed_at": observed_at,
|
||||||
|
"sender": sender,
|
||||||
|
"text": content,
|
||||||
|
"question": question,
|
||||||
|
"confidence": min(
|
||||||
|
effective_sender_confidence,
|
||||||
|
effective_body_confidence,
|
||||||
|
),
|
||||||
|
"sender_confidence": effective_sender_confidence,
|
||||||
|
"body_confidence": effective_body_confidence,
|
||||||
|
"side": "incoming",
|
||||||
|
"mention_bot": bool(question),
|
||||||
|
"group_fingerprint": target.title_fingerprint,
|
||||||
|
"conversation_key": target.key,
|
||||||
|
"conversation_kind": target.kind.value,
|
||||||
|
"source": (
|
||||||
|
"rapidocr-private-shadow"
|
||||||
|
if target.kind is ConversationKind.DIRECT
|
||||||
|
else "rapidocr-group-shadow"
|
||||||
|
),
|
||||||
|
"shadow_only": True,
|
||||||
|
"dispatch_allowed": False,
|
||||||
|
}
|
||||||
@@ -8009,6 +8009,20 @@ class EdgeApplication:
|
|||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
event_id = str(event.get("event_id", ""))
|
event_id = str(event.get("event_id", ""))
|
||||||
group_id = str(event.get("group_fingerprint", ""))
|
group_id = str(event.get("group_fingerprint", ""))
|
||||||
|
if event.get("shadow_only") or event.get("dispatch_allowed") is False:
|
||||||
|
self.telemetry.elapsed(
|
||||||
|
"edge.event.accepted",
|
||||||
|
started,
|
||||||
|
status="shadow_ignored",
|
||||||
|
event_id=event_id,
|
||||||
|
group_id=group_id,
|
||||||
|
route="none",
|
||||||
|
execution_path="none",
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "ignored",
|
||||||
|
"reason": "shadow_only",
|
||||||
|
}
|
||||||
question = event_question(event)
|
question = event_question(event)
|
||||||
decision = route_question(question)
|
decision = route_question(question)
|
||||||
route = decision.route.value
|
route = decision.route.value
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-official-extensions"
|
name = "jwebot-official-extensions"
|
||||||
version = "1.2.8b1+1019"
|
version = "2.0.0b1+1021"
|
||||||
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.2.8b1+1019",
|
"jwebot-sdk==2.0.0b1+1021",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ __all__ = [
|
|||||||
"render_feature_list",
|
"render_feature_list",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.8b1+1019"
|
__version__ = "2.0.0b1+1021"
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin."""
|
"""Generated reviewed adapter for coin."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin'
|
PROFILE = 'coin'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '抛一枚硬币'
|
DESCRIPTION = '抛一枚硬币'
|
||||||
HELP_ENTRIES = ('/翻硬币',)
|
HELP_ENTRIES = ('/翻硬币',)
|
||||||
COMMANDS = ('/翻硬币',)
|
COMMANDS = ('/翻硬币',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-loan."""
|
"""Generated reviewed adapter for coin-loan."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-loan'
|
PROFILE = 'coin-loan'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '贷款'
|
DESCRIPTION = '贷款'
|
||||||
HELP_ENTRIES = ('/贷款;/还款',)
|
HELP_ENTRIES = ('/贷款;/还款',)
|
||||||
COMMANDS = ('/贷款', '/借款', '/loan', '/borrow', '/还款', '/repay')
|
COMMANDS = ('/贷款', '/借款', '/loan', '/borrow', '/还款', '/repay')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-ranking."""
|
"""Generated reviewed adapter for coin-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-ranking'
|
PROFILE = 'coin-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '富榜'
|
DESCRIPTION = '富榜'
|
||||||
HELP_ENTRIES = ('/大富翁榜',)
|
HELP_ENTRIES = ('/大富翁榜',)
|
||||||
COMMANDS = ('/摩拉排行', '/摩拉排行榜', '/金币排行', '/金币排行榜', '/大富翁榜', '/richlist')
|
COMMANDS = ('/摩拉排行', '/摩拉排行榜', '/金币排行', '/金币排行榜', '/大富翁榜', '/richlist')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-taffy."""
|
"""Generated reviewed adapter for coin-taffy."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-taffy'
|
PROFILE = 'coin-taffy'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = 'Taffy摩拉'
|
DESCRIPTION = 'Taffy摩拉'
|
||||||
HELP_ENTRIES = ('/Taffy摩拉(兼容 /Taffy金币)',)
|
HELP_ENTRIES = ('/Taffy摩拉(兼容 /Taffy金币)',)
|
||||||
COMMANDS = ('/Taffy摩拉', '/Taffy金币', '/taffycoins')
|
COMMANDS = ('/Taffy摩拉', '/Taffy金币', '/taffycoins')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for coin-transfer."""
|
"""Generated reviewed adapter for coin-transfer."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'coin-transfer'
|
PROFILE = 'coin-transfer'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '摩拉转账'
|
DESCRIPTION = '摩拉转账'
|
||||||
HELP_ENTRIES = ('/转账 对方 金额;/transfer',)
|
HELP_ENTRIES = ('/转账 对方 金额;/transfer',)
|
||||||
COMMANDS = ('/转账', '/transfer')
|
COMMANDS = ('/转账', '/transfer')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for context-summary."""
|
"""Generated reviewed adapter for context-summary."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'context-summary'
|
PROFILE = 'context-summary'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '上下文总结'
|
DESCRIPTION = '上下文总结'
|
||||||
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
||||||
COMMANDS = ('/上下文', '/context')
|
COMMANDS = ('/上下文', '/context')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for cs-major-autochess."""
|
"""Generated reviewed adapter for cs-major-autochess."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'cs-major-autochess'
|
PROFILE = 'cs-major-autochess'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '自走棋'
|
DESCRIPTION = '自走棋'
|
||||||
HELP_ENTRIES = ('/自走棋 开始|报名|选',)
|
HELP_ENTRIES = ('/自走棋 开始|报名|选',)
|
||||||
COMMANDS = ('/自走棋', '/cs自走棋', '/autochess', '/选', '/刷新', '/锁定', '/战术', '/升级教练', '/上阵', '/下阵', '/出售', '/阵容', '/赛程', '/结束回合')
|
COMMANDS = ('/自走棋', '/cs自走棋', '/autochess', '/选', '/刷新', '/锁定', '/战术', '/升级教练', '/上阵', '/下阵', '/出售', '/阵容', '/赛程', '/结束回合')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for daily-fortune."""
|
"""Generated reviewed adapter for daily-fortune."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'daily-fortune'
|
PROFILE = 'daily-fortune'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '生成当天稳定一致的运势'
|
DESCRIPTION = '生成当天稳定一致的运势'
|
||||||
HELP_ENTRIES = ('/今日运势',)
|
HELP_ENTRIES = ('/今日运势',)
|
||||||
COMMANDS = ('/今日运势',)
|
COMMANDS = ('/今日运势',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for debug-telemetry."""
|
"""Generated reviewed adapter for debug-telemetry."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'debug-telemetry'
|
PROFILE = 'debug-telemetry'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '埋点 p50/p95'
|
DESCRIPTION = '埋点 p50/p95'
|
||||||
HELP_ENTRIES = ('/debug',)
|
HELP_ENTRIES = ('/debug',)
|
||||||
COMMANDS = ('/debug',)
|
COMMANDS = ('/debug',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for dice."""
|
"""Generated reviewed adapter for dice."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'dice'
|
PROFILE = 'dice'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '骰子和多人骰子挑战'
|
DESCRIPTION = '骰子和多人骰子挑战'
|
||||||
HELP_ENTRIES = ('/丢骰子 [NdM]', '/丢骰子挑战 [5d6|5d20|状态|结算|战绩|结束]')
|
HELP_ENTRIES = ('/丢骰子 [NdM]', '/丢骰子挑战 [5d6|5d20|状态|结算|战绩|结束]')
|
||||||
COMMANDS = ('/丢骰子', '/骰子', '/丢骰子挑战', '/骰子挑战')
|
COMMANDS = ('/丢骰子', '/骰子', '/丢骰子挑战', '/骰子挑战')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for draw-lot."""
|
"""Generated reviewed adapter for draw-lot."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'draw-lot'
|
PROFILE = 'draw-lot'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '抽取一条本地签文'
|
DESCRIPTION = '抽取一条本地签文'
|
||||||
HELP_ENTRIES = ('/抽签',)
|
HELP_ENTRIES = ('/抽签',)
|
||||||
COMMANDS = ('/抽签',)
|
COMMANDS = ('/抽签',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for game-history."""
|
"""Generated reviewed adapter for game-history."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'game-history'
|
PROFILE = 'game-history'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '游戏记录'
|
DESCRIPTION = '游戏记录'
|
||||||
HELP_ENTRIES = ('/游戏记录 /个人记录 /gamehistory',)
|
HELP_ENTRIES = ('/游戏记录 /个人记录 /gamehistory',)
|
||||||
COMMANDS = ('/游戏记录', '/个人记录', '/gamehistory')
|
COMMANDS = ('/游戏记录', '/个人记录', '/gamehistory')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for guess-number."""
|
"""Generated reviewed adapter for guess-number."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'guess-number'
|
PROFILE = 'guess-number'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '群内持久化猜数字游戏'
|
DESCRIPTION = '群内持久化猜数字游戏'
|
||||||
HELP_ENTRIES = ('/猜数字 开始 [上限];/猜 数字;/猜数字 [状态|结束]',)
|
HELP_ENTRIES = ('/猜数字 开始 [上限];/猜 数字;/猜数字 [状态|结束]',)
|
||||||
COMMANDS = ('/猜数字', '/猜')
|
COMMANDS = ('/猜数字', '/猜')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for history-search."""
|
"""Generated reviewed adapter for history-search."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'history-search'
|
PROFILE = 'history-search'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '搜索本地群聊文字记录'
|
DESCRIPTION = '搜索本地群聊文字记录'
|
||||||
HELP_ENTRIES = ('/查记录 关键词',)
|
HELP_ENTRIES = ('/查记录 关键词',)
|
||||||
COMMANDS = ('/查记录', '/历史搜索')
|
COMMANDS = ('/查记录', '/历史搜索')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for jwebot-version."""
|
"""Generated reviewed adapter for jwebot-version."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'jwebot-version'
|
PROFILE = 'jwebot-version'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = 'JWeBot 版本'
|
DESCRIPTION = 'JWeBot 版本'
|
||||||
HELP_ENTRIES = ('/version /版本',)
|
HELP_ENTRIES = ('/version /版本',)
|
||||||
COMMANDS = ('/版本', '/version')
|
COMMANDS = ('/版本', '/version')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for person-directory."""
|
"""Generated reviewed adapter for person-directory."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'person-directory'
|
PROFILE = 'person-directory'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '人物索引'
|
DESCRIPTION = '人物索引'
|
||||||
HELP_ENTRIES = ('/人物列表 [页码] /people;/人物 [名称或别名] /person',)
|
HELP_ENTRIES = ('/人物列表 [页码] /people;/人物 [名称或别名] /person',)
|
||||||
COMMANDS = ('/人物列表', '/别名列表', '/people', '/人物', '/个人属性', '/person')
|
COMMANDS = ('/人物列表', '/别名列表', '/people', '/人物', '/个人属性', '/person')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for person-memory."""
|
"""Generated reviewed adapter for person-memory."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'person-memory'
|
PROFILE = 'person-memory'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '管理个人和同群人物记忆'
|
DESCRIPTION = '管理个人和同群人物记忆'
|
||||||
HELP_ENTRIES = ('/记住 内容;/我的记忆;/忘记 关键词或全部;/人物记忆 [显示名]',)
|
HELP_ENTRIES = ('/记住 内容;/我的记忆;/忘记 关键词或全部;/人物记忆 [显示名]',)
|
||||||
COMMANDS = ('/记住', '/我的记忆', '/忘记', '/人物记忆', '/记忆帮助')
|
COMMANDS = ('/记住', '/我的记忆', '/忘记', '/人物记忆', '/记忆帮助')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for primogem-economy."""
|
"""Generated reviewed adapter for primogem-economy."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'primogem-economy'
|
PROFILE = 'primogem-economy'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '原石系统'
|
DESCRIPTION = '原石系统'
|
||||||
HELP_ENTRIES = ('/原石祈愿 /原石榜;/扫码加权 人;/Taffy发话 [定时HH:MM] 内容',)
|
HELP_ENTRIES = ('/原石祈愿 /原石榜;/扫码加权 人;/Taffy发话 [定时HH:MM] 内容',)
|
||||||
COMMANDS = ('/原石祈愿', '/祈愿', '/wish', '/原石榜', '/primogem榜', '/扫码加权', '/扫码概率', '/Taffy发话', '/taffysay')
|
COMMANDS = ('/原石祈愿', '/祈愿', '/wish', '/原石榜', '/primogem榜', '/扫码加权', '/扫码概率', '/Taffy发话', '/taffysay')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for random-number."""
|
"""Generated reviewed adapter for random-number."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'random-number'
|
PROFILE = 'random-number'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '在指定整数范围内取随机数'
|
DESCRIPTION = '在指定整数范围内取随机数'
|
||||||
HELP_ENTRIES = ('/随机数 最小值 最大值',)
|
HELP_ENTRIES = ('/随机数 最小值 最大值',)
|
||||||
COMMANDS = ('/随机数',)
|
COMMANDS = ('/随机数',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for repeat-ranking."""
|
"""Generated reviewed adapter for repeat-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'repeat-ranking'
|
PROFILE = 'repeat-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '查看北京时间今日复读排行'
|
DESCRIPTION = '查看北京时间今日复读排行'
|
||||||
HELP_ENTRIES = ('/复读榜',)
|
HELP_ENTRIES = ('/复读榜',)
|
||||||
COMMANDS = ('/复读榜', '/复读')
|
COMMANDS = ('/复读榜', '/复读')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for rock-paper-scissors."""
|
"""Generated reviewed adapter for rock-paper-scissors."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'rock-paper-scissors'
|
PROFILE = 'rock-paper-scissors'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '多人石头剪刀布游戏'
|
DESCRIPTION = '多人石头剪刀布游戏'
|
||||||
HELP_ENTRIES = ('/石头剪刀布 [开始|加入|出 选择|状态|结算|结束]',)
|
HELP_ENTRIES = ('/石头剪刀布 [开始|加入|出 选择|状态|结算|结束]',)
|
||||||
COMMANDS = ('/石头剪刀布',)
|
COMMANDS = ('/石头剪刀布',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for scan-ranking."""
|
"""Generated reviewed adapter for scan-ranking."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'scan-ranking'
|
PROFILE = 'scan-ranking'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '查看北京时间今日扫码排行'
|
DESCRIPTION = '查看北京时间今日扫码排行'
|
||||||
HELP_ENTRIES = ('/扫码排行',)
|
HELP_ENTRIES = ('/扫码排行',)
|
||||||
COMMANDS = ('/扫码排行', '/扫码榜', '/今日扫码')
|
COMMANDS = ('/扫码排行', '/扫码榜', '/今日扫码')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for server-status."""
|
"""Generated reviewed adapter for server-status."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'server-status'
|
PROFILE = 'server-status'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'official'
|
|||||||
DESCRIPTION = '服务器状态'
|
DESCRIPTION = '服务器状态'
|
||||||
HELP_ENTRIES = ('/状态;/status',)
|
HELP_ENTRIES = ('/状态;/status',)
|
||||||
COMMANDS = ('/状态', '/status')
|
COMMANDS = ('/状态', '/status')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for single-player-games."""
|
"""Generated reviewed adapter for single-player-games."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'single-player-games'
|
PROFILE = 'single-player-games'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '摩拉游戏'
|
DESCRIPTION = '摩拉游戏'
|
||||||
HELP_ENTRIES = ('/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助',)
|
HELP_ENTRIES = ('/摩拉/原石 /抽奖 /彩票 /21点 /游戏帮助',)
|
||||||
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
COMMANDS = ('/摩拉', '/金币', '/wallet', '/原石', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for steam-presence."""
|
"""Generated reviewed adapter for steam-presence."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'steam-presence'
|
PROFILE = 'steam-presence'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = 'Steam 状态与时长'
|
DESCRIPTION = 'Steam 状态与时长'
|
||||||
HELP_ENTRIES = ('/steam;/steam时长 游戏[、玩家]',)
|
HELP_ENTRIES = ('/steam;/steam时长 游戏[、玩家]',)
|
||||||
COMMANDS = ('/steam',)
|
COMMANDS = ('/steam',)
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
+6
-33
@@ -1,6 +1,6 @@
|
|||||||
"""Generated reviewed adapter for trivia-quiz."""
|
"""Generated reviewed adapter for trivia-quiz."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = 'trivia-quiz'
|
PROFILE = 'trivia-quiz'
|
||||||
@@ -8,38 +8,11 @@ AUTHOR = 'jwebot'
|
|||||||
DESCRIPTION = '冷门知识问答'
|
DESCRIPTION = '冷门知识问答'
|
||||||
HELP_ENTRIES = ('/回答 A;/answer A',)
|
HELP_ENTRIES = ('/回答 A;/answer A',)
|
||||||
COMMANDS = ('/回答', '/answer')
|
COMMANDS = ('/回答', '/answer')
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-sdk"
|
name = "jwebot-sdk"
|
||||||
version = "1.2.8b1+1019"
|
version = "2.0.0b1+1021"
|
||||||
description = "Typed, capability-limited extension API for JWeBot plugins"
|
description = "Typed, capability-limited extension API for JWeBot plugins"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from .context import (
|
|||||||
PluginLogger,
|
PluginLogger,
|
||||||
PluginStorage,
|
PluginStorage,
|
||||||
Scheduler,
|
Scheduler,
|
||||||
|
ServiceCommandAdapter,
|
||||||
SingleGroupContext,
|
SingleGroupContext,
|
||||||
)
|
)
|
||||||
from .contracts import (
|
from .contracts import (
|
||||||
@@ -35,7 +36,8 @@ __all__ = [
|
|||||||
"PluginStorage",
|
"PluginStorage",
|
||||||
"ReplyPlan",
|
"ReplyPlan",
|
||||||
"Scheduler",
|
"Scheduler",
|
||||||
|
"ServiceCommandAdapter",
|
||||||
"SingleGroupContext",
|
"SingleGroupContext",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.8b1+1019"
|
__version__ = "2.0.0b1+1021"
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from .contracts import Event, ExtensionResult, Fact
|
from .contracts import Event, ExtensionResult, Fact, ReplyPlan
|
||||||
|
|
||||||
|
|
||||||
CommandHandler = Callable[[Event, str], ExtensionResult]
|
CommandHandler = Callable[[Event, str], ExtensionResult]
|
||||||
@@ -68,3 +68,67 @@ class SingleGroupContext(Protocol):
|
|||||||
facts: FactWriter
|
facts: FactWriter
|
||||||
scheduler: Scheduler
|
scheduler: Scheduler
|
||||||
logger: PluginLogger
|
logger: PluginLogger
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceCommandAdapter:
|
||||||
|
"""Register commands that delegate only to declared kernel service profiles."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
profile: str,
|
||||||
|
*,
|
||||||
|
operation: str = "execute",
|
||||||
|
metadata: Mapping[str, str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.profile = self._bounded_text(profile, "profile")
|
||||||
|
self.operation = self._bounded_text(operation, "operation")
|
||||||
|
self.metadata = {
|
||||||
|
str(key): str(value)
|
||||||
|
for key, value in (metadata or {}).items()
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _bounded_text(value: object, name: str) -> str:
|
||||||
|
text = str(value or "").strip()
|
||||||
|
if not text or len(text) > 80:
|
||||||
|
raise ValueError(f"{name} is invalid")
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _handler(self, context: SingleGroupContext, command: str) -> CommandHandler:
|
||||||
|
command_name = self._bounded_text(command, "command")
|
||||||
|
|
||||||
|
def handle(event: Event, arguments: str) -> ExtensionResult:
|
||||||
|
response = context.services.call(
|
||||||
|
self.profile,
|
||||||
|
operation=self.operation,
|
||||||
|
payload={
|
||||||
|
"command": command_name,
|
||||||
|
"arguments": str(arguments),
|
||||||
|
"event": event.to_mapping(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
texts = response.get("texts")
|
||||||
|
if isinstance(texts, list) and texts:
|
||||||
|
if not all(isinstance(item, str) and item.strip() for item in texts):
|
||||||
|
raise ValueError("service returned invalid text parts")
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.multi_text(texts),
|
||||||
|
metadata=self.metadata,
|
||||||
|
)
|
||||||
|
text = response.get("text")
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
raise ValueError("service returned invalid text")
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.text(text),
|
||||||
|
metadata=self.metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
return handle
|
||||||
|
|
||||||
|
def register(
|
||||||
|
self,
|
||||||
|
context: SingleGroupContext,
|
||||||
|
commands: Iterable[str],
|
||||||
|
) -> None:
|
||||||
|
for command in commands:
|
||||||
|
context.commands.register(command, self._handler(context, command))
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.jwebot]
|
[tool.jwebot]
|
||||||
release = "beta 1.2.8.1019"
|
release = "beta 2.0.0.1021"
|
||||||
production_deployments_enabled_by_default = false
|
production_deployments_enabled_by_default = false
|
||||||
|
|
||||||
[tool.jwebot.workspace]
|
[tool.jwebot.workspace]
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path[:0] = [
|
||||||
|
str(PROJECT_ROOT / "packages" / "jwebot-config" / "src"),
|
||||||
|
str(PROJECT_ROOT / "packages" / "jwebot-sdk" / "src"),
|
||||||
|
str(PROJECT_ROOT / "packages" / "jwebot-official-extensions" / "src"),
|
||||||
|
str(PROJECT_ROOT / "packages" / "jwebot-kernel" / "src"),
|
||||||
|
]
|
||||||
|
|
||||||
|
from jwebot_kernel.conversation_scope import ( # noqa: E402
|
||||||
|
ConversationConfigError,
|
||||||
|
ConversationKind,
|
||||||
|
ConversationTarget,
|
||||||
|
ShadowConversationRegistry,
|
||||||
|
build_shadow_event,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fingerprint(label: str) -> str:
|
||||||
|
return hashlib.sha256(label.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationTargetTests(unittest.TestCase):
|
||||||
|
def test_group_requires_an_explicit_mention(self):
|
||||||
|
target = ConversationTarget(
|
||||||
|
key="main-group",
|
||||||
|
kind=ConversationKind.GROUP,
|
||||||
|
title_fingerprint=fingerprint("main-group"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
target.question("@JWeBot hello", bot_mention="JWeBot"),
|
||||||
|
"hello",
|
||||||
|
)
|
||||||
|
self.assertEqual(target.question("hello", bot_mention="JWeBot"), "")
|
||||||
|
|
||||||
|
def test_direct_chat_accepts_only_commands_or_wake_words(self):
|
||||||
|
target = ConversationTarget(
|
||||||
|
key="test-direct",
|
||||||
|
kind=ConversationKind.DIRECT,
|
||||||
|
title_fingerprint=fingerprint("test-direct"),
|
||||||
|
sender="Test User",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
target.question("/状态", bot_mention="JWeBot"),
|
||||||
|
"/状态",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
target.question("JWeBot: hello", bot_mention="JWeBot"),
|
||||||
|
"hello",
|
||||||
|
)
|
||||||
|
self.assertEqual(target.question("普通聊天", bot_mention="JWeBot"), "")
|
||||||
|
|
||||||
|
def test_direct_chat_requires_a_configured_sender(self):
|
||||||
|
with self.assertRaisesRegex(ConversationConfigError, "direct sender"):
|
||||||
|
ConversationTarget(
|
||||||
|
key="test-direct",
|
||||||
|
kind=ConversationKind.DIRECT,
|
||||||
|
title_fingerprint=fingerprint("test-direct"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_title_identity_must_be_a_sha256_digest(self):
|
||||||
|
with self.assertRaisesRegex(ConversationConfigError, "SHA-256"):
|
||||||
|
ConversationTarget(
|
||||||
|
key="test-direct",
|
||||||
|
kind=ConversationKind.DIRECT,
|
||||||
|
title_fingerprint="visible-contact-name",
|
||||||
|
sender="Test User",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ShadowRegistryTests(unittest.TestCase):
|
||||||
|
def test_registry_is_disabled_without_an_explicit_path(self):
|
||||||
|
registry = ShadowConversationRegistry.from_environment({})
|
||||||
|
|
||||||
|
self.assertFalse(registry.enabled)
|
||||||
|
self.assertEqual(registry.targets, ())
|
||||||
|
|
||||||
|
def test_disabled_registry_never_matches_targets(self):
|
||||||
|
digest = fingerprint("test-direct")
|
||||||
|
registry = ShadowConversationRegistry.from_mapping(
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": False,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"key": "test-direct",
|
||||||
|
"kind": "direct",
|
||||||
|
"title_fingerprint": digest,
|
||||||
|
"sender": "Test User",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNone(registry.target_for_fingerprint(digest))
|
||||||
|
|
||||||
|
def test_explicit_config_loads_only_shadow_targets(self):
|
||||||
|
digest = fingerprint("test-direct")
|
||||||
|
config = {
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": True,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"key": "test-direct",
|
||||||
|
"kind": "direct",
|
||||||
|
"title_fingerprint": digest,
|
||||||
|
"sender": "Test User",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / "shadow.json"
|
||||||
|
path.write_text(json.dumps(config), encoding="utf-8")
|
||||||
|
registry = ShadowConversationRegistry.from_environment(
|
||||||
|
{"JWEBOT_CONVERSATION_SHADOW_CONFIG": str(path)}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(registry.enabled)
|
||||||
|
self.assertEqual(registry.target_for_fingerprint(digest), registry.targets[0])
|
||||||
|
|
||||||
|
def test_duplicate_title_fingerprints_are_rejected(self):
|
||||||
|
digest = fingerprint("duplicate")
|
||||||
|
with self.assertRaisesRegex(ConversationConfigError, "fingerprints"):
|
||||||
|
ShadowConversationRegistry.from_mapping(
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": True,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"key": "direct-a",
|
||||||
|
"kind": "direct",
|
||||||
|
"title_fingerprint": digest,
|
||||||
|
"sender": "Test A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "direct-b",
|
||||||
|
"kind": "direct",
|
||||||
|
"title_fingerprint": digest,
|
||||||
|
"sender": "Test B",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_targets_are_rejected_in_private_shadow_phase(self):
|
||||||
|
with self.assertRaisesRegex(ConversationConfigError, "direct targets only"):
|
||||||
|
ShadowConversationRegistry.from_mapping(
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": True,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"key": "main-group",
|
||||||
|
"kind": "group",
|
||||||
|
"title_fingerprint": fingerprint("main-group"),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_unknown_delivery_field_is_rejected(self):
|
||||||
|
with self.assertRaisesRegex(ConversationConfigError, "unknown fields"):
|
||||||
|
ShadowConversationRegistry.from_mapping(
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"enabled": True,
|
||||||
|
"delivery_enabled": True,
|
||||||
|
"targets": [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ShadowEventTests(unittest.TestCase):
|
||||||
|
def test_direct_event_uses_verified_conversation_identity(self):
|
||||||
|
digest = fingerprint("test-direct")
|
||||||
|
target = ConversationTarget(
|
||||||
|
key="test-direct",
|
||||||
|
kind=ConversationKind.DIRECT,
|
||||||
|
title_fingerprint=digest,
|
||||||
|
sender="Test User",
|
||||||
|
)
|
||||||
|
|
||||||
|
event = build_shadow_event(
|
||||||
|
target,
|
||||||
|
observed_at="2026-10-20T12:00:00+08:00",
|
||||||
|
text="/状态",
|
||||||
|
body_confidence=0.98,
|
||||||
|
visual_signature="visual-1",
|
||||||
|
bot_mention="JWeBot",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(event["sender"], "Test User")
|
||||||
|
self.assertEqual(event["sender_confidence"], 1.0)
|
||||||
|
self.assertEqual(event["question"], "/状态")
|
||||||
|
self.assertTrue(event["mention_bot"])
|
||||||
|
self.assertEqual(event["group_fingerprint"], digest)
|
||||||
|
self.assertEqual(event["conversation_kind"], "direct")
|
||||||
|
self.assertTrue(event["shadow_only"])
|
||||||
|
self.assertFalse(event["dispatch_allowed"])
|
||||||
|
|
||||||
|
def test_unaddressed_direct_message_is_observed_but_not_triggered(self):
|
||||||
|
target = ConversationTarget(
|
||||||
|
key="test-direct",
|
||||||
|
kind=ConversationKind.DIRECT,
|
||||||
|
title_fingerprint=fingerprint("test-direct"),
|
||||||
|
sender="Test User",
|
||||||
|
)
|
||||||
|
|
||||||
|
event = build_shadow_event(
|
||||||
|
target,
|
||||||
|
observed_at="2026-10-20T12:00:00+08:00",
|
||||||
|
text="普通聊天",
|
||||||
|
body_confidence=0.98,
|
||||||
|
visual_signature="visual-2",
|
||||||
|
bot_mention="JWeBot",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(event["question"], "")
|
||||||
|
self.assertFalse(event["mention_bot"])
|
||||||
|
self.assertTrue(event["shadow_only"])
|
||||||
|
self.assertFalse(event["dispatch_allowed"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -316,6 +316,48 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
"第二条回复喵",
|
"第二条回复喵",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_conversation_context_is_isolated_by_fingerprint(self):
|
||||||
|
observed = {}
|
||||||
|
|
||||||
|
def complete(**kwargs):
|
||||||
|
observed[kwargs["event_id"]] = list(kwargs["context"])
|
||||||
|
return f"{kwargs['event_id']} 回复喵"
|
||||||
|
|
||||||
|
self.application.deepseek.complete = complete
|
||||||
|
self.application.deliver = mock.Mock()
|
||||||
|
for event_id, group, text in (
|
||||||
|
("group-first", "group-a", "@JWeBot 群里的第一条"),
|
||||||
|
("direct-first", "direct-a", "私聊里的第一条"),
|
||||||
|
("group-second", "group-a", "@JWeBot 群里继续"),
|
||||||
|
("direct-second", "direct-a", "私聊继续"),
|
||||||
|
):
|
||||||
|
self.application.accept(event(event_id, text, group=group))
|
||||||
|
self.application.process_direct_chat_once()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
observed["group-second"],
|
||||||
|
[("群里的第一条", "group-first 回复喵")],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
observed["direct-second"],
|
||||||
|
[("私聊里的第一条", "direct-first 回复喵")],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_shadow_event_is_rejected_before_routing_or_persistence(self):
|
||||||
|
response = self.application.accept(
|
||||||
|
{
|
||||||
|
**event("shadow-1", "/状态", group="direct-a"),
|
||||||
|
"shadow_only": True,
|
||||||
|
"dispatch_allowed": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
{"status": "ignored", "reason": "shadow_only"},
|
||||||
|
)
|
||||||
|
self.assertIsNone(self.application.ledger.row("shadow-1"))
|
||||||
|
|
||||||
def test_local_game_command_result_is_available_to_followup_chat(self):
|
def test_local_game_command_result_is_available_to_followup_chat(self):
|
||||||
observed_context = []
|
observed_context = []
|
||||||
|
|
||||||
@@ -1039,9 +1081,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
self.assertEqual(version["route"], "local")
|
self.assertEqual(version["route"], "local")
|
||||||
self.assertIn("JWeBot 版本", version["text"])
|
self.assertIn("JWeBot 版本", version["text"])
|
||||||
self.assertIn("kernel", version["text"])
|
self.assertIn("kernel", version["text"])
|
||||||
self.assertIn("最新版本:beta 1.2.8.1019", version["text"])
|
self.assertIn("最新版本:beta 2.0.0.1021", version["text"])
|
||||||
self.assertIn("更新时间:2026-10-19 22:49:30", version["text"])
|
self.assertIn("更新时间:2026-10-21 16:57:39", version["text"])
|
||||||
self.assertIn("更新摘要:冷门知识问答题库整体重写", version["text"])
|
self.assertIn("更新摘要:Beta 2.0 架构更新完成", version["text"])
|
||||||
self.assertEqual(debug["route"], "local")
|
self.assertEqual(debug["route"], "local")
|
||||||
self.assertIn("调试埋点", debug["text"])
|
self.assertIn("调试埋点", debug["text"])
|
||||||
self.assertEqual(balance["route"], "local")
|
self.assertEqual(balance["route"], "local")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE)
|
|||||||
|
|
||||||
|
|
||||||
class ReleaseUpdateTests(unittest.TestCase):
|
class ReleaseUpdateTests(unittest.TestCase):
|
||||||
release = "beta 1.2.8.1019"
|
release = "beta 2.0.0.1021"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temporary = tempfile.TemporaryDirectory()
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
@@ -111,8 +111,7 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。")
|
self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。")
|
||||||
announcement = MODULE.build_announcement(self.release, summary)
|
announcement = MODULE.build_announcement(self.release, summary)
|
||||||
self.assertIn(f"{self.release} 更新现已完成", announcement)
|
self.assertIn(f"{self.release} 更新现已完成", announcement)
|
||||||
self.assertIn("更新补偿:摩拉 x200", announcement)
|
self.assertIn("更新补偿:摩拉 x400,原石 x2", announcement)
|
||||||
self.assertNotIn("原石 x1", announcement)
|
|
||||||
self.assertNotIn("第二条", announcement)
|
self.assertNotIn("第二条", announcement)
|
||||||
|
|
||||||
def test_policy_release_announces_primogem(self):
|
def test_policy_release_announces_primogem(self):
|
||||||
@@ -121,10 +120,17 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
"beta 1.1.0.1016",
|
"beta 1.1.0.1016",
|
||||||
"beta 1.2.0.1019",
|
"beta 1.2.0.1019",
|
||||||
):
|
):
|
||||||
|
self.assertEqual(MODULE.mora_compensation_amount(release), 200)
|
||||||
self.assertEqual(MODULE.primogem_compensation_amount(release), 1)
|
self.assertEqual(MODULE.primogem_compensation_amount(release), 1)
|
||||||
announcement = MODULE.build_announcement(release, "原石系统上线。")
|
announcement = MODULE.build_announcement(release, "原石系统上线。")
|
||||||
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
||||||
|
|
||||||
|
def test_major_release_announces_larger_compensation(self):
|
||||||
|
self.assertEqual(MODULE.mora_compensation_amount(self.release), 400)
|
||||||
|
self.assertEqual(MODULE.primogem_compensation_amount(self.release), 2)
|
||||||
|
announcement = MODULE.build_announcement(self.release, "架构更新完成。")
|
||||||
|
self.assertIn("更新补偿:摩拉 x400,原石 x2", announcement)
|
||||||
|
|
||||||
def test_patch_release_without_policy_does_not_announce_primogem(self):
|
def test_patch_release_without_policy_does_not_announce_primogem(self):
|
||||||
release = "beta 1.1.2.1019"
|
release = "beta 1.1.2.1019"
|
||||||
self.assertEqual(MODULE.primogem_compensation_amount(release), 0)
|
self.assertEqual(MODULE.primogem_compensation_amount(release), 0)
|
||||||
@@ -149,8 +155,13 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertEqual(result["stage"], "shutdown")
|
self.assertEqual(result["stage"], "shutdown")
|
||||||
self.assertEqual(result["delivered"], 1)
|
self.assertEqual(result["delivered"], 1)
|
||||||
self.assertEqual(delivered[0][1], "group-a")
|
self.assertEqual(delivered[0][1], "group-a")
|
||||||
self.assertIn("准备停机更新", delivered[0][2])
|
self.assertEqual(
|
||||||
self.assertIn(self.release, delivered[0][2])
|
delivered[0][2],
|
||||||
|
(
|
||||||
|
"Taffy 准备停机更新,本次更新是架构级大版本更新,"
|
||||||
|
f"预期版本{self.release},离线的时候保持耐心迎接Taffy的归来哦~"
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertIn("shutdown", delivered[0][3])
|
self.assertIn("shutdown", delivered[0][3])
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1000, 50])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1000, 50])
|
||||||
|
|
||||||
@@ -186,13 +197,13 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(delivered), 1)
|
self.assertEqual(len(delivered), 1)
|
||||||
self.assertEqual(delivered[0][1], "group-a")
|
self.assertEqual(delivered[0][1], "group-a")
|
||||||
self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2])
|
self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2])
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||||
for state in self.account_states():
|
for state in self.account_states():
|
||||||
self.assertEqual(state["primogems"], 0)
|
self.assertEqual(state["primogems"], 2)
|
||||||
matches = [
|
matches = [
|
||||||
event
|
event
|
||||||
for event in state["coin_events"]
|
for event in state["coin_events"]
|
||||||
if event["delta"] == 200
|
if event["delta"] == 400
|
||||||
and event["reason"] == f"{self.release} 版本更新补偿"
|
and event["reason"] == f"{self.release} 版本更新补偿"
|
||||||
]
|
]
|
||||||
self.assertEqual(len(matches), 1)
|
self.assertEqual(len(matches), 1)
|
||||||
@@ -202,7 +213,12 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
for event in state["primogem_events"]
|
for event in state["primogem_events"]
|
||||||
if event["reason"] == f"{self.release} 版本更新补偿"
|
if event["reason"] == f"{self.release} 版本更新补偿"
|
||||||
]
|
]
|
||||||
self.assertEqual(primogem_matches, [])
|
self.assertEqual(len(primogem_matches), 1)
|
||||||
|
self.assertEqual(primogem_matches[0]["delta"], 2)
|
||||||
|
self.assertEqual(
|
||||||
|
primogem_matches[0]["created_at"],
|
||||||
|
applied_at.timestamp(),
|
||||||
|
)
|
||||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||||
self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1)
|
self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1)
|
||||||
|
|
||||||
@@ -219,8 +235,8 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
delivery_helper=self.delivery_helper,
|
delivery_helper=self.delivery_helper,
|
||||||
deliverer=uncertain,
|
deliverer=uncertain,
|
||||||
)
|
)
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
self.assertEqual([state["primogems"] for state in self.account_states()], [2, 2])
|
||||||
self.assertEqual(self.announcement_marker()["state"], "uncertain")
|
self.assertEqual(self.announcement_marker()["state"], "uncertain")
|
||||||
|
|
||||||
called = []
|
called = []
|
||||||
@@ -250,8 +266,8 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(result["compensation"], "already_applied")
|
self.assertEqual(result["compensation"], "already_applied")
|
||||||
self.assertEqual(len(called), 1)
|
self.assertEqual(len(called), 1)
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
self.assertEqual([state["primogems"] for state in self.account_states()], [2, 2])
|
||||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from jwebot_sdk import ( # noqa: E402
|
|||||||
Fact,
|
Fact,
|
||||||
PlanKind,
|
PlanKind,
|
||||||
ReplyPlan,
|
ReplyPlan,
|
||||||
|
ServiceCommandAdapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,5 +109,70 @@ class PlanContractTests(unittest.TestCase):
|
|||||||
ExtensionResult.from_mapping(value)
|
ExtensionResult.from_mapping(value)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceCommandAdapterTests(unittest.TestCase):
|
||||||
|
def event(self):
|
||||||
|
return Event(
|
||||||
|
event_id="event-1",
|
||||||
|
observed_at="2026-10-14T10:00:00+08:00",
|
||||||
|
sender="Alice",
|
||||||
|
text="@JWeBot /coin",
|
||||||
|
group_fingerprint="group-a",
|
||||||
|
mention_bot=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def context(self, response):
|
||||||
|
class Commands:
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = {}
|
||||||
|
|
||||||
|
def register(self, command, handler):
|
||||||
|
self.handlers[command] = handler
|
||||||
|
|
||||||
|
class Services:
|
||||||
|
def __init__(self):
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def call(self, profile, *, operation, payload):
|
||||||
|
self.calls.append((profile, operation, payload))
|
||||||
|
return response
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
pass
|
||||||
|
|
||||||
|
context = Context()
|
||||||
|
context.commands = Commands()
|
||||||
|
context.services = Services()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def test_adapter_delegates_to_declared_service_profile(self):
|
||||||
|
context = self.context({"texts": ["第一条", "第二条"]})
|
||||||
|
adapter = ServiceCommandAdapter(
|
||||||
|
"coin",
|
||||||
|
metadata={"local_extension": "coin"},
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter.register(context, ["/翻硬币"])
|
||||||
|
result = context.commands.handlers["/翻硬币"](self.event(), "")
|
||||||
|
|
||||||
|
self.assertEqual(result.reply.kind, PlanKind.MULTI_TEXT)
|
||||||
|
self.assertEqual(result.reply.messages, ("第一条", "第二条"))
|
||||||
|
self.assertEqual(dict(result.metadata), {"local_extension": "coin"})
|
||||||
|
self.assertEqual(context.services.calls[0][0], "coin")
|
||||||
|
self.assertEqual(context.services.calls[0][1], "execute")
|
||||||
|
self.assertEqual(context.services.calls[0][2]["command"], "/翻硬币")
|
||||||
|
self.assertEqual(
|
||||||
|
context.services.calls[0][2]["event"]["event_id"],
|
||||||
|
"event-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_adapter_rejects_invalid_service_text(self):
|
||||||
|
context = self.context({"texts": ["ok", ""]})
|
||||||
|
adapter = ServiceCommandAdapter("coin")
|
||||||
|
|
||||||
|
adapter.register(context, ["/翻硬币"])
|
||||||
|
with self.assertRaisesRegex(ValueError, "invalid text parts"):
|
||||||
|
context.commands.handlers["/翻硬币"](self.event(), "")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -931,6 +931,263 @@ class RecorderSignatureMigrationTests(unittest.TestCase):
|
|||||||
engine.warmup.assert_called_once()
|
engine.warmup.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
class RecorderConversationStateTests(unittest.TestCase):
|
||||||
|
def test_legacy_active_state_migrates_only_to_its_matching_conversation(self):
|
||||||
|
engine = mock.Mock()
|
||||||
|
with mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"ensure_directories",
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"OcrEngine",
|
||||||
|
return_value=engine,
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"read_json",
|
||||||
|
return_value={
|
||||||
|
"initialized": True,
|
||||||
|
"last_group_fingerprint": "group-a",
|
||||||
|
"last_visual_hash": "visual-a",
|
||||||
|
"visible_signatures": ["message-a"],
|
||||||
|
"recent_trigger_signatures": ["trigger-a"],
|
||||||
|
"message_signature_scheme": MODULE.MESSAGE_SIGNATURE_SCHEME,
|
||||||
|
},
|
||||||
|
):
|
||||||
|
recorder = MODULE.Recorder(queue_enabled=True)
|
||||||
|
|
||||||
|
group_a = recorder.conversation_state("group-a")
|
||||||
|
group_b = recorder.conversation_state("group-b")
|
||||||
|
|
||||||
|
self.assertTrue(group_a["initialized"])
|
||||||
|
self.assertEqual(group_a["visible_signatures"], ["message-a"])
|
||||||
|
self.assertFalse(group_b["initialized"])
|
||||||
|
self.assertEqual(group_b["visible_signatures"], [])
|
||||||
|
|
||||||
|
def test_switching_conversations_does_not_cross_deduplicate_messages(self):
|
||||||
|
engine = mock.Mock()
|
||||||
|
first_a = MODULE.IncomingMessage(
|
||||||
|
"Alice",
|
||||||
|
"@JWeBot first",
|
||||||
|
0.98,
|
||||||
|
0.99,
|
||||||
|
0.98,
|
||||||
|
100,
|
||||||
|
"a-first",
|
||||||
|
)
|
||||||
|
first_b = MODULE.IncomingMessage(
|
||||||
|
"Bob",
|
||||||
|
"@JWeBot first",
|
||||||
|
0.98,
|
||||||
|
0.99,
|
||||||
|
0.98,
|
||||||
|
100,
|
||||||
|
"b-first",
|
||||||
|
)
|
||||||
|
second_a = MODULE.IncomingMessage(
|
||||||
|
"Alice",
|
||||||
|
"@JWeBot second",
|
||||||
|
0.98,
|
||||||
|
0.99,
|
||||||
|
0.98,
|
||||||
|
160,
|
||||||
|
"a-second",
|
||||||
|
)
|
||||||
|
engine.recognize_messages.side_effect = [
|
||||||
|
([first_a], 0.01),
|
||||||
|
([first_b], 0.01),
|
||||||
|
([first_a, second_a], 0.01),
|
||||||
|
]
|
||||||
|
engine.last_cache_hits = 0
|
||||||
|
engine.last_cache_misses = 1
|
||||||
|
engine.last_cache_refresh = False
|
||||||
|
queued = []
|
||||||
|
image = MODULE.Image.new(
|
||||||
|
"RGB",
|
||||||
|
(MODULE.EXPECTED_WIDTH, MODULE.EXPECTED_HEIGHT),
|
||||||
|
"white",
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"ensure_directories",
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"OcrEngine",
|
||||||
|
return_value=engine,
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"read_json",
|
||||||
|
return_value={},
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"incoming_mask_and_bubbles",
|
||||||
|
return_value=(None, []),
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"write_history_event",
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"queue_event",
|
||||||
|
side_effect=queued.append,
|
||||||
|
):
|
||||||
|
recorder = MODULE.Recorder(queue_enabled=True)
|
||||||
|
recorder.save_state = mock.Mock()
|
||||||
|
recorder.process_stable_image(
|
||||||
|
image,
|
||||||
|
"layout-a-1",
|
||||||
|
visual_hash="visual-a-1",
|
||||||
|
observed_at="2026-10-20T12:00:00+08:00",
|
||||||
|
group_fingerprint="group-a",
|
||||||
|
)
|
||||||
|
recorder.process_stable_image(
|
||||||
|
image,
|
||||||
|
"layout-b-1",
|
||||||
|
visual_hash="visual-b-1",
|
||||||
|
observed_at="2026-10-20T12:00:01+08:00",
|
||||||
|
group_fingerprint="group-b",
|
||||||
|
)
|
||||||
|
recorder.process_stable_image(
|
||||||
|
image,
|
||||||
|
"layout-a-2",
|
||||||
|
visual_hash="visual-a-2",
|
||||||
|
observed_at="2026-10-20T12:00:02+08:00",
|
||||||
|
group_fingerprint="group-a",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(queued), 1)
|
||||||
|
self.assertEqual(queued[0]["group_fingerprint"], "group-a")
|
||||||
|
self.assertEqual(queued[0]["text"], "@JWeBot second")
|
||||||
|
states = recorder.state["conversation_states"]
|
||||||
|
self.assertEqual(states["group-a"]["visible_signatures"], ["a-first", "a-second"])
|
||||||
|
self.assertEqual(states["group-b"]["visible_signatures"], ["b-first"])
|
||||||
|
|
||||||
|
def test_shadow_direct_messages_are_observed_without_queueing(self):
|
||||||
|
class ShadowTarget:
|
||||||
|
def question(self, text, *, bot_mention):
|
||||||
|
del bot_mention
|
||||||
|
return text if text.startswith("/") else ""
|
||||||
|
|
||||||
|
class ShadowRegistry:
|
||||||
|
def target_for_fingerprint(self, fingerprint):
|
||||||
|
return ShadowTarget() if fingerprint == "direct-a" else None
|
||||||
|
|
||||||
|
def build_shadow_event(
|
||||||
|
target,
|
||||||
|
*,
|
||||||
|
observed_at,
|
||||||
|
text,
|
||||||
|
body_confidence,
|
||||||
|
visual_signature,
|
||||||
|
bot_mention,
|
||||||
|
observed_sender,
|
||||||
|
sender_confidence,
|
||||||
|
ordinal,
|
||||||
|
):
|
||||||
|
del (
|
||||||
|
target,
|
||||||
|
body_confidence,
|
||||||
|
visual_signature,
|
||||||
|
bot_mention,
|
||||||
|
observed_sender,
|
||||||
|
sender_confidence,
|
||||||
|
ordinal,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"event_id": "shadow-event",
|
||||||
|
"observed_at": observed_at,
|
||||||
|
"sender": "Test User",
|
||||||
|
"text": text,
|
||||||
|
"mention_bot": True,
|
||||||
|
"group_fingerprint": "direct-a",
|
||||||
|
"shadow_only": True,
|
||||||
|
"dispatch_allowed": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine = mock.Mock()
|
||||||
|
first = MODULE.IncomingMessage(
|
||||||
|
"unknown",
|
||||||
|
"/状态",
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.98,
|
||||||
|
100,
|
||||||
|
"direct-first",
|
||||||
|
)
|
||||||
|
second = MODULE.IncomingMessage(
|
||||||
|
"unknown",
|
||||||
|
"/版本",
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.98,
|
||||||
|
160,
|
||||||
|
"direct-second",
|
||||||
|
)
|
||||||
|
engine.recognize_messages.side_effect = [
|
||||||
|
([first], 0.01),
|
||||||
|
([first, second], 0.01),
|
||||||
|
]
|
||||||
|
engine.last_cache_hits = 0
|
||||||
|
engine.last_cache_misses = 1
|
||||||
|
engine.last_cache_refresh = False
|
||||||
|
history = []
|
||||||
|
image = MODULE.Image.new(
|
||||||
|
"RGB",
|
||||||
|
(MODULE.EXPECTED_WIDTH, MODULE.EXPECTED_HEIGHT),
|
||||||
|
"white",
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"ensure_directories",
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"OcrEngine",
|
||||||
|
return_value=engine,
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"read_json",
|
||||||
|
return_value={},
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"load_conversation_shadow_support",
|
||||||
|
return_value=(ShadowRegistry(), build_shadow_event),
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"incoming_mask_and_bubbles",
|
||||||
|
return_value=(None, []),
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"write_history_event",
|
||||||
|
side_effect=history.append,
|
||||||
|
), mock.patch.object(
|
||||||
|
MODULE,
|
||||||
|
"queue_event",
|
||||||
|
) as queue_event:
|
||||||
|
recorder = MODULE.Recorder(queue_enabled=True)
|
||||||
|
recorder.save_state = mock.Mock()
|
||||||
|
recorder.process_stable_image(
|
||||||
|
image,
|
||||||
|
"layout-direct-1",
|
||||||
|
visual_hash="visual-direct-1",
|
||||||
|
observed_at="2026-10-20T12:00:00+08:00",
|
||||||
|
group_fingerprint="direct-a",
|
||||||
|
)
|
||||||
|
recorder.process_stable_image(
|
||||||
|
image,
|
||||||
|
"layout-direct-2",
|
||||||
|
visual_hash="visual-direct-2",
|
||||||
|
observed_at="2026-10-20T12:00:01+08:00",
|
||||||
|
group_fingerprint="direct-a",
|
||||||
|
)
|
||||||
|
|
||||||
|
queue_event.assert_not_called()
|
||||||
|
self.assertEqual(len(history), 1)
|
||||||
|
self.assertEqual(history[0]["text"], "/版本")
|
||||||
|
self.assertTrue(history[0]["shadow_only"])
|
||||||
|
self.assertFalse(history[0]["dispatch_allowed"])
|
||||||
|
self.assertEqual(recorder.state["shadowed_total"], 1)
|
||||||
|
|
||||||
|
|
||||||
class StorageTests(unittest.TestCase):
|
class StorageTests(unittest.TestCase):
|
||||||
def test_elapsed_since_iso_is_timezone_aware_and_bounded(self):
|
def test_elapsed_since_iso_is_timezone_aware_and_bounded(self):
|
||||||
now = datetime(2026, 10, 14, 12, 0, 1, tzinfo=timezone.utc)
|
now = datetime(2026, 10, 14, 12, 0, 1, tzinfo=timezone.utc)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def module_text(descriptor) -> str:
|
|||||||
extension_id = descriptor.extension_id
|
extension_id = descriptor.extension_id
|
||||||
return f'''"""Generated reviewed adapter for {extension_id}."""
|
return f'''"""Generated reviewed adapter for {extension_id}."""
|
||||||
|
|
||||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
from jwebot_sdk import ServiceCommandAdapter, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
PROFILE = {extension_id!r}
|
PROFILE = {extension_id!r}
|
||||||
@@ -76,41 +76,14 @@ AUTHOR = {descriptor.author!r}
|
|||||||
DESCRIPTION = {descriptor.description!r}
|
DESCRIPTION = {descriptor.description!r}
|
||||||
HELP_ENTRIES = {descriptor.help_entries!r}
|
HELP_ENTRIES = {descriptor.help_entries!r}
|
||||||
COMMANDS = {descriptor.commands!r}
|
COMMANDS = {descriptor.commands!r}
|
||||||
|
ADAPTER = ServiceCommandAdapter(
|
||||||
|
PROFILE,
|
||||||
def _handler(context: SingleGroupContext, command: str):
|
metadata={{"local_extension": PROFILE, "author": AUTHOR}},
|
||||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
)
|
||||||
response = context.services.call(
|
|
||||||
PROFILE,
|
|
||||||
operation="execute",
|
|
||||||
payload={{
|
|
||||||
"command": command,
|
|
||||||
"arguments": arguments,
|
|
||||||
"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")
|
|
||||||
return ExtensionResult(
|
|
||||||
reply=ReplyPlan.text(text),
|
|
||||||
metadata={{"local_extension": PROFILE, "author": AUTHOR}},
|
|
||||||
)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def plugin(context: SingleGroupContext) -> None:
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
for command in COMMANDS:
|
ADAPTER.register(context, COMMANDS)
|
||||||
context.commands.register(command, _handler(context, command))
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+154
-30
@@ -116,6 +116,30 @@ class RouterInvocationUnavailable(RuntimeError):
|
|||||||
"""A transient route invocation failure that must be retried by the queue."""
|
"""A transient route invocation failure that must be retried by the queue."""
|
||||||
|
|
||||||
|
|
||||||
|
def load_conversation_shadow_support() -> tuple[object | None, Callable[..., Any] | None]:
|
||||||
|
if not os.environ.get("JWEBOT_CONVERSATION_SHADOW_CONFIG", "").strip():
|
||||||
|
return None, None
|
||||||
|
root = Path(__file__).resolve().parent
|
||||||
|
source_roots = [
|
||||||
|
root / "packages" / package / "src"
|
||||||
|
for package in (
|
||||||
|
"jwebot-config",
|
||||||
|
"jwebot-sdk",
|
||||||
|
"jwebot-official-extensions",
|
||||||
|
"jwebot-kernel",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for source_root in reversed(source_roots):
|
||||||
|
if source_root.is_dir():
|
||||||
|
sys.path.insert(0, str(source_root))
|
||||||
|
from jwebot_kernel.conversation_scope import ( # noqa: PLC0415
|
||||||
|
ShadowConversationRegistry,
|
||||||
|
build_shadow_event,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ShadowConversationRegistry.from_environment(), build_shadow_event
|
||||||
|
|
||||||
|
|
||||||
def router_error_message(
|
def router_error_message(
|
||||||
*,
|
*,
|
||||||
result: subprocess.CompletedProcess[str] | None = None,
|
result: subprocess.CompletedProcess[str] | None = None,
|
||||||
@@ -1577,27 +1601,81 @@ class Recorder:
|
|||||||
self.state["last_visual_hash"] = ""
|
self.state["last_visual_hash"] = ""
|
||||||
self.state["visible_signatures"] = []
|
self.state["visible_signatures"] = []
|
||||||
self.state["recent_trigger_signatures"] = []
|
self.state["recent_trigger_signatures"] = []
|
||||||
|
raw_conversations = self.state.get("conversation_states")
|
||||||
|
if not isinstance(raw_conversations, dict):
|
||||||
|
raw_conversations = {}
|
||||||
|
self.state["conversation_states"] = {
|
||||||
|
str(fingerprint): value
|
||||||
|
for fingerprint, value in raw_conversations.items()
|
||||||
|
if isinstance(value, dict)
|
||||||
|
and value.get("message_signature_scheme") == MESSAGE_SIGNATURE_SCHEME
|
||||||
|
}
|
||||||
self.pending_hash = ""
|
self.pending_hash = ""
|
||||||
self.pending_count = 0
|
self.pending_count = 0
|
||||||
|
self.pending_frames: dict[str, tuple[str, int]] = {}
|
||||||
self.no_overlap_recovery_ready = False
|
self.no_overlap_recovery_ready = False
|
||||||
|
self.recovery_ready_conversations: set[str] = set()
|
||||||
|
(
|
||||||
|
self.shadow_conversations,
|
||||||
|
self.build_shadow_event,
|
||||||
|
) = load_conversation_shadow_support()
|
||||||
|
|
||||||
def save_state(self) -> None:
|
def save_state(self) -> None:
|
||||||
write_json(RECORDER_STATE, self.state)
|
write_json(RECORDER_STATE, self.state)
|
||||||
|
|
||||||
|
def conversation_state(self, fingerprint: str) -> dict[str, Any]:
|
||||||
|
conversations = self.state["conversation_states"]
|
||||||
|
value = conversations.get(fingerprint)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return value
|
||||||
|
if (
|
||||||
|
fingerprint == self.state.get("last_group_fingerprint")
|
||||||
|
and self.state.get("message_signature_scheme")
|
||||||
|
== MESSAGE_SIGNATURE_SCHEME
|
||||||
|
):
|
||||||
|
value = {
|
||||||
|
"initialized": bool(self.state.get("initialized")),
|
||||||
|
"last_layout_hash": str(self.state.get("last_layout_hash", "")),
|
||||||
|
"last_visual_hash": str(self.state.get("last_visual_hash", "")),
|
||||||
|
"visible_signatures": list(
|
||||||
|
self.state.get("visible_signatures", [])
|
||||||
|
),
|
||||||
|
"recent_trigger_signatures": list(
|
||||||
|
self.state.get("recent_trigger_signatures", [])
|
||||||
|
),
|
||||||
|
"message_signature_scheme": MESSAGE_SIGNATURE_SCHEME,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
value = {
|
||||||
|
"initialized": False,
|
||||||
|
"last_layout_hash": "",
|
||||||
|
"last_visual_hash": "",
|
||||||
|
"visible_signatures": [],
|
||||||
|
"recent_trigger_signatures": [],
|
||||||
|
"message_signature_scheme": MESSAGE_SIGNATURE_SCHEME,
|
||||||
|
}
|
||||||
|
conversations[fingerprint] = value
|
||||||
|
return value
|
||||||
|
|
||||||
def capture_cycle(self, *, stable_frames: int) -> None:
|
def capture_cycle(self, *, stable_frames: int) -> None:
|
||||||
started = time.monotonic()
|
started = time.monotonic()
|
||||||
image = capture_image()
|
image = capture_image()
|
||||||
crop = message_image(image)
|
crop = message_image(image)
|
||||||
layout_hash, visual_hash = incoming_frame_hashes(crop)
|
layout_hash, visual_hash = incoming_frame_hashes(crop)
|
||||||
if visual_hash != self.pending_hash:
|
group = title_fingerprint(image)
|
||||||
self.pending_hash = visual_hash
|
conversation = self.conversation_state(group)
|
||||||
self.pending_count = 1
|
pending_hash, pending_count = self.pending_frames.get(group, ("", 0))
|
||||||
|
if visual_hash != pending_hash:
|
||||||
|
pending_count = 1
|
||||||
else:
|
else:
|
||||||
self.pending_count += 1
|
pending_count += 1
|
||||||
|
self.pending_frames[group] = (visual_hash, pending_count)
|
||||||
|
self.pending_hash = visual_hash
|
||||||
|
self.pending_count = pending_count
|
||||||
outcome = "pending"
|
outcome = "pending"
|
||||||
if (
|
if (
|
||||||
self.pending_count >= stable_frames
|
pending_count >= stable_frames
|
||||||
and visual_hash != self.state.get("last_visual_hash")
|
and visual_hash != conversation.get("last_visual_hash")
|
||||||
):
|
):
|
||||||
outcome = "process"
|
outcome = "process"
|
||||||
self.process_stable_image(
|
self.process_stable_image(
|
||||||
@@ -1605,12 +1683,13 @@ class Recorder:
|
|||||||
layout_hash,
|
layout_hash,
|
||||||
visual_hash=visual_hash,
|
visual_hash=visual_hash,
|
||||||
observed_at=now_iso(),
|
observed_at=now_iso(),
|
||||||
|
group_fingerprint=group,
|
||||||
)
|
)
|
||||||
telemetry_event(
|
telemetry_event(
|
||||||
"ocr.capture",
|
"ocr.capture",
|
||||||
elapsed_ms=(time.monotonic() - started) * 1000,
|
elapsed_ms=(time.monotonic() - started) * 1000,
|
||||||
outcome=outcome,
|
outcome=outcome,
|
||||||
count=self.pending_count,
|
count=pending_count,
|
||||||
source="screen",
|
source="screen",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1634,60 +1713,89 @@ class Recorder:
|
|||||||
*,
|
*,
|
||||||
visual_hash: str | None = None,
|
visual_hash: str | None = None,
|
||||||
observed_at: str | None = None,
|
observed_at: str | None = None,
|
||||||
|
group_fingerprint: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
pipeline_started = time.monotonic()
|
pipeline_started = time.monotonic()
|
||||||
crop = message_image(image)
|
crop = message_image(image)
|
||||||
_, bubbles = incoming_mask_and_bubbles(crop)
|
_, bubbles = incoming_mask_and_bubbles(crop)
|
||||||
messages, elapsed = self.engine.recognize_messages(crop, bubbles)
|
messages, elapsed = self.engine.recognize_messages(crop, bubbles)
|
||||||
|
group = group_fingerprint or title_fingerprint(image)
|
||||||
|
conversation = self.conversation_state(group)
|
||||||
|
shadow_target = (
|
||||||
|
self.shadow_conversations.target_for_fingerprint(group)
|
||||||
|
if self.shadow_conversations is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
previous = [
|
previous = [
|
||||||
value
|
value
|
||||||
for value in self.state.get("visible_signatures", [])
|
for value in conversation.get("visible_signatures", [])
|
||||||
if isinstance(value, str)
|
if isinstance(value, str)
|
||||||
]
|
]
|
||||||
recent_trigger_signatures = [
|
recent_trigger_signatures = [
|
||||||
value
|
value
|
||||||
for value in self.state.get("recent_trigger_signatures", [])
|
for value in conversation.get("recent_trigger_signatures", [])
|
||||||
if isinstance(value, str)
|
if isinstance(value, str)
|
||||||
]
|
]
|
||||||
new_messages, current_signatures, overlap, resynced = new_sequence_suffix(
|
new_messages, current_signatures, overlap, resynced = new_sequence_suffix(
|
||||||
previous,
|
previous,
|
||||||
messages,
|
messages,
|
||||||
recent_trigger_signatures=recent_trigger_signatures,
|
recent_trigger_signatures=recent_trigger_signatures,
|
||||||
recover_unseen_trigger=self.no_overlap_recovery_ready,
|
recover_unseen_trigger=group in self.recovery_ready_conversations,
|
||||||
)
|
)
|
||||||
initialized = bool(self.state.get("initialized"))
|
initialized = bool(conversation.get("initialized"))
|
||||||
observed_at = observed_at or now_iso()
|
observed_at = observed_at or now_iso()
|
||||||
if not initialized:
|
if not initialized:
|
||||||
new_messages = []
|
new_messages = []
|
||||||
resynced = True
|
resynced = True
|
||||||
self.state["initialized"] = True
|
conversation["initialized"] = True
|
||||||
group = title_fingerprint(image)
|
|
||||||
current_trigger_signatures = [
|
current_trigger_signatures = [
|
||||||
message.signature
|
message.signature
|
||||||
for message in messages
|
for message in messages
|
||||||
if trigger_question(message.text) is not None
|
if (
|
||||||
|
shadow_target.question(message.text, bot_mention=BOT_MENTION)
|
||||||
|
if shadow_target is not None
|
||||||
|
else trigger_question(message.text)
|
||||||
|
)
|
||||||
]
|
]
|
||||||
recent_trigger_signatures = (
|
recent_trigger_signatures = (
|
||||||
recent_trigger_signatures + current_trigger_signatures
|
recent_trigger_signatures + current_trigger_signatures
|
||||||
)[-MAX_RECENT_TRIGGER_SIGNATURES:]
|
)[-MAX_RECENT_TRIGGER_SIGNATURES:]
|
||||||
|
self.recovery_ready_conversations.add(group)
|
||||||
self.no_overlap_recovery_ready = True
|
self.no_overlap_recovery_ready = True
|
||||||
appended = 0
|
appended = 0
|
||||||
queued = 0
|
queued = 0
|
||||||
|
shadowed = 0
|
||||||
for ordinal, message in enumerate(new_messages):
|
for ordinal, message in enumerate(new_messages):
|
||||||
event = history_event(
|
if shadow_target is not None:
|
||||||
message,
|
if self.build_shadow_event is None:
|
||||||
observed_at=observed_at,
|
raise RuntimeError("shadow event builder is unavailable")
|
||||||
group_fingerprint=group,
|
event = self.build_shadow_event(
|
||||||
ordinal=ordinal,
|
shadow_target,
|
||||||
)
|
observed_at=observed_at,
|
||||||
if (
|
text=message.text,
|
||||||
self.queue_enabled
|
body_confidence=message.body_confidence,
|
||||||
and event["mention_bot"]
|
visual_signature=message.signature,
|
||||||
and message.sender != "unknown"
|
bot_mention=BOT_MENTION,
|
||||||
and message.sender_confidence >= MIN_SENDER_CONFIDENCE
|
observed_sender=message.sender,
|
||||||
):
|
sender_confidence=message.sender_confidence,
|
||||||
queue_event(event)
|
ordinal=ordinal,
|
||||||
queued += 1
|
)
|
||||||
|
shadowed += 1
|
||||||
|
else:
|
||||||
|
event = history_event(
|
||||||
|
message,
|
||||||
|
observed_at=observed_at,
|
||||||
|
group_fingerprint=group,
|
||||||
|
ordinal=ordinal,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
self.queue_enabled
|
||||||
|
and event["mention_bot"]
|
||||||
|
and message.sender != "unknown"
|
||||||
|
and message.sender_confidence >= MIN_SENDER_CONFIDENCE
|
||||||
|
):
|
||||||
|
queue_event(event)
|
||||||
|
queued += 1
|
||||||
write_history_event(event)
|
write_history_event(event)
|
||||||
appended += 1
|
appended += 1
|
||||||
logging.info(
|
logging.info(
|
||||||
@@ -1698,9 +1806,23 @@ class Recorder:
|
|||||||
message.confidence,
|
message.confidence,
|
||||||
bool(queued),
|
bool(queued),
|
||||||
)
|
)
|
||||||
|
conversation.update(
|
||||||
|
{
|
||||||
|
"last_capture_at": observed_at,
|
||||||
|
"last_layout_hash": layout_hash,
|
||||||
|
"last_visual_hash": visual_hash or layout_hash,
|
||||||
|
"last_ocr_ms": round(elapsed * 1000),
|
||||||
|
"visible_signatures": current_signatures,
|
||||||
|
"recent_trigger_signatures": recent_trigger_signatures,
|
||||||
|
"message_signature_scheme": MESSAGE_SIGNATURE_SCHEME,
|
||||||
|
"visible_count": len(messages),
|
||||||
|
"last_overlap": overlap,
|
||||||
|
"last_resynced": resynced,
|
||||||
|
}
|
||||||
|
)
|
||||||
self.state.update(
|
self.state.update(
|
||||||
{
|
{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"last_capture_at": observed_at,
|
"last_capture_at": observed_at,
|
||||||
"last_layout_hash": layout_hash,
|
"last_layout_hash": layout_hash,
|
||||||
"last_visual_hash": visual_hash or layout_hash,
|
"last_visual_hash": visual_hash or layout_hash,
|
||||||
@@ -1714,6 +1836,8 @@ class Recorder:
|
|||||||
"last_resynced": resynced,
|
"last_resynced": resynced,
|
||||||
"events_total": int(self.state.get("events_total", 0)) + appended,
|
"events_total": int(self.state.get("events_total", 0)) + appended,
|
||||||
"queued_total": int(self.state.get("queued_total", 0)) + queued,
|
"queued_total": int(self.state.get("queued_total", 0)) + queued,
|
||||||
|
"shadowed_total": int(self.state.get("shadowed_total", 0))
|
||||||
|
+ shadowed,
|
||||||
"resync_total": int(self.state.get("resync_total", 0))
|
"resync_total": int(self.state.get("resync_total", 0))
|
||||||
+ int(resynced and initialized),
|
+ int(resynced and initialized),
|
||||||
"errors_total": int(self.state.get("errors_total", 0)),
|
"errors_total": int(self.state.get("errors_total", 0)),
|
||||||
@@ -1747,7 +1871,7 @@ class Recorder:
|
|||||||
group_id=group,
|
group_id=group,
|
||||||
count=appended,
|
count=appended,
|
||||||
depth=queued,
|
depth=queued,
|
||||||
outcome="queued" if queued else "observed",
|
outcome="shadowed" if shadowed else "queued" if queued else "observed",
|
||||||
source="rapidocr",
|
source="rapidocr",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user