This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%。完整流程见
|
||||
|
||||
@@ -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`.
|
||||
@@ -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"]
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -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 = [
|
||||
|
||||
@@ -36,4 +36,4 @@ __all__ = [
|
||||
"write_default_profile",
|
||||
]
|
||||
|
||||
__version__ = "0.6.10a0+1015"
|
||||
__version__ = "0.6.11a0+1015"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -16,4 +16,4 @@ __all__ = [
|
||||
"process_request",
|
||||
]
|
||||
|
||||
__version__ = "0.6.10a0+1015"
|
||||
__version__ = "0.6.11a0+1015"
|
||||
|
||||
@@ -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'",
|
||||
]
|
||||
|
||||
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"verify_installed_release_signature",
|
||||
]
|
||||
|
||||
__version__ = "0.6.10a0+1015"
|
||||
__version__ = "0.6.11a0+1015"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -90,4 +90,4 @@ __all__ = [
|
||||
"render_feature_list",
|
||||
]
|
||||
|
||||
__version__ = "0.6.10a0+1015"
|
||||
__version__ = "0.6.11a0+1015"
|
||||
|
||||
+45
@@ -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))
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -38,4 +38,4 @@ __all__ = [
|
||||
"SingleGroupContext",
|
||||
]
|
||||
|
||||
__version__ = "0.6.10a0+1015"
|
||||
__version__ = "0.6.11a0+1015"
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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("/彩票开始")
|
||||
|
||||
Reference in New Issue
Block a user