Release beta 1.1.1 stacked scan boosts
This commit is contained in:
@@ -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 偶发重复识别消耗型命令导致重复回复的问题,并完善摩拉/扫码榜展示。
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
+20
-18
@@ -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} 权重"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:今天也要加班吗喵")
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user