Search by

drsanwujiang / dicerobot

drsanwujiang

A TRPG dice robot based on Swoole.

Package info

github.com/drsanwujiang/DiceRobot

Homepage

Documentation

Language:Python

pkg:composer/drsanwujiang/dicerobot

Statistics

Installs: 136

Dependents: 1

Suggesters: 0

Stars: 12

Open Issues: 0

3.1.0 2021-09-25 08:47 UTC

This package is auto-updated.

Last update: 2026-09-26 20:14:55 UTC


README

基于 QQ 开放平台的 TRPG 助手。

架构

dicerobot/
├── qq/        平台适配:签名校验、access token、OpenAPI 客户端、webhook 入口
├── bot/       运行时:事件归一化、回复配额、插件加载与调度
├── plugins/   内置插件:dice / check / nickname / system
├── trpg/      领域逻辑:掷骰引擎、检定规则(零 IO,不依赖任何外层包)
└── storage/   持久化:SQLAlchemy 模型、仓储与迁移

依赖方向单向:plugins -> bot -> qq、bot -> trpg、bot -> storage。

qq 为叶子包,不依赖任何内部模块。webhook 以本地 Protocol 声明事件接收方, 由 app.py 负责装配,因此 bot 调用客户端发消息不会与 webhook 形成循环依赖。

trpg 同样不依赖任何其他层,该约束由 dicerobot/trpg/ruff.toml 在 CI 中强制。

开发

poetry install
poetry run pytest
poetry run ruff check .
poetry run ruff format --check .  # CI 会校验格式,提交前请先格式化
poetry run mypy                   # strict 模式,只检查 dicerobot 包

数据库迁移在应用启动时自动执行,也可手动运行:

poetry run alembic upgrade head  # 升级到最新版本
poetry run alembic revision --autogenerate -m "说明"  # 依据模型变更生成迁移

迁移只需 DATABASE_URL(缺省时用默认值),不依赖机器人凭据。

指令

指令 说明
.r [表达式] [理由] 掷骰,如 .r 3d6+2 侦查、.r 4d6k3、.r (2d6+3)*2
.rh [表达式] [理由] 暗骰,结果私聊发给掷骰者,群内只公布表达式
.set [面数] 查看或设置默认骰
.ra <技能值> [理由] 技能检定,如 .ra 60 侦查
.rab / .rap 带奖励骰或惩罚骰的检定,如 .rab 60 侦查、.rab2 60(两个奖励骰)
.rah / .rha 暗检定,结果私聊发给发起者
.rahb / .rahp / .rhab / .rhap 带奖励骰或惩罚骰的暗检定
.rule [规则] 查看或设置检定规则(coc7、simple)
.nn [昵称] 设置昵称,.nn 清除 恢复默认
.bot on / .bot off 启用或停用机器人,仅限群主与管理员
.plugin / .plugin off dice 查看或启停插件,仅限群主与管理员
.help 列出可用指令
.ping 确认机器人在线

每条指令都有中文别名,如 .掷骰、.检定、.昵称;前缀也可用全角句号 。。

重复执行的次数写在指令之后或行尾都可以,.r 3#1d100、.r3#1d100 与 .r 1d100#3 等价。 N 为一到三位的正整数,其余写法(含 #0)视为正文的一部分;两处同时写出时无法判断以 谁为准,按未命中处理。

暗骰与暗检定经主动消息投递,用户若在客户端关闭了机器人的主动消息则收不到,此时会在 群内追加一句提示。

掷骰表达式

表达式采用 OneDice 标准的子集:

写法 含义 例
AdB A 个 B 面骰,A 与 B 都可缺省 .r、.r 5d、.r 3d6
AdBkC 取最高的 C 个相加 .r 4d6k3
AdBqC 取最低的 C 个相加 .r 4d6q3
bN / pN 带 N 个奖励骰或惩罚骰的 d100,N 缺省为 1 .r b、.r p2
AdBbC / AdBpC A 个各带 C 个奖惩骰的 d100 .r 2db3
混合运算 + - * /、乘方 ^ 与括号 .r d50*3+(2-p2)

奖惩骰把 d100 拆成十位骰与个位骰:追加若干个十位骰后,奖励骰取结果最小的一个,惩罚骰取 最大的一个,个位骰只掷一次。十位与个位都是 0 时结果为 100。奖惩骰因此固定为 D100, .r 2d20b1 会报错,也不受 .set 所设默认骰的影响。

输出会展开被舍弃的十位骰,便于验算:

.r b3     →  D100B3=25[奖励骰:6 0 1]=5
.rab 60   →  D100B=63[奖励骰:1]=13/60,极难成功
.rab2 60  →  D100B2=63[奖励骰:1 8]=13/60,极难成功

骰池(a)、双重十字(c)、命运骰(f)、多元组与循环不在支持范围内。

插件

每条指令都归属于某个插件。插件在模块层声明,指令以装饰器挂载:

plugin = Plugin(name="dice", display_name="掷骰", version="1.0.0", chat_settings=DiceChatSettings)


@plugin.command("r", "roll", "掷骰", description="掷骰,如 .r 3d6+2 侦查", max_times=30)
async def roll(context: CommandContext) -> None:
    settings = context.chat_settings(DiceChatSettings)
    ...

插件也可以响应非消息事件。这类事件自带 event_id,可作被动回复,因此不消耗主动 消息配额:

@plugin.event(EventType.GROUP_ADD_ROBOT, EventType.FRIEND_ADD)
async def greet(context: EventContext) -> None:
    context.write("我是 DiceRobot,发送 .help 查看可用指令。")

同一事件可被多个插件响应,它们共用一个回复会话,输出合并为一条消息;单个插件出错 不影响其余插件,且不会向用户回复错误提示。

插件设置以 JSON 存储,读取时由声明的 pydantic 模型校验并补齐默认值,因此增删设置项 不需要迁移数据库。写回须显式调用 save_settings 或 save_chat_settings。

启停分三层依次判定:会话总开关(.bot)、插件全局开关、插件在本会话的开关 (.plugin)。标记 requires_enabled=False 的指令不受这三层影响,用于保证关闭之后 仍有恢复手段。

第三方插件是独立的分发包,通过 entry points 被发现:

[project.entry-points."dicerobot.plugins"]
mycards = "mycards:plugin"

配置从环境变量或 .env 读取,参见 .env.example。

Windows 下若测试输出中的中文显示为乱码,设置 PYTHONUTF8=1 即可。

部署

平台侧的硬性约束

  • 回调地址必须是 HTTPS,端口只能是 80、443、8080、8443 之一
  • 需要在开放平台配置 IP 白名单,家用宽带的动态 IP 会导致请求被拒

步骤

  1. 在 QQ 开放平台 创建机器人,取得 AppID 与 AppSecret。

  2. 准备一台有公网 IP 的服务器和一个解析到它的域名。

  3. 复制配置并填入凭据:

    cp .env.example .env
  4. 把 Caddyfile 中的 bot.example.com 换成自己的域名。

  5. 启动:

    docker compose up -d

    Caddy 会自动申请证书;数据库与日志分别落在 ./data 与 ./logs。

  6. 在开放平台把回调地址填为 https://<域名>/qq/webhook。平台会立即下发一次校验 请求(op = 13),应用签名回传后地址才会生效。

  7. 在群里 @机器人 .ping,收到 pong 即联通。

自定义检定规则

检定规则是 data/rules/*.json,首次启动时自动写入内置的 coc7 与 simple,之后可直接编辑—— 比如把极难成功从五分之一改成三分之一:

{ "name": "极难成功", "description": "骰值不大于技能值的三分之一", "condition": "roll <= skill // 3" }

condition 中只能使用 skill 与 roll 两个变量以及算术、比较与布尔运算,函数调用一类会被拒绝。 等级按顺序取首个匹配,故大成功、大失败这类特例要排在普通成功之前。改完重启生效;写错会在启动时 直接报错并指明是哪份文件的哪一级。新增规则只需放一份新文件,文件名与其中的 id 保持一致,之后 在群里用 .rule <id> 切换。

运维

数据库迁移在每次启动时自动执行,且可重复运行,容器重建无需额外操作。

docker compose logs -f dicerobot  # 查看日志
docker compose pull && docker compose up -d  # 更新

镜像以非 root 用户运行,健康检查探测 /health。