Release beta 1.2.6.1019
check / local-gate (push) Failing after 2m29s

This commit is contained in:
JWeBot Contributors
2026-10-19 22:29:36 +08:00
parent 8670745235
commit ed2fd0200f
19 changed files with 165 additions and 44 deletions
+6
View File
@@ -1,5 +1,11 @@
# 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
- 冷门知识问答题库改为中文展示:题干、选项和解释不再混用英文词。
+4 -4
View File
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
并通过带视觉确认的 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 收口了当前稳定功能:本地命令插件、人物与上下文索引、
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
@@ -90,7 +90,7 @@ PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
/摩拉(兼 /原石);/原石祈愿;/原石榜;/扫码加权 人名(24小时+5);/Taffy发话 内容
/知识问答;/回答 A
/知识问答 [主题];支持主题:反恐精英、顶点英雄、守望先锋、英雄联盟、少女乐团派对、韩流、原神;/回答 A
/游戏记录 [页码](/gamehistory)
/游戏记录 [页码](/gamehistory)
/游戏帮助(/games)
@@ -270,10 +270,10 @@ visually confirmed GUI bridge.
Current version:
```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
context indexes, Steam presence/playtime queries, virtual coin games,
+1 -1
View File
@@ -1 +1 @@
beta 1.2.5.1019
beta 1.2.6.1019
+2 -2
View File
@@ -1,6 +1,6 @@
# 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
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. |
| `/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. |
| `/知识问答` | - | 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. |
| `/抽奖 [金额]` | `/draw` | Play a single-player prize draw. |
| `/彩票 开始` | `/lottery start` | Open a multiplayer lottery round. |
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-config"
version = "1.2.5b1+1019"
version = "1.2.6b1+1019"
description = "Profile-based configuration compiler for JWeBot"
requires-python = ">=3.9"
dependencies = [
@@ -36,4 +36,4 @@ __all__ = [
"write_default_profile",
]
__version__ = "1.2.5b1+1019"
__version__ = "1.2.6b1+1019"
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-extension-host"
version = "1.2.5b1+1019"
version = "1.2.6b1+1019"
description = "Single-release, single-invocation process host for JWeBot extensions"
requires-python = ">=3.9"
dependencies = [
"jwebot-sdk==1.2.5b1+1019",
"jwebot-kernel==1.2.5b1+1019",
"jwebot-sdk==1.2.6b1+1019",
"jwebot-kernel==1.2.6b1+1019",
]
[project.scripts]
@@ -16,4 +16,4 @@ __all__ = [
"process_request",
]
__version__ = "1.2.5b1+1019"
__version__ = "1.2.6b1+1019"
+4 -4
View File
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-kernel"
version = "1.2.5b1+1019"
version = "1.2.6b1+1019"
description = "Stable routing and extension authority for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-config==1.2.5b1+1019",
"jwebot-sdk==1.2.5b1+1019",
"jwebot-official-extensions==1.2.5b1+1019",
"jwebot-config==1.2.6b1+1019",
"jwebot-sdk==1.2.6b1+1019",
"jwebot-official-extensions==1.2.6b1+1019",
"tomli>=2; python_version < '3.11'",
]
@@ -107,4 +107,4 @@ __all__ = [
"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_QUIZ_PROBABILITY_PERCENT = 75
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")
ACKNOWLEDGEMENT = "已收到,正在处理中喵"
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-kindred", "英雄联盟", "永猎双子由哪两个化身组成?", "羊灵和狼灵", ("乌鸦和雄鹿", "狐狸和猫头鹰", "太阳和月亮"), "永猎双子是羊灵与狼灵两个死亡化身。", 2),
("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 = (
"【{domain} 冷知识】{prompt}",
@@ -378,12 +431,32 @@ def trivia_reward(rarity: int) -> int:
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)
def trivia_question_bank() -> tuple[dict[str, object], ...]:
questions: list[dict[str, object]] = [
{
**question,
"base_id": str(question["id"]),
"topic": canonical_trivia_topic(question.get("domain")) or str(question["domain"]),
"rarity": 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}",
"base_id": base_id,
"domain": domain,
"topic": canonical_trivia_topic(domain) or domain,
"question": template.format(domain=domain, prompt=prompt),
"options": rotated,
"answer": answer_label,
@@ -6764,10 +6838,20 @@ class HourlyBroadcastEngine:
*,
group: str,
local_now: datetime,
topic: str | None = None,
) -> 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))
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)
for offset in range(len(bank)):
candidate = bank[(start + offset) % len(bank)]
@@ -6783,8 +6867,15 @@ class HourlyBroadcastEngine:
raise LocalCommandError("冷门问答题库选项无效")
rarity = max(1, min(5, int(question.get("rarity", 3))))
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 = [
f"冷门知识问答(冷门度 {rarity}/5,限时5分钟,答对 +{reward} 摩拉)",
heading,
str(question.get("question", "")),
]
lines.extend(
@@ -6799,8 +6890,13 @@ class HourlyBroadcastEngine:
*,
group: str,
local_now: datetime,
topic: str | None = None,
) -> 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)
expires_at = (
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
@@ -6811,6 +6907,7 @@ class HourlyBroadcastEngine:
{
"question_id": str(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"]),
"options": list(question["options"]),
"answer": str(question["answer"]),
@@ -6828,9 +6925,13 @@ class HourlyBroadcastEngine:
self,
*,
group: str,
topic_name: str = "",
now: datetime | None = None,
) -> str:
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)
if active:
try:
@@ -6839,7 +6940,11 @@ class HourlyBroadcastEngine:
except (TypeError, ValueError):
return "冷门问答状态坏了喵"
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(
self,
@@ -8107,10 +8212,11 @@ class EdgeApplication:
}
if decision.execution_path is ExecutionPath.LOCAL_COMMAND:
try:
command_kind = local_command_kind(str(row["question"]))
command_kind, command_arguments = local_command_parts(str(row["question"]))
reply = (
self.clock.force_trivia_message(
group=str(row["group_fingerprint"])
group=str(row["group_fingerprint"]),
topic_name=command_arguments,
)
if command_kind == "trivia_start"
else
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-official-extensions"
version = "1.2.5b1+1019"
version = "1.2.6b1+1019"
description = "Reviewed local command adapters and broker implementation for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-sdk==1.2.5b1+1019",
"jwebot-sdk==1.2.6b1+1019",
]
[tool.setuptools.packages.find]
@@ -90,4 +90,4 @@ __all__ = [
"render_feature_list",
]
__version__ = "1.2.5b1+1019"
__version__ = "1.2.6b1+1019"
@@ -153,8 +153,8 @@ DIRECT_KERNEL_COMMANDS = (
command_kinds=("deepseek_balance",),
),
KernelCommandDescriptor(
description="手动触发冷门知识问答",
help_entries=("/知识问答",),
description="冷门问答",
help_entries=("/知识问答 [主题]",),
command_kinds=("trivia_start",),
),
)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-sdk"
version = "1.2.5b1+1019"
version = "1.2.6b1+1019"
description = "Typed, capability-limited extension API for JWeBot plugins"
requires-python = ">=3.9"
dependencies = []
@@ -38,4 +38,4 @@ __all__ = [
"SingleGroupContext",
]
__version__ = "1.2.5b1+1019"
__version__ = "1.2.6b1+1019"
+1 -1
View File
@@ -1,5 +1,5 @@
[tool.jwebot]
release = "beta 1.2.5.1019"
release = "beta 1.2.6.1019"
production_deployments_enabled_by_default = false
[tool.jwebot.workspace]
+19 -10
View File
@@ -1035,9 +1035,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
self.assertEqual(version["route"], "local")
self.assertIn("JWeBot 版本", version["text"])
self.assertIn("kernel", version["text"])
self.assertIn("最新版本:beta 1.2.5.1019", version["text"])
self.assertIn("更新时间:2026-10-19 22:12:52", version["text"])
self.assertIn("更新摘要:冷门知识问答题库改为中文展示", version["text"])
self.assertIn("最新版本:beta 1.2.6.1019", version["text"])
self.assertIn("更新时间:2026-10-19 22:26:26", version["text"])
self.assertIn("更新摘要:冷门知识问答新增少女乐团派对、韩流和原神题库", version["text"])
self.assertEqual(debug["route"], "local")
self.assertIn("调试埋点", debug["text"])
self.assertEqual(balance["route"], "local")
@@ -1398,28 +1398,37 @@ class LedgerAndApplicationTests(unittest.TestCase):
def test_trivia_quiz_can_be_started_by_local_command(self):
self.application.deepseek.complete = mock.Mock(side_effect=AssertionError)
unknown = self.application.accept(
event("trivia-force-unknown", "@JWeBot /知识问答 星际")
)
first = self.application.accept(
event("trivia-force", "@JWeBot /知识问答")
event("trivia-force", "@JWeBot /知识问答 原神")
)
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["execution_path"], "local-command")
self.assertIn("冷门知识问答", first["text"])
self.assertIn("原神", first["text"])
self.assertIn("回答格式:/回答 A", first["text"])
self.assertIn("已经有一题在进行中", second["text"])
self.assertIn("原神", second["text"])
self.assertEqual(
self.application.ledger.row("trivia-force")["status"],
"local_completed",
)
self.assertIsNotNone(
self.application.ledger.local_game(
"group-a",
MODULE.TRIVIA_GAME_KEY,
)
state = self.application.ledger.local_game(
"group-a",
MODULE.TRIVIA_GAME_KEY,
)
self.assertIsNotNone(state)
assert state is not None
self.assertEqual(state["topic"], "原神")
self.application.deepseek.complete.assert_not_called()
def test_ambient_chat_cooldown_keeps_next_hour_scan_broadcast(self):
+1 -1
View File
@@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE)
class ReleaseUpdateTests(unittest.TestCase):
release = "beta 1.2.5.1019"
release = "beta 1.2.6.1019"
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()