Release beta 1.1.1 stacked scan boosts

This commit is contained in:
JWeBot Contributors
2026-10-16 17:31:45 +08:00
parent 35ef82f14f
commit 522e349491
20 changed files with 72 additions and 65 deletions
+8
View File
@@ -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 偶发重复识别消耗型命令导致重复回复的问题,并完善摩拉/扫码榜展示。
+3 -3
View File
@@ -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,
+1 -1
View File
@@ -1 +1 @@
beta 1.1.0.1016
beta 1.1.1.1016
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 = [
@@ -36,4 +36,4 @@ __all__ = [
"write_default_profile",
]
__version__ = "1.1.0b1+1016"
__version__ = "1.1.1b1+1016"
@@ -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]
@@ -16,4 +16,4 @@ __all__ = [
"process_request",
]
__version__ = "1.1.0b1+1016"
__version__ = "1.1.1b1+1016"
+4 -4
View File
@@ -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'",
]
@@ -107,4 +107,4 @@ __all__ = [
"verify_installed_release_signature",
]
__version__ = "1.1.0b1+1016"
__version__ = "1.1.1b1+1016"
@@ -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(
@@ -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]
@@ -90,4 +90,4 @@ __all__ = [
"render_feature_list",
]
__version__ = "1.1.0b1+1016"
__version__ = "1.1.1b1+1016"
@@ -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} 权重"
)
+1 -1
View File
@@ -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 = []
@@ -38,4 +38,4 @@ __all__ = [
"SingleGroupContext",
]
__version__ = "1.1.0b1+1016"
__version__ = "1.1.1b1+1016"
+1 -1
View File
@@ -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]
+3 -3
View File
@@ -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")
+7 -2
View File
@@ -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:今天也要加班吗喵")
+12 -16
View File
@@ -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")