@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## beta 1.2.6.1019 - 2026-10-19 22:26:26
|
||||||
|
|
||||||
|
- 冷门知识问答新增少女乐团派对、韩流和原神题库,并支持 `/知识问答 主题` 定向开题。
|
||||||
|
- Unknown trivia topics now return the supported topic list, while automatic
|
||||||
|
quiz broadcasts still draw from the full trivia bank.
|
||||||
|
|
||||||
## beta 1.2.5.1019 - 2026-10-19 22:12:52
|
## beta 1.2.5.1019 - 2026-10-19 22:12:52
|
||||||
|
|
||||||
- 冷门知识问答题库改为中文展示:题干、选项和解释不再混用英文词。
|
- 冷门知识问答题库改为中文展示:题干、选项和解释不再混用英文词。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
|||||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||||
并通过带视觉确认的 GUI 发送链路回复。
|
并通过带视觉确认的 GUI 发送链路回复。
|
||||||
|
|
||||||
当前版本:`beta 1.2.5.1019`,更新时间:2026-10-19 22:12:52。
|
当前版本:`beta 1.2.6.1019`,更新时间:2026-10-19 22:26:26。
|
||||||
|
|
||||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||||
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
||||||
@@ -90,7 +90,7 @@ PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
|
|||||||
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
||||||
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
|
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
|
||||||
/摩拉(兼 /原石);/原石祈愿;/原石榜;/扫码加权 人名(24小时+5);/Taffy发话 内容
|
/摩拉(兼 /原石);/原石祈愿;/原石榜;/扫码加权 人名(24小时+5);/Taffy发话 内容
|
||||||
/知识问答;/回答 A
|
/知识问答 [主题];支持主题:反恐精英、顶点英雄、守望先锋、英雄联盟、少女乐团派对、韩流、原神;/回答 A
|
||||||
/游戏记录 [页码](/gamehistory)
|
/游戏记录 [页码](/gamehistory)
|
||||||
/游戏记录 [页码](/gamehistory)
|
/游戏记录 [页码](/gamehistory)
|
||||||
/游戏帮助(/games)
|
/游戏帮助(/games)
|
||||||
@@ -270,10 +270,10 @@ visually confirmed GUI bridge.
|
|||||||
Current version:
|
Current version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
beta 1.2.5.1019
|
beta 1.2.6.1019
|
||||||
```
|
```
|
||||||
|
|
||||||
Updated: 2026-10-19 22:12:52.
|
Updated: 2026-10-19 22:26:26.
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# JWeBot Command Catalog
|
# JWeBot Command Catalog
|
||||||
|
|
||||||
Current baseline: `beta 1.2.5.1019`.
|
Current baseline: `beta 1.2.6.1019`.
|
||||||
|
|
||||||
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
|
||||||
@@ -51,7 +51,7 @@ descriptor inventory; plugin code delegates to reviewed service profiles.
|
|||||||
| `/扫码加权 人名` | `/扫码概率 人名` | Spend a scan-boost ticket or 1 primogem to give that person +5 hourly scan weight for 24 hours. |
|
| `/扫码加权 人名` | `/扫码概率 人名` | Spend a scan-boost ticket or 1 primogem to give that person +5 hourly scan weight for 24 hours. |
|
||||||
| `/Taffy发话 内容` | `/taffysay content` | Spend Taffy speech credit; 1 primogem converts to 2 credits when needed. |
|
| `/Taffy发话 内容` | `/taffysay content` | Spend Taffy speech credit; 1 primogem converts to 2 credits when needed. |
|
||||||
| `/Taffy发话 定时 HH:MM 内容` | - | Schedule a Taffy speech through the guarded clock delivery queue. |
|
| `/Taffy发话 定时 HH:MM 内容` | - | Schedule a Taffy speech through the guarded clock delivery queue. |
|
||||||
| `/知识问答` | - | Start a five-minute cold-knowledge quiz immediately, unless one is already active. |
|
| `/知识问答 [主题]` | - | Start a five-minute cold-knowledge quiz immediately, optionally limited to one supported topic. Supported topics: 反恐精英, 顶点英雄, 守望先锋, 英雄联盟, 少女乐团派对, 韩流, 原神. |
|
||||||
| `/回答 A` | `/answer A` | Answer the active five-minute cold-knowledge quiz for a rarity-based Mora reward. |
|
| `/回答 A` | `/answer A` | Answer the active five-minute cold-knowledge quiz for a rarity-based Mora reward. |
|
||||||
| `/抽奖 [金额]` | `/draw` | Play a single-player prize draw. |
|
| `/抽奖 [金额]` | `/draw` | Play a single-player prize draw. |
|
||||||
| `/彩票 开始` | `/lottery start` | Open a multiplayer lottery round. |
|
| `/彩票 开始` | `/lottery start` | Open a multiplayer lottery round. |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-config"
|
name = "jwebot-config"
|
||||||
version = "1.2.5b1+1019"
|
version = "1.2.6b1+1019"
|
||||||
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.5b1+1019"
|
__version__ = "1.2.6b1+1019"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-extension-host"
|
name = "jwebot-extension-host"
|
||||||
version = "1.2.5b1+1019"
|
version = "1.2.6b1+1019"
|
||||||
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.5b1+1019",
|
"jwebot-sdk==1.2.6b1+1019",
|
||||||
"jwebot-kernel==1.2.5b1+1019",
|
"jwebot-kernel==1.2.6b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ __all__ = [
|
|||||||
"process_request",
|
"process_request",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.5b1+1019"
|
__version__ = "1.2.6b1+1019"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-kernel"
|
name = "jwebot-kernel"
|
||||||
version = "1.2.5b1+1019"
|
version = "1.2.6b1+1019"
|
||||||
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.5b1+1019",
|
"jwebot-config==1.2.6b1+1019",
|
||||||
"jwebot-sdk==1.2.5b1+1019",
|
"jwebot-sdk==1.2.6b1+1019",
|
||||||
"jwebot-official-extensions==1.2.5b1+1019",
|
"jwebot-official-extensions==1.2.6b1+1019",
|
||||||
"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.5b1+1019"
|
__version__ = "1.2.6b1+1019"
|
||||||
|
|||||||
@@ -166,6 +166,44 @@ TRIVIA_HISTORY_KEY = "trivia_quiz_history"
|
|||||||
TRIVIA_ANSWER_WINDOW_MINUTES = 5
|
TRIVIA_ANSWER_WINDOW_MINUTES = 5
|
||||||
TRIVIA_QUIZ_PROBABILITY_PERCENT = 75
|
TRIVIA_QUIZ_PROBABILITY_PERCENT = 75
|
||||||
TRIVIA_RECENT_BASE_LIMIT = 24
|
TRIVIA_RECENT_BASE_LIMIT = 24
|
||||||
|
TRIVIA_TOPIC_NAMES = (
|
||||||
|
"反恐精英",
|
||||||
|
"顶点英雄",
|
||||||
|
"守望先锋",
|
||||||
|
"英雄联盟",
|
||||||
|
"少女乐团派对",
|
||||||
|
"韩流",
|
||||||
|
"原神",
|
||||||
|
)
|
||||||
|
TRIVIA_TOPIC_ALIASES = {
|
||||||
|
"反恐精英": "反恐精英",
|
||||||
|
"反恐": "反恐精英",
|
||||||
|
"cs": "反恐精英",
|
||||||
|
"cs2": "反恐精英",
|
||||||
|
"csgo": "反恐精英",
|
||||||
|
"顶点英雄": "顶点英雄",
|
||||||
|
"顶点": "顶点英雄",
|
||||||
|
"apex": "顶点英雄",
|
||||||
|
"守望先锋": "守望先锋",
|
||||||
|
"守望": "守望先锋",
|
||||||
|
"ow": "守望先锋",
|
||||||
|
"overwatch": "守望先锋",
|
||||||
|
"英雄联盟": "英雄联盟",
|
||||||
|
"联盟": "英雄联盟",
|
||||||
|
"lol": "英雄联盟",
|
||||||
|
"leagueoflegends": "英雄联盟",
|
||||||
|
"少女乐团派对": "少女乐团派对",
|
||||||
|
"邦邦": "少女乐团派对",
|
||||||
|
"gbp": "少女乐团派对",
|
||||||
|
"girlsbandparty": "少女乐团派对",
|
||||||
|
"bangdream": "少女乐团派对",
|
||||||
|
"韩流": "韩流",
|
||||||
|
"韩国流行音乐": "韩流",
|
||||||
|
"kpop": "韩流",
|
||||||
|
"原神": "原神",
|
||||||
|
"genshin": "原神",
|
||||||
|
"genshinimpact": "原神",
|
||||||
|
}
|
||||||
BEIJING_TIMEZONE = ZoneInfo("Asia/Shanghai")
|
BEIJING_TIMEZONE = ZoneInfo("Asia/Shanghai")
|
||||||
ACKNOWLEDGEMENT = "已收到,正在处理中喵"
|
ACKNOWLEDGEMENT = "已收到,正在处理中喵"
|
||||||
STEAM_ACKNOWLEDGEMENT = "Steam 查询 [■□□] 正在读取公开数据"
|
STEAM_ACKNOWLEDGEMENT = "Steam 查询 [■□□] 正在读取公开数据"
|
||||||
@@ -360,6 +398,21 @@ TRIVIA_FACTS: tuple[tuple[str, str, str, str, tuple[str, str, str], str, int], .
|
|||||||
("lol-zac", "英雄联盟", "扎克名字常被解释为什么缩写?", "祖安无定形战斗体", ("祖安奥术核心", "时间装置自动体", "祖安酸液生物"), "扎克的名字常被当作祖安无定形战斗体的缩写梗。", 4),
|
("lol-zac", "英雄联盟", "扎克名字常被解释为什么缩写?", "祖安无定形战斗体", ("祖安奥术核心", "时间装置自动体", "祖安酸液生物"), "扎克的名字常被当作祖安无定形战斗体的缩写梗。", 4),
|
||||||
("lol-kindred", "英雄联盟", "永猎双子由哪两个化身组成?", "羊灵和狼灵", ("乌鸦和雄鹿", "狐狸和猫头鹰", "太阳和月亮"), "永猎双子是羊灵与狼灵两个死亡化身。", 2),
|
("lol-kindred", "英雄联盟", "永猎双子由哪两个化身组成?", "羊灵和狼灵", ("乌鸦和雄鹿", "狐狸和猫头鹰", "太阳和月亮"), "永猎双子是羊灵与狼灵两个死亡化身。", 2),
|
||||||
("lol-ryze", "英雄联盟", "瑞兹长期收集并封存的危险物是什么?", "世界符文", ("暗裔武器", "海克斯核心", "虚空碎片"), "瑞兹的主线目标是阻止世界符文失控。", 3),
|
("lol-ryze", "英雄联盟", "瑞兹长期收集并封存的危险物是什么?", "世界符文", ("暗裔武器", "海克斯核心", "虚空碎片"), "瑞兹的主线目标是阻止世界符文失控。", 3),
|
||||||
|
("gbp-random-star", "少女乐团派对", "户山香澄使用的星形吉他昵称是什么?", "随机星", ("流星号", "闪耀心", "星光鸟"), "户山香澄的标志性星形吉他昵称是随机星。", 4),
|
||||||
|
("gbp-hikawa-sisters", "少女乐团派对", "冰川双子里姐姐是谁?", "冰川纱夜", ("冰川日菜", "丸山彩", "今井莉莎"), "冰川纱夜是姐姐,冰川日菜是妹妹。", 3),
|
||||||
|
("gbp-aya-idol", "少女乐团派对", "丸山彩所在乐队最核心的组合定位是?", "偶像乐队", ("幼驯染摇滚", "哥特交响", "欢乐马戏"), "丸山彩所在组合以偶像乐队定位为核心。", 3),
|
||||||
|
("gbp-yukina-father", "少女乐团派对", "凑友希那坚持音乐的家庭原因主要和谁有关?", "父亲", ("姐姐", "祖母", "老师"), "凑友希那对音乐的执着,很大一部分和父亲的经历有关。", 4),
|
||||||
|
("gbp-tsugumi-cafe", "少女乐团派对", "羽泽鸫家里经营的店更接近什么?", "咖啡店", ("唱片店", "花店", "乐器店"), "羽泽鸫家经营咖啡店,她也经常帮忙。", 3),
|
||||||
|
("kpop-snsd-debut", "韩流", "少女时代的出道曲是哪首?", "再次重逢的世界", ("说出你的愿望", "哦", "狮心"), "少女时代的出道曲是再次重逢的世界。", 4),
|
||||||
|
("kpop-redvelvet-leader", "韩流", "红贝贝的队长是谁?", "裴柱现", ("姜涩琪", "孙胜完", "朴秀荣"), "红贝贝的队长是裴柱现。", 3),
|
||||||
|
("kpop-newjeans-fandom", "韩流", "新牛仔的官方粉丝名中文常译为什么?", "兔子", ("星星", "花园", "钥匙"), "新牛仔的官方粉丝名常译作兔子。", 4),
|
||||||
|
("kpop-first-win", "韩流", "歌谣节目里常说的“一位”通常指什么?", "当期第一名", ("出道舞台", "安可失败", "投票资格"), "一位通常指某个歌谣节目当期排名第一。", 2),
|
||||||
|
("kpop-lightstick", "韩流", "韩流演唱会里粉丝常用来应援的发光道具叫什么?", "应援棒", ("舞台牌", "投票券", "签售卡"), "应援棒是韩流现场应援里很常见的发光道具。", 2),
|
||||||
|
("genshin-barbatos", "原神", "蒙德风神的真实魔神名是?", "巴巴托斯", ("摩拉克斯", "巴尔泽布", "布耶尔"), "蒙德风神温迪对应的魔神名是巴巴托斯。", 3),
|
||||||
|
("genshin-morax", "原神", "璃月岩王帝君的魔神名是?", "摩拉克斯", ("巴巴托斯", "厄歌莉娅", "布耶尔"), "岩王帝君钟离对应的魔神名是摩拉克斯。", 3),
|
||||||
|
("genshin-ei", "原神", "稻妻一心净土里的真正主人是谁?", "雷电影", ("雷电真", "八重神子", "九条裟罗"), "一心净土里的真正主人是雷电影。", 4),
|
||||||
|
("genshin-nahida-title", "原神", "纳西妲作为神明对应的称号是?", "小吉祥草王", ("大慈树王", "尘世闲游", "正义之神"), "纳西妲在须弥被称为小吉祥草王。", 3),
|
||||||
|
("genshin-neuvillette", "原神", "枫丹最高审判官是谁?", "那维莱特", ("莱欧斯利", "芙宁娜", "克洛琳德"), "那维莱特是枫丹的最高审判官。", 3),
|
||||||
)
|
)
|
||||||
TRIVIA_TEMPLATES = (
|
TRIVIA_TEMPLATES = (
|
||||||
"【{domain} 冷知识】{prompt}",
|
"【{domain} 冷知识】{prompt}",
|
||||||
@@ -378,12 +431,32 @@ def trivia_reward(rarity: int) -> int:
|
|||||||
return 20 + max(1, min(5, int(rarity))) * 30
|
return 20 + max(1, min(5, int(rarity))) * 30
|
||||||
|
|
||||||
|
|
||||||
|
def trivia_topic_key(value: object) -> str:
|
||||||
|
return re.sub(
|
||||||
|
r"[\s'’`·・_\-::.。!!??]+",
|
||||||
|
"",
|
||||||
|
str(value or "").casefold(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_trivia_topic(value: object) -> str | None:
|
||||||
|
topic = trivia_topic_key(value)
|
||||||
|
if not topic:
|
||||||
|
return None
|
||||||
|
return TRIVIA_TOPIC_ALIASES.get(topic)
|
||||||
|
|
||||||
|
|
||||||
|
def supported_trivia_topics_message() -> str:
|
||||||
|
return "支持主题:" + "、".join(TRIVIA_TOPIC_NAMES)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def trivia_question_bank() -> tuple[dict[str, object], ...]:
|
def trivia_question_bank() -> tuple[dict[str, object], ...]:
|
||||||
questions: list[dict[str, object]] = [
|
questions: list[dict[str, object]] = [
|
||||||
{
|
{
|
||||||
**question,
|
**question,
|
||||||
"base_id": str(question["id"]),
|
"base_id": str(question["id"]),
|
||||||
|
"topic": canonical_trivia_topic(question.get("domain")) or str(question["domain"]),
|
||||||
"rarity": int(question.get("rarity", 4)),
|
"rarity": int(question.get("rarity", 4)),
|
||||||
"reward": trivia_reward(int(question.get("rarity", 4))),
|
"reward": trivia_reward(int(question.get("rarity", 4))),
|
||||||
}
|
}
|
||||||
@@ -399,6 +472,7 @@ def trivia_question_bank() -> tuple[dict[str, object], ...]:
|
|||||||
"id": f"{base_id}-{variant + 1}",
|
"id": f"{base_id}-{variant + 1}",
|
||||||
"base_id": base_id,
|
"base_id": base_id,
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
|
"topic": canonical_trivia_topic(domain) or domain,
|
||||||
"question": template.format(domain=domain, prompt=prompt),
|
"question": template.format(domain=domain, prompt=prompt),
|
||||||
"options": rotated,
|
"options": rotated,
|
||||||
"answer": answer_label,
|
"answer": answer_label,
|
||||||
@@ -6764,10 +6838,20 @@ class HourlyBroadcastEngine:
|
|||||||
*,
|
*,
|
||||||
group: str,
|
group: str,
|
||||||
local_now: datetime,
|
local_now: datetime,
|
||||||
|
topic: str | None = None,
|
||||||
) -> dict[str, object]:
|
) -> dict[str, object]:
|
||||||
bank = trivia_question_bank()
|
bank = tuple(
|
||||||
|
question
|
||||||
|
for question in trivia_question_bank()
|
||||||
|
if topic is None or str(question.get("topic")) == topic
|
||||||
|
)
|
||||||
|
if not bank:
|
||||||
|
raise LocalCommandError("这个主题还没有题喵\n" + supported_trivia_topics_message())
|
||||||
recent = set(self.trivia_recent_base_ids(group))
|
recent = set(self.trivia_recent_base_ids(group))
|
||||||
seed = f"{group}|{local_now.date().isoformat()}|{local_now.hour:02d}|trivia"
|
seed = (
|
||||||
|
f"{group}|{local_now.date().isoformat()}|"
|
||||||
|
f"{local_now.hour:02d}|trivia|{topic or 'all'}"
|
||||||
|
)
|
||||||
start = deterministic_int(seed, 0, len(bank) - 1)
|
start = deterministic_int(seed, 0, len(bank) - 1)
|
||||||
for offset in range(len(bank)):
|
for offset in range(len(bank)):
|
||||||
candidate = bank[(start + offset) % len(bank)]
|
candidate = bank[(start + offset) % len(bank)]
|
||||||
@@ -6783,8 +6867,15 @@ class HourlyBroadcastEngine:
|
|||||||
raise LocalCommandError("冷门问答题库选项无效")
|
raise LocalCommandError("冷门问答题库选项无效")
|
||||||
rarity = max(1, min(5, int(question.get("rarity", 3))))
|
rarity = max(1, min(5, int(question.get("rarity", 3))))
|
||||||
reward = max(1, min(500, int(question.get("reward", trivia_reward(rarity)))))
|
reward = max(1, min(500, int(question.get("reward", trivia_reward(rarity)))))
|
||||||
|
topic = normalize_text(question.get("topic") or question.get("domain"))
|
||||||
|
heading = f"冷门知识问答(冷门度 {rarity}/5,限时5分钟,答对 +{reward} 摩拉)"
|
||||||
|
if topic:
|
||||||
|
heading = (
|
||||||
|
f"冷门知识问答(主题:{topic},冷门度 {rarity}/5,"
|
||||||
|
f"限时5分钟,答对 +{reward} 摩拉)"
|
||||||
|
)
|
||||||
lines = [
|
lines = [
|
||||||
f"冷门知识问答(冷门度 {rarity}/5,限时5分钟,答对 +{reward} 摩拉)",
|
heading,
|
||||||
str(question.get("question", "")),
|
str(question.get("question", "")),
|
||||||
]
|
]
|
||||||
lines.extend(
|
lines.extend(
|
||||||
@@ -6799,8 +6890,13 @@ class HourlyBroadcastEngine:
|
|||||||
*,
|
*,
|
||||||
group: str,
|
group: str,
|
||||||
local_now: datetime,
|
local_now: datetime,
|
||||||
|
topic: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
question = self.select_trivia_question(group=group, local_now=local_now)
|
question = self.select_trivia_question(
|
||||||
|
group=group,
|
||||||
|
local_now=local_now,
|
||||||
|
topic=topic,
|
||||||
|
)
|
||||||
message = self.render_trivia_message(question)
|
message = self.render_trivia_message(question)
|
||||||
expires_at = (
|
expires_at = (
|
||||||
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
||||||
@@ -6811,6 +6907,7 @@ class HourlyBroadcastEngine:
|
|||||||
{
|
{
|
||||||
"question_id": str(question["id"]),
|
"question_id": str(question["id"]),
|
||||||
"base_id": str(question.get("base_id") or question["id"]),
|
"base_id": str(question.get("base_id") or question["id"]),
|
||||||
|
"topic": str(question.get("topic") or question.get("domain") or ""),
|
||||||
"question": str(question["question"]),
|
"question": str(question["question"]),
|
||||||
"options": list(question["options"]),
|
"options": list(question["options"]),
|
||||||
"answer": str(question["answer"]),
|
"answer": str(question["answer"]),
|
||||||
@@ -6828,9 +6925,13 @@ class HourlyBroadcastEngine:
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
group: str,
|
group: str,
|
||||||
|
topic_name: str = "",
|
||||||
now: datetime | None = None,
|
now: datetime | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
local_now = self.in_beijing_timezone(now)
|
local_now = self.in_beijing_timezone(now)
|
||||||
|
topic = canonical_trivia_topic(topic_name)
|
||||||
|
if normalize_text(topic_name) and topic is None:
|
||||||
|
return supported_trivia_topics_message()
|
||||||
active = self.ledger.local_game(group, TRIVIA_GAME_KEY)
|
active = self.ledger.local_game(group, TRIVIA_GAME_KEY)
|
||||||
if active:
|
if active:
|
||||||
try:
|
try:
|
||||||
@@ -6839,7 +6940,11 @@ class HourlyBroadcastEngine:
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return "冷门问答状态坏了喵"
|
return "冷门问答状态坏了喵"
|
||||||
self.ledger.clear_local_game(group, TRIVIA_GAME_KEY)
|
self.ledger.clear_local_game(group, TRIVIA_GAME_KEY)
|
||||||
return self.start_trivia_question(group=group, local_now=local_now)
|
return self.start_trivia_question(
|
||||||
|
group=group,
|
||||||
|
local_now=local_now,
|
||||||
|
topic=topic,
|
||||||
|
)
|
||||||
|
|
||||||
def due_trivia_broadcast(
|
def due_trivia_broadcast(
|
||||||
self,
|
self,
|
||||||
@@ -8107,10 +8212,11 @@ class EdgeApplication:
|
|||||||
}
|
}
|
||||||
if decision.execution_path is ExecutionPath.LOCAL_COMMAND:
|
if decision.execution_path is ExecutionPath.LOCAL_COMMAND:
|
||||||
try:
|
try:
|
||||||
command_kind = local_command_kind(str(row["question"]))
|
command_kind, command_arguments = local_command_parts(str(row["question"]))
|
||||||
reply = (
|
reply = (
|
||||||
self.clock.force_trivia_message(
|
self.clock.force_trivia_message(
|
||||||
group=str(row["group_fingerprint"])
|
group=str(row["group_fingerprint"]),
|
||||||
|
topic_name=command_arguments,
|
||||||
)
|
)
|
||||||
if command_kind == "trivia_start"
|
if command_kind == "trivia_start"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-official-extensions"
|
name = "jwebot-official-extensions"
|
||||||
version = "1.2.5b1+1019"
|
version = "1.2.6b1+1019"
|
||||||
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.5b1+1019",
|
"jwebot-sdk==1.2.6b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ __all__ = [
|
|||||||
"render_feature_list",
|
"render_feature_list",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.5b1+1019"
|
__version__ = "1.2.6b1+1019"
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ DIRECT_KERNEL_COMMANDS = (
|
|||||||
command_kinds=("deepseek_balance",),
|
command_kinds=("deepseek_balance",),
|
||||||
),
|
),
|
||||||
KernelCommandDescriptor(
|
KernelCommandDescriptor(
|
||||||
description="手动触发冷门知识问答",
|
description="冷门问答",
|
||||||
help_entries=("/知识问答",),
|
help_entries=("/知识问答 [主题]",),
|
||||||
command_kinds=("trivia_start",),
|
command_kinds=("trivia_start",),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-sdk"
|
name = "jwebot-sdk"
|
||||||
version = "1.2.5b1+1019"
|
version = "1.2.6b1+1019"
|
||||||
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 = []
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ __all__ = [
|
|||||||
"SingleGroupContext",
|
"SingleGroupContext",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.5b1+1019"
|
__version__ = "1.2.6b1+1019"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.jwebot]
|
[tool.jwebot]
|
||||||
release = "beta 1.2.5.1019"
|
release = "beta 1.2.6.1019"
|
||||||
production_deployments_enabled_by_default = false
|
production_deployments_enabled_by_default = false
|
||||||
|
|
||||||
[tool.jwebot.workspace]
|
[tool.jwebot.workspace]
|
||||||
|
|||||||
+19
-10
@@ -1035,9 +1035,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.5.1019", version["text"])
|
self.assertIn("最新版本:beta 1.2.6.1019", version["text"])
|
||||||
self.assertIn("更新时间:2026-10-19 22:12:52", version["text"])
|
self.assertIn("更新时间:2026-10-19 22:26:26", version["text"])
|
||||||
self.assertIn("更新摘要:冷门知识问答题库改为中文展示", version["text"])
|
self.assertIn("更新摘要:冷门知识问答新增少女乐团派对、韩流和原神题库", 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")
|
||||||
@@ -1398,28 +1398,37 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
def test_trivia_quiz_can_be_started_by_local_command(self):
|
def test_trivia_quiz_can_be_started_by_local_command(self):
|
||||||
self.application.deepseek.complete = mock.Mock(side_effect=AssertionError)
|
self.application.deepseek.complete = mock.Mock(side_effect=AssertionError)
|
||||||
|
|
||||||
|
unknown = self.application.accept(
|
||||||
|
event("trivia-force-unknown", "@JWeBot /知识问答 星际")
|
||||||
|
)
|
||||||
first = self.application.accept(
|
first = self.application.accept(
|
||||||
event("trivia-force", "@JWeBot /知识问答")
|
event("trivia-force", "@JWeBot /知识问答 原神")
|
||||||
)
|
)
|
||||||
second = self.application.accept(
|
second = self.application.accept(
|
||||||
event("trivia-force-existing", "@JWeBot /知识问答")
|
event("trivia-force-existing", "@JWeBot /知识问答 K-pop")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assertIn("支持主题:", unknown["text"])
|
||||||
|
self.assertIn("原神", unknown["text"])
|
||||||
|
self.assertIn("少女乐团派对", unknown["text"])
|
||||||
self.assertEqual(first["route"], "local")
|
self.assertEqual(first["route"], "local")
|
||||||
self.assertEqual(first["execution_path"], "local-command")
|
self.assertEqual(first["execution_path"], "local-command")
|
||||||
self.assertIn("冷门知识问答", first["text"])
|
self.assertIn("冷门知识问答", first["text"])
|
||||||
|
self.assertIn("原神", first["text"])
|
||||||
self.assertIn("回答格式:/回答 A", first["text"])
|
self.assertIn("回答格式:/回答 A", first["text"])
|
||||||
self.assertIn("已经有一题在进行中", second["text"])
|
self.assertIn("已经有一题在进行中", second["text"])
|
||||||
|
self.assertIn("原神", second["text"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.application.ledger.row("trivia-force")["status"],
|
self.application.ledger.row("trivia-force")["status"],
|
||||||
"local_completed",
|
"local_completed",
|
||||||
)
|
)
|
||||||
self.assertIsNotNone(
|
state = self.application.ledger.local_game(
|
||||||
self.application.ledger.local_game(
|
"group-a",
|
||||||
"group-a",
|
MODULE.TRIVIA_GAME_KEY,
|
||||||
MODULE.TRIVIA_GAME_KEY,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
self.assertIsNotNone(state)
|
||||||
|
assert state is not None
|
||||||
|
self.assertEqual(state["topic"], "原神")
|
||||||
self.application.deepseek.complete.assert_not_called()
|
self.application.deepseek.complete.assert_not_called()
|
||||||
|
|
||||||
def test_ambient_chat_cooldown_keeps_next_hour_scan_broadcast(self):
|
def test_ambient_chat_cooldown_keeps_next_hour_scan_broadcast(self):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE)
|
|||||||
|
|
||||||
|
|
||||||
class ReleaseUpdateTests(unittest.TestCase):
|
class ReleaseUpdateTests(unittest.TestCase):
|
||||||
release = "beta 1.2.5.1019"
|
release = "beta 1.2.6.1019"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temporary = tempfile.TemporaryDirectory()
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
|
|||||||
Reference in New Issue
Block a user