# Danding Points 插件 全局积分/虚拟货币服务层,为其他插件提供统一的积分管理能力。用户在一个插件中获得的积分可以在另一个插件中消费。 本插件不包含任何用户交互命令(无 NoneBot matcher),纯 API 服务层,供其他插件直接 import 调用。 ## 目录结构 ``` danding_points/ ├── __init__.py # 插件元数据 & points_api 单例导出 ├── config.py # 配置类 └── api.py # PointsAPI 核心 ``` ## 配置选项 在 `.env` 文件或 NoneBot 配置文件中添加(均带 `DANDING_` 前缀): | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `DANDING_POINTS_API_HOST` | str | `https://api.danding.vip/bot/points` | xapi 积分 API 地址 | | `DANDING_BOT_USER` | str | `1424473282` | xapi Bot 鉴权用户 | | `DANDING_BOT_TOKEN` | str | 空 | xapi Bot 鉴权 Token,未设置时读取 `DANDING_API_TOKEN` / `BOT_TOKEN` | 积分余额上限与单次操作上限由 xapi `BOT_POINTS_MAX_BALANCE` / `BOT_POINTS_MAX_PER_OPERATION` 控制,nonebot 本地不再持有阈值。 ## API 接口 所有方法均为 `async`,通过 `points_api` 单例调用。 ### `get_balance(user_id: str) -> int` 查询用户积分余额。用户不存在时自动返回 `0`,不会创建账户。 ```python balance = await points_api.get_balance("123456") ``` ### `add_points(user_id, amount, source, reason=None) -> Tuple[bool, int]` 为用户增加积分。用户不存在时自动建户。 - `amount`: 正整数,必须 > 0 - `source`: 来源标识,不能为空(用于流水追踪) - `reason`: 变动原因,可选 - 返回 `(success, new_balance)`,失败时 `success=False` 触发校验:操作上限 → 余额上限。任一校验失败均返回 `(False, 当前余额)`。 ```python ok, balance = await points_api.add_points("123456", 100, "gacha", "抽卡奖励") if ok: print(f"充值成功,当前余额: {balance}") ``` ### `spend_points(user_id, amount, source, reason=None) -> Tuple[bool, int]` 消费用户积分。余额不足时返回 `(False, 当前余额)`。 - `amount`: 正整数,必须 > 0 - 流水中 `amount` 记录为负数 ```python ok, balance = await points_api.spend_points("123456", 30, "shop", "购买道具") if not ok: print("余额不足") ``` ### `set_points(user_id, amount, source, reason=None) -> Tuple[bool, int]` 直接设定用户积分。**绕过** 操作上限和余额上限约束。 - `amount`: 非负整数,必须 >= 0 - 新值等于旧值时不写流水,直接返回 `(True, amount)` - `total_earned` 仅累加正向差额(设低不影响) ```python ok, balance = await points_api.set_points("123456", 500, "admin", "管理员调整") ``` ### `get_transactions(user_id, limit=20, offset=0) -> List[dict]` 查询用户积分流水记录,按时间倒序。 - `limit`: 1~100,超出范围自动裁剪 - `offset`: 分页偏移量,>= 0 返回字段:`id, user_id, amount, balance_after, source, reason, created_at` ```python txs = await points_api.get_transactions("123456", limit=10, offset=0) for tx in txs: print(f"{tx['created_at']} {tx['amount']:+d} 余额:{tx['balance_after']}") ``` ### `get_ranking(limit=10, order_by="points") -> List[dict]` 查询积分排行榜。 - `limit`: 1~100 - `order_by`: `"points"`(按余额)或 `"total_earned"`(按累计获得),其他值回退为 `"points"` - 使用 `RANK()` 窗口函数,同分并列,次级排序按 `user_id` 字母序 返回字段:`rank, user_id, points, total_earned, total_spent` ```python ranking = await points_api.get_ranking(limit=10, order_by="points") for r in ranking: print(f"#{r['rank']} {r['user_id']} {r['points']}分") ``` ## 其他插件对接 ### 基本用法 在需要积分功能的插件中 import 单例即可: ```python from nonebot import require require("danding_points") from danding_bot.plugins.danding_points import points_api ``` ### 示例:抽卡插件发放奖励 ```python # 在 onmyoji_gacha 插件中 from danding_bot.plugins.danding_points import points_api async def reward_user(user_id: str): ok, balance = await points_api.add_points( user_id, 50, "onmyoji_gacha", "SSR 抽到奖励" ) return balance ``` ### 示例:商店插件消费积分 ```python # 在 shop 插件中 from danding_bot.plugins.danding_points import points_api async def buy_item(user_id: str, cost: int): ok, balance = await points_api.spend_points( user_id, cost, "shop", "购买商品" ) if not ok: return "积分不足" return f"购买成功,剩余 {balance} 积分" ``` ### 示例:管理员插件调整积分 ```python # 在 danding_api 插件中 from danding_bot.plugins.danding_points import points_api async def admin_set(user_id: str, amount: int): ok, balance = await points_api.set_points( user_id, amount, "danding_api", "管理员手动调整" ) return balance ``` ### source 命名建议 `source` 参数用于标识积分变动来源,建议各插件使用自身插件名作为 source: | 插件 | source 值 | |------|-----------| | onmyoji_gacha 签到 | `"gacha_sign"` | | group_horse_racing | `"horse_race"` | | 管理调整 | `"admin"` | ## 数据库 本插件不再写入本地 SQLite。积分账户与流水由 xapi MySQL `bot_user_points` / `bot_point_transactions` 承载,nonebot 只通过 `/bot/points/*` HTTP API 读写。