From bf4f28f48e36094b0691728aecd0274499a1501f Mon Sep 17 00:00:00 2001 From: JWeBot Contributors Date: Thu, 15 Oct 2026 19:00:29 +0800 Subject: [PATCH] Add coin leaderboard command --- CHANGELOG.md | 9 ++ README.md | 16 ++- VERSION | 2 +- docs/local-plugins-0.6.1.md | 5 + extensions/jwebot/coin-ranking/README.md | 20 ++++ extensions/jwebot/coin-ranking/plugin.toml | 14 +++ .../jwebot/coin-ranking/src/coin_ranking.py | 45 ++++++++ .../jwebot/coin-ranking/tests/test_plugin.py | 29 +++++ packages/jwebot-config/pyproject.toml | 2 +- .../src/jwebot_config/__init__.py | 2 +- packages/jwebot-extension-host/pyproject.toml | 6 +- .../src/jwebot_extension_host/__init__.py | 2 +- packages/jwebot-kernel/pyproject.toml | 8 +- .../src/jwebot_kernel/__init__.py | 2 +- .../src/jwebot_kernel/edge_runtime.py | 108 ++++++++++++++++++ .../jwebot-official-extensions/pyproject.toml | 4 +- .../jwebot_official_extensions/__init__.py | 2 +- .../adapters/coin_ranking.py | 45 ++++++++ .../jwebot_official_extensions/descriptors.py | 11 ++ .../local_commands.py | 1 + .../player_games.py | 25 ++++ packages/jwebot-sdk/pyproject.toml | 2 +- .../jwebot-sdk/src/jwebot_sdk/__init__.py | 2 +- pyproject.toml | 2 +- tests/test_official_extension_inventory.py | 1 + tests/test_release_063.py | 16 +++ 26 files changed, 357 insertions(+), 24 deletions(-) create mode 100644 extensions/jwebot/coin-ranking/README.md create mode 100644 extensions/jwebot/coin-ranking/plugin.toml create mode 100644 extensions/jwebot/coin-ranking/src/coin_ranking.py create mode 100644 extensions/jwebot/coin-ranking/tests/test_plugin.py create mode 100644 packages/jwebot-official-extensions/src/jwebot_official_extensions/adapters/coin_ranking.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 476b648..32179fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## alpha 0.6.11.1015 + +- Add `/大富翁榜`, `/金币排行`, `/金币排行榜`, and `/richlist` for a + group-scoped virtual coin leaderboard. Rankings are read-only, sorted by + current coin balance, recover display names from configured aliases and + observed profiles, and mark active loan principal when present. +- Split leaderboard command ownership into a dedicated reviewed + `coin-ranking` local extension. + ## alpha 0.6.10.1015 - Add virtual coin loans with `/贷款 [金额]` and `/loan amount`, plus diff --git a/README.md b/README.md index 82159b3..204b383 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC 观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件, 并通过带视觉确认的 GUI 发送链路回复。 -当前版本:`alpha 0.6.10.1015`。 +当前版本:`alpha 0.6.11.1015`。 0.6.1 新增本地上下文总结、人物与别名索引、单人虚拟金币游戏,并增加 两秒画面复核以补抓遗漏的消息变化。命令和游戏管理规则见 @@ -43,6 +43,8 @@ MR15/加时参数都由数据文件驱动,启动时会校验字段、ID 和素 收款人需先发送 `/金币` 初始化账户。 0.6.10 新增虚拟金币贷款:`/贷款 [金额]` 最多借入 500 金币,`/还款` 可按金额或全部归还;每个账户同一时间最多一笔未还贷款。 +0.6.11 新增 `/大富翁榜`、`/金币排行`,按当前群金币余额展示排行榜, +有未还贷款时会在榜单中标注。 ### 运行流程 @@ -109,6 +111,7 @@ PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh /上下文 [100|200|300](/context) /人物列表 [页码](/people);/人物 [名称或别名](/person) /金币(/wallet);/抽奖 [金额](/draw) +/大富翁榜(/金币排行) /转账 对方 金额(/transfer name amount) /贷款 [金额](/loan amount);/还款 [金额|全部](/repay) /彩票 开始|报名|当前状态|开奖|取消(/lottery) @@ -287,7 +290,7 @@ visually confirmed GUI bridge. Current version: ```text -alpha 0.6.10.1015 +alpha 0.6.11.1015 ``` ## Runtime Flow @@ -388,10 +391,11 @@ Version 0.6.1 adds local `/context 100|200|300` extractive summaries, `/people` and `/person` lookup, and `/wallet`, `/draw`, `/lottery`, `/blackjack` single-player virtual coin games. Chinese aliases share the same implementations. Accounts receive 1000 virtual coins once; funds, game state and event replay -results commit together. `/transfer name amount` moves virtual coins between -initialized group-scoped accounts in one ledger transaction. OCR display aliases -identify group-scoped people; `/loan amount` and `/repay` manage one capped -virtual coin loan per account. Native WeChat account IDs are unavailable. See +results commit together. `/richlist` shows a group-scoped coin leaderboard. +`/transfer name amount` moves virtual coins between initialized group-scoped +accounts in one ledger transaction. OCR display aliases identify group-scoped +people; `/loan amount` and `/repay` manage one capped virtual coin loan per +account. Native WeChat account IDs are unavailable. See [plugin rules and capture reconciliation](docs/local-plugins-0.6.1.md). Version 0.6.2 adds a 30-second total deadline to direct DeepSeek streams so an unfinished upstream response cannot occupy both generation workers for about diff --git a/VERSION b/VERSION index 7b63817..9634937 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -alpha 0.6.10.1015 +alpha 0.6.11.1015 diff --git a/docs/local-plugins-0.6.1.md b/docs/local-plugins-0.6.1.md index 53c0060..8ec2fe2 100644 --- a/docs/local-plugins-0.6.1.md +++ b/docs/local-plugins-0.6.1.md @@ -8,6 +8,7 @@ OpenClaw;中文、英文命令对应同一实现,英文不区分大小写, |---|---|---| | context-summary | `/上下文 [100|200|300]` | `/context [100|200|300]` | | single-player-games | `/金币` | `/wallet` | +| coin-ranking | `/大富翁榜`、`/金币排行` | `/richlist` | | coin-transfer | `/转账 对方 金额` | `/transfer name amount` | | coin-loan | `/贷款 [金额]`、`/还款 [金额\|全部]` | `/loan amount`、`/repay [amount\|all]` | | single-player-games | `/抽奖 [金额]` | `/draw [金额]` | @@ -71,6 +72,10 @@ person_key。相同人物的已配置别名共用金币和牌局,其他群使 一笔未还贷款。`/还款 [金额|全部]` 从当前金币余额中扣除并减少未还 贷款,借款、还款、响应记录和重试去重同样写在一个 ledger 事务内。 +`/大富翁榜` 或 `/金币排行 [人数]` 只读取当前群已有金币账户,不会创建 +调用者账户。榜单按当前金币余额降序排列,人数范围 1~20;账户存在 +未还贷款时会在名字后标注贷款本金。 + 抽奖默认下注 10:5% 概率返还五倍下注,20% 返还两倍,其余无返还。 alpha 0.6.3 将旧的个人即时彩票替换为群内多人轮次。三位数字顺序无关, 下注范围 1~500,命中 1/2/3 个分别返还 50%/120%/300%。完整流程见 diff --git a/extensions/jwebot/coin-ranking/README.md b/extensions/jwebot/coin-ranking/README.md new file mode 100644 index 0000000..9b9aaa0 --- /dev/null +++ b/extensions/jwebot/coin-ranking/README.md @@ -0,0 +1,20 @@ +# coin-ranking + +Author: `jwebot` + +富榜 + +Help: + +- `/大富翁榜` + +Registered commands: + +- `/金币排行` +- `/金币排行榜` +- `/大富翁榜` +- `/richlist` + +The adapter has no direct Ledger, GUI, environment, shell, or network access. +It delegates to the reviewed `coin-ranking` Kernel service profile +and returns a structured `ReplyPlan`. diff --git a/extensions/jwebot/coin-ranking/plugin.toml b/extensions/jwebot/coin-ranking/plugin.toml new file mode 100644 index 0000000..b1228a4 --- /dev/null +++ b/extensions/jwebot/coin-ranking/plugin.toml @@ -0,0 +1,14 @@ +id = "coin-ranking" +author = "jwebot" +description = "富榜" +help = ["/大富翁榜"] +version = "1.0.0" +api_version = "1" +entrypoint = "coin_ranking:plugin" +type = "command" + +[capabilities] +commands = ["/金币排行", "/金币排行榜", "/大富翁榜", "/richlist"] +storage = false +network_profiles = [] +service_profiles = ["coin-ranking"] diff --git a/extensions/jwebot/coin-ranking/src/coin_ranking.py b/extensions/jwebot/coin-ranking/src/coin_ranking.py new file mode 100644 index 0000000..938f8aa --- /dev/null +++ b/extensions/jwebot/coin-ranking/src/coin_ranking.py @@ -0,0 +1,45 @@ +"""Generated reviewed adapter for coin-ranking.""" + +from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext + + +PROFILE = 'coin-ranking' +AUTHOR = 'jwebot' +DESCRIPTION = '富榜' +HELP_ENTRIES = ('/大富翁榜',) +COMMANDS = ('/金币排行', '/金币排行榜', '/大富翁榜', '/richlist') + + +def _handler(context: SingleGroupContext, command: str): + def handle(event: Event, arguments: str) -> ExtensionResult: + response = context.services.call( + PROFILE, + operation="execute", + payload={ + "command": command, + "arguments": arguments, + "event": event.to_mapping(), + }, + ) + texts = response.get("texts") + if isinstance(texts, list) and texts and all( + isinstance(item, str) and item.strip() for item in texts + ): + return ExtensionResult( + reply=ReplyPlan.multi_text(texts), + metadata={"local_extension": PROFILE, "author": AUTHOR}, + ) + text = response.get("text") + if not isinstance(text, str) or not text.strip(): + raise ValueError("local service returned invalid text") + return ExtensionResult( + reply=ReplyPlan.text(text), + metadata={"local_extension": PROFILE, "author": AUTHOR}, + ) + + return handle + + +def plugin(context: SingleGroupContext) -> None: + for command in COMMANDS: + context.commands.register(command, _handler(context, command)) diff --git a/extensions/jwebot/coin-ranking/tests/test_plugin.py b/extensions/jwebot/coin-ranking/tests/test_plugin.py new file mode 100644 index 0000000..4eff247 --- /dev/null +++ b/extensions/jwebot/coin-ranking/tests/test_plugin.py @@ -0,0 +1,29 @@ +import sys +import unittest +from pathlib import Path +from unittest import mock + + +SOURCE = Path(__file__).resolve().parents[1] / "src" +sys.path.insert(0, str(SOURCE)) + +from coin_ranking import AUTHOR, DESCRIPTION, HELP_ENTRIES, plugin + + +class PluginTests(unittest.TestCase): + def test_registers_declared_commands_and_author(self): + context = mock.Mock() + plugin(context) + registered = [ + call.args[0] + for call in context.commands.register.call_args_list + ] + self.assertEqual(AUTHOR, 'jwebot') + self.assertEqual(DESCRIPTION, '富榜') + self.assertEqual(HELP_ENTRIES, ('/大富翁榜',)) + self.assertIn('/金币排行', registered) + self.assertEqual(len(registered), 4) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/jwebot-config/pyproject.toml b/packages/jwebot-config/pyproject.toml index 4d6d5f4..3396cee 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 = "0.6.10a0+1015" +version = "0.6.11a0+1015" 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 3938097..a52de0b 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__ = "0.6.10a0+1015" +__version__ = "0.6.11a0+1015" diff --git a/packages/jwebot-extension-host/pyproject.toml b/packages/jwebot-extension-host/pyproject.toml index 71d06bc..8e92ec6 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 = "0.6.10a0+1015" +version = "0.6.11a0+1015" description = "Single-release, single-invocation process host for JWeBot extensions" requires-python = ">=3.9" dependencies = [ - "jwebot-sdk==0.6.10a0+1015", - "jwebot-kernel==0.6.10a0+1015", + "jwebot-sdk==0.6.11a0+1015", + "jwebot-kernel==0.6.11a0+1015", ] [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 31dfac1..11a90f9 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__ = "0.6.10a0+1015" +__version__ = "0.6.11a0+1015" diff --git a/packages/jwebot-kernel/pyproject.toml b/packages/jwebot-kernel/pyproject.toml index 55a7fcd..7a0b727 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 = "0.6.10a0+1015" +version = "0.6.11a0+1015" description = "Stable routing and extension authority for JWeBot" requires-python = ">=3.9" dependencies = [ - "jwebot-config==0.6.10a0+1015", - "jwebot-sdk==0.6.10a0+1015", - "jwebot-official-extensions==0.6.10a0+1015", + "jwebot-config==0.6.11a0+1015", + "jwebot-sdk==0.6.11a0+1015", + "jwebot-official-extensions==0.6.11a0+1015", "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 1dfbd4a..7ca75a3 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__ = "0.6.10a0+1015" +__version__ = "0.6.11a0+1015" diff --git a/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py b/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py index 177e968..315940f 100644 --- a/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py +++ b/packages/jwebot-kernel/src/jwebot_kernel/edge_runtime.py @@ -3665,6 +3665,114 @@ class Ledger: "game_counts": dict(account["game_counts"]), } + def player_coin_ranking( + self, + group: str, + *, + limit: int = 10, + ) -> list[dict[str, Any]]: + group = bounded_text(group, limit=256) + self.seed_person_aliases(group) + with self.lock: + account_rows = [ + dict(row) + for row in self.connection.execute( + """ + SELECT person_key, state_json, updated_at + FROM player_accounts + WHERE group_fingerprint = ? + """, + (group,), + ).fetchall() + ] + alias_rows = [ + dict(row) + for row in self.connection.execute( + """ + SELECT DISTINCT canonical_name + FROM person_aliases + WHERE group_fingerprint = ? + """, + (group,), + ).fetchall() + ] + profile_rows = [ + dict(row) + for row in self.connection.execute( + """ + SELECT person_key, display_name + FROM person_profiles + WHERE group_fingerprint = ? + """, + (group,), + ).fetchall() + ] + observation_rows = [ + dict(row) + for row in self.connection.execute( + """ + SELECT person_key, display_name + FROM person_observations + WHERE group_fingerprint = ? + ORDER BY observed_at DESC, created_at DESC + """, + (group,), + ).fetchall() + ] + labels: dict[str, str] = {} + for row in alias_rows: + canonical = bounded_text(row["canonical_name"], limit=120) + if canonical: + labels[person_scope_key(group, canonical)] = person_display_label( + canonical + ) + for row in profile_rows: + person_key = str(row["person_key"]) + labels.setdefault( + person_key, + person_display_label(row["display_name"]), + ) + for row in observation_rows: + person_key = str(row["person_key"]) + labels.setdefault( + person_key, + person_display_label(row["display_name"]), + ) + records: list[dict[str, Any]] = [] + for row in account_rows: + person_key = str(row["person_key"]) + try: + account = json.loads(str(row["state_json"])) + except json.JSONDecodeError as error: + raise LocalCommandError("金币账户状态无效") from error + if not isinstance(account, dict): + raise LocalCommandError("金币账户状态无效") + normalize_player_account(account) + loan = account.get("loan") + loan_principal = ( + int(loan.get("principal", 0)) + if isinstance(loan, dict) + else 0 + ) + records.append( + { + "display_name": labels.get( + person_key, + f"玩家 {person_key[:8]}", + ), + "balance": int(account["balance"]), + "loan": loan_principal, + "updated_at": str(row["updated_at"]), + } + ) + records.sort( + key=lambda item: ( + -int(item["balance"]), + str(item["display_name"]).casefold(), + ) + ) + return records[: max(1, min(int(limit), 20))] + def transfer_player_coins( self, group: str, diff --git a/packages/jwebot-official-extensions/pyproject.toml b/packages/jwebot-official-extensions/pyproject.toml index 6439f1e..1304257 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 = "0.6.10a0+1015" +version = "0.6.11a0+1015" description = "Reviewed local command adapters and broker implementation for JWeBot" requires-python = ">=3.9" dependencies = [ - "jwebot-sdk==0.6.10a0+1015", + "jwebot-sdk==0.6.11a0+1015", ] [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 3804650..c00738c 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__ = "0.6.10a0+1015" +__version__ = "0.6.11a0+1015" diff --git a/packages/jwebot-official-extensions/src/jwebot_official_extensions/adapters/coin_ranking.py b/packages/jwebot-official-extensions/src/jwebot_official_extensions/adapters/coin_ranking.py new file mode 100644 index 0000000..938f8aa --- /dev/null +++ b/packages/jwebot-official-extensions/src/jwebot_official_extensions/adapters/coin_ranking.py @@ -0,0 +1,45 @@ +"""Generated reviewed adapter for coin-ranking.""" + +from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext + + +PROFILE = 'coin-ranking' +AUTHOR = 'jwebot' +DESCRIPTION = '富榜' +HELP_ENTRIES = ('/大富翁榜',) +COMMANDS = ('/金币排行', '/金币排行榜', '/大富翁榜', '/richlist') + + +def _handler(context: SingleGroupContext, command: str): + def handle(event: Event, arguments: str) -> ExtensionResult: + response = context.services.call( + PROFILE, + operation="execute", + payload={ + "command": command, + "arguments": arguments, + "event": event.to_mapping(), + }, + ) + texts = response.get("texts") + if isinstance(texts, list) and texts and all( + isinstance(item, str) and item.strip() for item in texts + ): + return ExtensionResult( + reply=ReplyPlan.multi_text(texts), + metadata={"local_extension": PROFILE, "author": AUTHOR}, + ) + text = response.get("text") + if not isinstance(text, str) or not text.strip(): + raise ValueError("local service returned invalid text") + return ExtensionResult( + reply=ReplyPlan.text(text), + metadata={"local_extension": PROFILE, "author": AUTHOR}, + ) + + return handle + + +def plugin(context: SingleGroupContext) -> None: + for command in COMMANDS: + context.commands.register(command, _handler(context, command)) diff --git a/packages/jwebot-official-extensions/src/jwebot_official_extensions/descriptors.py b/packages/jwebot-official-extensions/src/jwebot_official_extensions/descriptors.py index a5b7f42..d9cec75 100644 --- a/packages/jwebot-official-extensions/src/jwebot_official_extensions/descriptors.py +++ b/packages/jwebot-official-extensions/src/jwebot_official_extensions/descriptors.py @@ -42,6 +42,10 @@ LOCAL_COMMAND_ALIASES = { "context": "context_summary", "金币": "wallet", "wallet": "wallet", + "金币排行": "coin_ranking", + "金币排行榜": "coin_ranking", + "大富翁榜": "coin_ranking", + "richlist": "coin_ranking", "转账": "transfer", "transfer": "transfer", "贷款": "loan", @@ -171,6 +175,13 @@ LOCAL_EXTENSIONS = ( help_entries=("/转账 对方 金额;/transfer",), command_kinds=("transfer",), ), + LocalExtensionDescriptor( + extension_id="coin-ranking", + author=JWEBOT_AUTHOR, + description="富榜", + help_entries=("/大富翁榜",), + command_kinds=("coin_ranking",), + ), LocalExtensionDescriptor( extension_id="coin-loan", author=JWEBOT_AUTHOR, diff --git a/packages/jwebot-official-extensions/src/jwebot_official_extensions/local_commands.py b/packages/jwebot-official-extensions/src/jwebot_official_extensions/local_commands.py index 337ddb8..063b698 100644 --- a/packages/jwebot-official-extensions/src/jwebot_official_extensions/local_commands.py +++ b/packages/jwebot-official-extensions/src/jwebot_official_extensions/local_commands.py @@ -154,6 +154,7 @@ class LocalCommandEngine: return self.reply_cleaner(self.person_index(row, kind, arguments)) if kind in { "wallet", + "coin_ranking", "transfer", "loan", "repay", 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 facbb55..2b64e3b 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 @@ -175,6 +175,8 @@ class PlayerGames: return self.game_history(row, arguments) if kind == "lottery": return self.group_lottery(row, arguments) + if kind == "coin_ranking": + return self.coin_ranking(row, arguments) if kind == "transfer": return self.transfer(row, arguments) if kind in {"loan", "repay"}: @@ -316,6 +318,29 @@ class PlayerGames: str(row["group_fingerprint"]), str(row["sender"]), str(row["event_id"]), kind + ":" + arguments, execute) + def coin_ranking(self, row: dict[str, Any], arguments: str) -> str: + value = arguments.strip() + if value and not re.fullmatch(r"\d{1,2}", value): + return "用法:/大富翁榜 或 /金币排行 [人数]" + limit = int(value or 10) + if not 1 <= limit <= 20: + return "金币排行人数必须为 1~20" + records = self.ledger.player_coin_ranking( + str(row["group_fingerprint"]), + limit=limit, + ) + if not records: + return "当前群还没有金币账户;发送 /金币 初始化" + lines = ["大富翁榜:"] + for index, record in enumerate(records, start=1): + loan = int(record.get("loan", 0)) + loan_text = f"(未还贷款 {loan})" if loan else "" + lines.append( + f"{index}. {record['display_name']} {record['balance']} 金币" + f"{loan_text}" + ) + return "\n".join(lines) + @staticmethod def parse_transfer( arguments: str, diff --git a/packages/jwebot-sdk/pyproject.toml b/packages/jwebot-sdk/pyproject.toml index 3528279..409298f 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 = "0.6.10a0+1015" +version = "0.6.11a0+1015" 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 c53af5a..90224d1 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__ = "0.6.10a0+1015" +__version__ = "0.6.11a0+1015" diff --git a/pyproject.toml b/pyproject.toml index 62a1edc..7b103a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.jwebot] -release = "alpha 0.6.10.1015" +release = "alpha 0.6.11.1015" production_deployments_enabled_by_default = false [tool.jwebot.workspace] diff --git a/tests/test_official_extension_inventory.py b/tests/test_official_extension_inventory.py index c2eb1d7..bc41433 100644 --- a/tests/test_official_extension_inventory.py +++ b/tests/test_official_extension_inventory.py @@ -91,6 +91,7 @@ class LocalExtensionInventoryTests(unittest.TestCase): "dice": JWEBOT_AUTHOR, "coin": JWEBOT_AUTHOR, "coin-transfer": JWEBOT_AUTHOR, + "coin-ranking": JWEBOT_AUTHOR, "coin-loan": JWEBOT_AUTHOR, "random-number": JWEBOT_AUTHOR, "draw-lot": JWEBOT_AUTHOR, diff --git a/tests/test_release_063.py b/tests/test_release_063.py index d7dc675..61d0425 100644 --- a/tests/test_release_063.py +++ b/tests/test_release_063.py @@ -246,6 +246,22 @@ class Release063GameTests(unittest.TestCase): self.assertIn("贷款金额必须为 1~500", self.command("/loan 501")) self.assertIn("当前没有未还贷款", self.command("/还款 全部")) + def test_coin_ranking_lists_accounts_by_balance(self): + self.assertIn("金币 1000", self.command("/金币")) + self.assertIn("金币 1000", self.command("/金币", sender="Bob")) + self.assertIn("金币 1000", self.command("/金币", sender="Carol")) + self.command("/转账 Bob 250") + self.command("/贷款 100", sender="Carol") + + ranking = self.command("/大富翁榜") + + self.assertIn("大富翁榜:", ranking) + self.assertIn("1. bob 1250 金币", ranking) + self.assertIn("2. carol 1100 金币(未还贷款 100)", ranking) + self.assertIn("3. alice 750 金币", ranking) + self.assertNotIn("Dave", ranking) + self.assertIn("金币排行人数必须为 1~20", self.command("/金币排行 0")) + def test_open_round_and_history_survive_restart(self): root = Path(self.temp.name) self.command("/彩票开始")