diff --git a/CHANGELOG.md b/CHANGELOG.md index 383f3cc..b17ac21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## beta 1.1.1.1016 - 2026-10-16 17:19:45 + +- 修复扫码加权重复购买只刷新不叠加的问题。 +- Change scan boost semantics to stack each successful purchase as an + independent 24-hour +5 weight entry. +- Preserve legacy boost entries by deriving their 24-hour expiry from their + creation time. + ## beta 1.1.0.1016 - 2026-10-16 17:04:25 - 修复 OCR 偶发重复识别消耗型命令导致重复回复的问题,并完善摩拉/扫码榜展示。 diff --git a/README.md b/README.md index cbd1921..779b616 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC 观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件, 并通过带视觉确认的 GUI 发送链路回复。 -当前版本:`beta 1.1.0.1016`,更新时间:2026-10-16 17:04:25。 +当前版本:`beta 1.1.1.1016`,更新时间:2026-10-16 17:19:45。 1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、 Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major @@ -269,10 +269,10 @@ visually confirmed GUI bridge. Current version: ```text -beta 1.1.0.1016 +beta 1.1.1.1016 ``` -Updated: 2026-10-16 17:04:25. +Updated: 2026-10-16 17:19:45. The 1.0 Beta baseline consolidates the stable local command set, person and context indexes, Steam presence/playtime queries, virtual coin games, diff --git a/VERSION b/VERSION index 3ceaa2b..09ecb39 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -beta 1.1.0.1016 +beta 1.1.1.1016 diff --git a/docs/command-catalog.md b/docs/command-catalog.md index ea8dc0e..c2b400e 100644 --- a/docs/command-catalog.md +++ b/docs/command-catalog.md @@ -1,6 +1,6 @@ # JWeBot Command Catalog -Current baseline: `beta 1.1.0.1016`. +Current baseline: `beta 1.1.1.1016`. This catalog records the reviewed commands shipped with the built-in local extensions. Runtime ownership is frozen at startup from the generated diff --git a/packages/jwebot-config/pyproject.toml b/packages/jwebot-config/pyproject.toml index 491b810..a78f779 100644 --- a/packages/jwebot-config/pyproject.toml +++ b/packages/jwebot-config/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jwebot-config" -version = "1.1.0b1+1016" +version = "1.1.1b1+1016" description = "Profile-based configuration compiler for JWeBot" requires-python = ">=3.9" dependencies = [ diff --git a/packages/jwebot-config/src/jwebot_config/__init__.py b/packages/jwebot-config/src/jwebot_config/__init__.py index cceb044..856265a 100644 --- a/packages/jwebot-config/src/jwebot_config/__init__.py +++ b/packages/jwebot-config/src/jwebot_config/__init__.py @@ -36,4 +36,4 @@ __all__ = [ "write_default_profile", ] -__version__ = "1.1.0b1+1016" +__version__ = "1.1.1b1+1016" diff --git a/packages/jwebot-extension-host/pyproject.toml b/packages/jwebot-extension-host/pyproject.toml index 0b1efb1..4343d05 100644 --- a/packages/jwebot-extension-host/pyproject.toml +++ b/packages/jwebot-extension-host/pyproject.toml @@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta" [project] name = "jwebot-extension-host" -version = "1.1.0b1+1016" +version = "1.1.1b1+1016" description = "Single-release, single-invocation process host for JWeBot extensions" requires-python = ">=3.9" dependencies = [ - "jwebot-sdk==1.1.0b1+1016", - "jwebot-kernel==1.1.0b1+1016", + "jwebot-sdk==1.1.1b1+1016", + "jwebot-kernel==1.1.1b1+1016", ] [project.scripts] diff --git a/packages/jwebot-extension-host/src/jwebot_extension_host/__init__.py b/packages/jwebot-extension-host/src/jwebot_extension_host/__init__.py index 21e8b33..bb7480d 100644 --- a/packages/jwebot-extension-host/src/jwebot_extension_host/__init__.py +++ b/packages/jwebot-extension-host/src/jwebot_extension_host/__init__.py @@ -16,4 +16,4 @@ __all__ = [ "process_request", ] -__version__ = "1.1.0b1+1016" +__version__ = "1.1.1b1+1016" diff --git a/packages/jwebot-kernel/pyproject.toml b/packages/jwebot-kernel/pyproject.toml index daa36ad..f6fd56f 100644 --- a/packages/jwebot-kernel/pyproject.toml +++ b/packages/jwebot-kernel/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "jwebot-kernel" -version = "1.1.0b1+1016" +version = "1.1.1b1+1016" description = "Stable routing and extension authority for JWeBot" requires-python = ">=3.9" dependencies = [ - "jwebot-config==1.1.0b1+1016", - "jwebot-sdk==1.1.0b1+1016", - "jwebot-official-extensions==1.1.0b1+1016", + "jwebot-config==1.1.1b1+1016", + "jwebot-sdk==1.1.1b1+1016", + "jwebot-official-extensions==1.1.1b1+1016", "tomli>=2; python_version < '3.11'", ] diff --git a/packages/jwebot-kernel/src/jwebot_kernel/__init__.py b/packages/jwebot-kernel/src/jwebot_kernel/__init__.py index 8884222..d178f52 100644 --- a/packages/jwebot-kernel/src/jwebot_kernel/__init__.py +++ b/packages/jwebot-kernel/src/jwebot_kernel/__init__.py @@ -107,4 +107,4 @@ __all__ = [ "verify_installed_release_signature", ] -__version__ = "1.1.0b1+1016" +__version__ = "1.1.1b1+1016" diff --git a/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py b/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py index 6231444..5250afa 100644 --- a/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py +++ b/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py @@ -2909,7 +2909,6 @@ class Ledger: group_fingerprint: str, slot_key: str, ) -> bool: - consume_boost_key = "" with self.lock, self.connection: row = self.connection.execute( """ @@ -2950,9 +2949,6 @@ class Ledger: utc_now(), ), ) - consume_boost_key = person_key - if consume_boost_key: - self.consume_scan_boost(group_fingerprint, consume_boost_key) return True def fail_clock_broadcast( diff --git a/packages/jwebot-official-extensions/pyproject.toml b/packages/jwebot-official-extensions/pyproject.toml index 381d28d..31fe894 100644 --- a/packages/jwebot-official-extensions/pyproject.toml +++ b/packages/jwebot-official-extensions/pyproject.toml @@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta" [project] name = "jwebot-official-extensions" -version = "1.1.0b1+1016" +version = "1.1.1b1+1016" description = "Reviewed local command adapters and broker implementation for JWeBot" requires-python = ">=3.9" dependencies = [ - "jwebot-sdk==1.1.0b1+1016", + "jwebot-sdk==1.1.1b1+1016", ] [tool.setuptools.packages.find] diff --git a/packages/jwebot-official-extensions/src/jwebot_official_extensions/__init__.py b/packages/jwebot-official-extensions/src/jwebot_official_extensions/__init__.py index 057c71f..cc359f8 100644 --- a/packages/jwebot-official-extensions/src/jwebot_official_extensions/__init__.py +++ b/packages/jwebot-official-extensions/src/jwebot_official_extensions/__init__.py @@ -90,4 +90,4 @@ __all__ = [ "render_feature_list", ] -__version__ = "1.1.0b1+1016" +__version__ = "1.1.1b1+1016" diff --git a/packages/jwebot-official-extensions/src/jwebot_official_extensions/player_games.py b/packages/jwebot-official-extensions/src/jwebot_official_extensions/player_games.py index e7266e1..ec7c626 100644 --- a/packages/jwebot-official-extensions/src/jwebot_official_extensions/player_games.py +++ b/packages/jwebot-official-extensions/src/jwebot_official_extensions/player_games.py @@ -32,8 +32,10 @@ PRIMOGEM_MORA_VALUE = 3600 PRIMOGEM_EVENT_LIMIT = 50 PRIMOGEM_EVENT_DISPLAY_LIMIT = 5 WISH_HISTORY_LIMIT = 30 -SCAN_BOOST_USES = 24 -SCAN_BOOST_WEIGHT = 4 +SCAN_BOOST_HOURS = 24 +SCAN_BOOST_SECONDS = SCAN_BOOST_HOURS * 60 * 60 +SCAN_BOOST_WEIGHT = 5 +SCAN_BOOST_LIMIT = 200 TAFFY_SAY_CREDITS_PER_PRIMOGEM = 2 TAFFY_SAY_MAX_CHARS = 160 WISH_MORA_TABLE = ( @@ -331,31 +333,34 @@ def normalize_player_account(account: dict[str, Any]) -> dict[str, Any]: if not isinstance(boosts, list): boosts = [] normalized_boosts = [] + current_time = time.time() for boost in boosts: if not isinstance(boost, dict): continue target_key = str(boost.get("target_person_key", "")).strip() target_name = str(boost.get("target_display_name", "")).strip() - remaining = boost.get("remaining", 0) weight = boost.get("weight", 0) + created_at = float(boost.get("created_at", current_time) or current_time) + expires_at = boost.get("expires_at") + if not isinstance(expires_at, (int, float)): + expires_at = created_at + SCAN_BOOST_SECONDS if ( target_key and target_name - and type(remaining) is int - and remaining > 0 and type(weight) is int and weight > 0 + and float(expires_at) > current_time ): normalized_boosts.append( { "target_person_key": target_key[:128], "target_display_name": target_name[:120], - "remaining": min(remaining, 10_000), - "weight": min(weight, 100), - "created_at": float(boost.get("created_at", 0) or 0), + "weight": min(max(weight, SCAN_BOOST_WEIGHT), 100), + "created_at": created_at, + "expires_at": float(expires_at), } ) - account["scan_boosts"] = normalized_boosts[-20:] + account["scan_boosts"] = normalized_boosts[-SCAN_BOOST_LIMIT:] return account @@ -883,24 +888,21 @@ class PlayerGames: f"{player_label}:没有扫码概率券,原石也不足;" f"当前原石 {account['primogems']} 个" ) - boosts = [ - item - for item in account["scan_boosts"] - if item["target_person_key"] != target["person_key"] - ] + now = time.time() + boosts = list(account["scan_boosts"]) boosts.append( { "target_person_key": str(target["person_key"]), "target_display_name": target_label, - "remaining": SCAN_BOOST_USES, "weight": SCAN_BOOST_WEIGHT, - "created_at": time.time(), + "created_at": now, + "expires_at": now + SCAN_BOOST_SECONDS, } ) - account["scan_boosts"] = boosts[-20:] + account["scan_boosts"] = boosts[-SCAN_BOOST_LIMIT:] return ( f"{player_label}:已给 {target_label} 增加整点扫码权重;" - f"{cost_text};未来 {SCAN_BOOST_USES} 次送达扫码播报中," + f"{cost_text};未来 {SCAN_BOOST_HOURS} 小时内," f"{target_label} 额外 +{SCAN_BOOST_WEIGHT} 权重" ) diff --git a/packages/jwebot-sdk/pyproject.toml b/packages/jwebot-sdk/pyproject.toml index cdfdee3..5700f65 100644 --- a/packages/jwebot-sdk/pyproject.toml +++ b/packages/jwebot-sdk/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jwebot-sdk" -version = "1.1.0b1+1016" +version = "1.1.1b1+1016" description = "Typed, capability-limited extension API for JWeBot plugins" requires-python = ">=3.9" dependencies = [] diff --git a/packages/jwebot-sdk/src/jwebot_sdk/__init__.py b/packages/jwebot-sdk/src/jwebot_sdk/__init__.py index c58d83e..7938ad9 100644 --- a/packages/jwebot-sdk/src/jwebot_sdk/__init__.py +++ b/packages/jwebot-sdk/src/jwebot_sdk/__init__.py @@ -38,4 +38,4 @@ __all__ = [ "SingleGroupContext", ] -__version__ = "1.1.0b1+1016" +__version__ = "1.1.1b1+1016" diff --git a/pyproject.toml b/pyproject.toml index 922816b..b401cb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.jwebot] -release = "beta 1.1.0.1016" +release = "beta 1.1.1.1016" production_deployments_enabled_by_default = false [tool.jwebot.workspace] diff --git a/tests/test_jwebot_edge.py b/tests/test_jwebot_edge.py index d122a52..6207086 100644 --- a/tests/test_jwebot_edge.py +++ b/tests/test_jwebot_edge.py @@ -924,9 +924,9 @@ class LedgerAndApplicationTests(unittest.TestCase): self.assertEqual(version["route"], "local") self.assertIn("JWeBot 版本", version["text"]) self.assertIn("kernel", version["text"]) - self.assertIn("最新版本:beta 1.1.0.1016", version["text"]) - self.assertIn("更新时间:2026-10-16 17:04:25", version["text"]) - self.assertIn("更新摘要:修复 OCR 偶发重复识别", version["text"]) + self.assertIn("最新版本:beta 1.1.1.1016", version["text"]) + self.assertIn("更新时间:2026-10-16 17:19:45", version["text"]) + self.assertIn("更新摘要:修复扫码加权重复购买", version["text"]) self.assertEqual(debug["route"], "local") self.assertIn("调试埋点", debug["text"]) self.assertEqual(balance["route"], "local") diff --git a/tests/test_release_105.py b/tests/test_release_105.py index 0886fcf..2a71c14 100644 --- a/tests/test_release_105.py +++ b/tests/test_release_105.py @@ -116,7 +116,7 @@ class Release105PrimogemTests(unittest.TestCase): def test_scan_boost_and_taffy_say_spend_primogem_resources(self): self.command("/原石") account = self.state() - account["primogems"] = 2 + account["primogems"] = 3 self.engine.ledger._save_player_account( "group-a", self.engine.ledger.resolve_person("group-a", "Alice")["person_key"], @@ -132,7 +132,12 @@ class Release105PrimogemTests(unittest.TestCase): self.assertIn(bob_key, boost_targets) ranking = self.command("/扫码榜", event_id="scan-ranking-after-boost") self.assertIn("下一小时扫码权重:", ranking) - self.assertIn("bob 5", ranking) + self.assertIn("bob 6", ranking) + + second_boost = self.command("/扫码加权 Bob", event_id="scan-boost-second") + self.assertIn("已给 bob 增加整点扫码权重", second_boost) + stacked = self.command("/扫码榜", event_id="scan-ranking-stacked") + self.assertIn("bob 11", stacked) speech = self.command("/Taffy发话 今天也要加班吗") self.assertEqual(speech, "Taffy:今天也要加班吗喵") diff --git a/tests/test_release_update.py b/tests/test_release_update.py index 64f7772..325e352 100644 --- a/tests/test_release_update.py +++ b/tests/test_release_update.py @@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE) class ReleaseUpdateTests(unittest.TestCase): - release = "beta 1.1.0.1016" + release = "beta 1.1.1.1016" def setUp(self): self.temporary = tempfile.TemporaryDirectory() @@ -111,14 +111,15 @@ class ReleaseUpdateTests(unittest.TestCase): self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。") announcement = MODULE.build_announcement(self.release, summary) self.assertIn(f"{self.release} 更新现已完成", announcement) - self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement) + self.assertIn("更新补偿:摩拉 x200", announcement) + self.assertNotIn("原石 x1", announcement) self.assertNotIn("第二条", announcement) def test_policy_release_announces_primogem(self): - release = "beta 1.0.5.1016" - self.assertEqual(MODULE.primogem_compensation_amount(release), 1) - announcement = MODULE.build_announcement(release, "原石系统上线。") - self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement) + for release in ("beta 1.0.5.1016", "beta 1.1.0.1016"): + self.assertEqual(MODULE.primogem_compensation_amount(release), 1) + announcement = MODULE.build_announcement(release, "原石系统上线。") + self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement) def test_patch_release_without_policy_does_not_announce_primogem(self): release = "beta 1.1.1.1016" @@ -161,7 +162,7 @@ class ReleaseUpdateTests(unittest.TestCase): self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2]) self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250]) for state in self.account_states(): - self.assertEqual(state["primogems"], 1) + self.assertEqual(state["primogems"], 0) matches = [ event for event in state["coin_events"] @@ -173,14 +174,9 @@ class ReleaseUpdateTests(unittest.TestCase): primogem_matches = [ event for event in state["primogem_events"] - if event["delta"] == 1 - and event["reason"] == f"{self.release} 版本更新补偿" + if event["reason"] == f"{self.release} 版本更新补偿" ] - self.assertEqual(len(primogem_matches), 1) - self.assertEqual( - primogem_matches[0]["created_at"], - applied_at.timestamp(), - ) + self.assertEqual(primogem_matches, []) self.assertEqual(self.announcement_marker()["state"], "delivered") self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1) @@ -198,7 +194,7 @@ class ReleaseUpdateTests(unittest.TestCase): deliverer=uncertain, ) self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250]) - self.assertEqual([state["primogems"] for state in self.account_states()], [1, 1]) + self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0]) self.assertEqual(self.announcement_marker()["state"], "uncertain") called = [] @@ -229,7 +225,7 @@ class ReleaseUpdateTests(unittest.TestCase): self.assertEqual(result["compensation"], "already_applied") self.assertEqual(len(called), 1) self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250]) - self.assertEqual([state["primogems"] for state in self.account_states()], [1, 1]) + self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0]) self.assertEqual(self.announcement_marker()["state"], "delivered")