Skip to content

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

commspt-bot-nonebot

LittleSkin Community Support QQ bot

LittleSkin 的社区支持 QQ 机器人,主要基于 nonebot/nonebot2 框架。

新增/优化的特性

  • 简易问答内容配置
  • 泛平台消息层:消息收发、图片、@ 均基于 UniMessage(UniSeg),命令解析跨平台通用
  • 双后端出图:BROWSERLESS_MODE 可在远程 Browserless 服务与本地 Playwright 渲染间切换
  • 外部服务失败重试:FAILED_RETRY_TIME 控制重试次数(默认 2,即合计最多 3 次请求),失败后回复统一提示「处理你的请求时发生了一些问题」并记录各网络服务的中文失败日志
  • 入群审批双入口:既可在通知消息下回复 do accept / do reject 文字命令审批,也可直接对通知消息贴 👍 / 👎 表情快捷审批(见入群审批)
  • 封禁记录上云:&banlog 一条命令把封禁信息写入飞书多维表格,类别走表格选项校验,支持多选与备注(见飞书封禁记录)
  • 请求日志:把项目对外部 HTTP / MongoDB / SDK 请求的失败按天追加写入文本文件,仅用于排障(config.yml 的 request_logging,默认开启,见请求日志)

目录结构

此结构可以表达大部分框架原理,但可能不是实时更新

assets/                               # 图片与字体(可通过 COMMSPT_ASSETS_DIR 覆盖)
plugins/commspt_bot/              # 父插件(共享层)
├── __init__.py                   # PluginMetadata + 自动加载子插件
├── config.py                     # 配置 schema(值请写在 .env)
├── models/                       # 外部 API 的 pydantic 模型层
│   ├── const.py                  # Yggdrasil / CSL 客户端与常量
│   ├── littleskin_api.py         # LittleSkin Admin API
│   ├── mongodb_data.py           # UIDMapping(Mongo)
│   ├── cloudconfig.py            # 云控开关
│   ├── csl_api.py                # CustomSkinLoader API
│   ├── version_api.py            # 版本查询(CSL / Authlib / Liberica Java)
│   ├── bingling_ipip.py          # IP 归属地
│   ├── feishu_bitable.py         # 飞书多维表格客户端(&banlog 写入)
│   └── render_user_info.py       # 用户信息卡数据模型 + 出图
├── utils/
│   ├── adv_filter.py             # 群白名单 / 管理员 规则与权限
│   ├── messenger.py              # 跨群发送工具
│   ├── join_action.py            # 入群审批公共执行核心 + 表情审批凭证索引
│   ├── join_request_notice.py    # 入群申请通知的格式化与解析
│   ├── browserless.py            # Jinja2 渲染 → Browserless 远程 / 本地 Playwright 截图
│   ├── onebot_message.py         # 原生命令匹配 / 参数解析 / 引用回复工具
│   ├── skinrendermcapi.py        # 皮肤渲染 + 水印
│   ├── mongodb_manager.py        # UID 映射读写
│   ├── ban_lookup.py             # 按 UID 查询封禁记录(&user / 入群通知复用)
│   ├── ban_record.py             # &banlog 参数解析与表格列名映射(纯函数)
│   └── random_sleep.py           # 随机延时(拟人化)
├── templates/                    # 用户信息卡模板
└── plugins/                      # 14 个子插件(每个功能一个)
    ├── commspt_simple_response.py    # 单一 on_message 分发器,服务 commspt_simple_response.json 中的全部静态问答命令;管理员 &sreload 可在运行时热重载
    ├── commspt_profile.py            # &ygg / &pro 玩家查询
    ├── commspt_cao.py                # 生草复读机
    ├── commspt_view_skin.py          # &view / &view.ygg / %view.pro 皮肤渲染
    ├── commspt_profile_check.py      # &check 玩家体检
    ├── commspt_user_info.py          # &user / &setuid 用户信息卡
    ├── commspt_group_member.py       # &uid QQ↔UID 查询
    ├── commspt_get_latest.py         # &csl.latest / &ygg.latest / &java.latest
    ├── commspt_mute.py               # &mute / &unmute / &recall / &muteall ...(原生 OneBot V11)
    ├── commspt_ban_record.py         # &banlog 封禁记录写入飞书多维表格
    ├── commspt_join_group.py         # 入群申请审核 + 新成员欢迎
    ├── commspt_do_action_join.py     # do accept|reject 手动审批(回复通知消息,裸命令,原生 on_regex)
    ├── commspt_join_emoji.py         # 对审批通知贴 👍/👎 表情快捷审批(原生 on_notice)
    ├── commspt_ot_nt.py              # &ot 定向提醒
    ├── commspt_dev.py                # &id 环境信息
    └── commspt_log_file.py           # 群文件消息日志(占位)

快速开始

1. 安装依赖

uv sync

2. 配置

cp .env.example .env                # 或 .env.prod
cp config.yml.example config.yml

按需填写:群号(DEFINED_QQ__*)、管理员列表(config.yml 中的 admin_list)、请求日志开关与目录 (config.yml 中的 request_logging,见请求日志)、外部 API 地址、 LITTLESKIN_ADMIN_TOKEN、DB_MONGO__URL 等。简单与嵌套配置从 .env 读取(config.py 的 Setting 只定义 schema); 结构化配置(列表 / 映射块)从 config.yml 读取(默认位于项目根目录,可用 COMMSPT_CONFIG_FILE 覆盖路径)。 简易问答(&help / &faq / &pay 等)的回复内容在项目根目录的 commspt_simple_response.json 中编辑,可通过 COMMSPT_SIMPLE_RESPONSE_FILE 环境变量覆盖路径。 静态资源(图片、字体)默认存放在项目根目录的 assets/ 下,可通过 COMMSPT_ASSETS_DIR 环境变量覆盖。

配置项说明:

配置项 说明
COMMAND_START 命令前缀(NoneBot 内置),默认 ["&"]
ALCONNA_USE_COMMAND_START 是否将 COMMAND_START 作为 Alconna 全局命令前缀。库默认值为 false;本项目在 .env.example 中设为 true,且必须为 true,否则配置的前缀不会应用到一般 Alconna 命令(含静态问答分发器的前缀识别)。禁言 / 撤回 / 入群审批等原生命令自行读取纯文本前缀,不受该开关影响。
DEFINED_QQ__LITTLESKIN_MAIN / __LITTLESKIN_CAFE 主群 / 水群群号
DEFINED_QQ__COMMSPT_GROUP 社区支持组群号
DEFINED_QQ__NOTIFICATION_CHANNEL 通知群号(入群欢迎通知)
DEFINED_QQ__DEV_GROUP 开发群群号
admin_list(config.yml) 管理员 QQ 列表(YAML 数组)
request_logging(config.yml) 请求日志开关与目录:enabled(默认 true)、directory(默认 ./logs/commspt-bot/),见请求日志
LITTLESKIN_ADMIN_TOKEN LittleSkin Admin API Token
DB_MONGO__URL MongoDB 连接串(QQ↔UID 映射)
DB_MONGO__CONNECT_TIMEOUT_MS / __SERVER_SELECTION_TIMEOUT_MS / __SOCKET_TIMEOUT_MS MongoDB 客户端超时(毫秒,默认 5000 / 10000 / 10000)。连接串中已显式声明的同名选项优先
API_BROWSERLESS__ENDPOINT browserless 截图服务(BROWSERLESS_MODE=REMOTE 时使用)
BROWSERLESS_MODE 出图后端:REMOTE(默认,调用远程 Browserless)或 LOCAL(本地 Playwright 渲染)。LOCAL 需先安装可选依赖与浏览器内核:uv sync --extra render-local && uv run playwright install chromium,并自行准备 assets/browserless/ 下的本地资源(含字体,详见下文「本地渲染」小节)
FAILED_RETRY_TIME 外部网络服务请求失败后的重试次数(默认 2,即合计最多 3 次请求;0 表示不重试)。HTTP 请求的重试在传输层统一实现(主站管理端 API、IP 属地、云控、CSL、飞书、版本查询);映射数据库(MongoDB)与 Yggdrasil SDK 仍由调用层重试。审批动作、消息发送、渲染类服务(SkinRenderMC / Browserless / 本地 Playwright)与 QQ 协议端调用不重试
API_SKINRENDERMC__ENDPOINT 皮肤渲染服务
API_CLOUDCONFIG 云控配置(JSON 对象)
API_FEISHU__APP_ID / API_FEISHU__APP_SECRET 飞书自建应用凭证(&banlog 写多维表格用),见飞书封禁记录
FEISHU_BAN__APP_TOKEN / FEISHU_BAN__TABLE_ID 多维表格 app_token 与 table_id
FEISHU_BAN__FIELDS__* 多维表格中各逻辑字段对应的列名
COMMSPT_SIMPLE_RESPONSE_FILE 简易问答内容 JSON 文件路径(默认项目根目录 commspt_simple_response.json)
COMMSPT_ASSETS_DIR 静态资源目录(默认项目根目录 assets/)

本地渲染(BROWSERLESS_MODE=LOCAL)与字体

LOCAL 模式不依赖远程 Browserless,改用本地 Playwright + Chromium 出图,模板中所有远程资源会改从项目根目录的 assets/browserless/ 读取(当前只剩字体一个)。

卡片的主字体是 MiSans Bold(小米 MiSans,免费商用),由 @font-face 显式加载,REMOTE 与 LOCAL 都用它,不再依赖渲染机上装了什么字体——早期用系统字体栈时,Windows 命中 Noto Sans SC、Linux 命中 Noto Sans CJK SC,字重与度量都不同,同一模板在两处出图并不一致。

UID / 积分 / 邮箱 / QQ / 时间戳等编号类字段同样走 MiSans Bold,并开启 font-variant-numeric: tabular-nums——MiSans 自带 tnum,数字位宽一致,逐位核对一样清楚。整卡只用一种字体后,每行图标都能和同一套字体度量对齐,也不必再为「等宽栈在 Windows 命中 Consolas、Linux 命中 Liberation Mono / DejaVu Sans Mono」这类逐平台差异留补偿常量。email_help 里的 ⚠️ 不在 MiSans 内,回退到宿主字体是可接受的。

字体许可:MiSans 由小米提供,免费商用(含嵌入),许可要求「在软件中注明使用了 MiSans 字体」。本仓库不随附字体文件,请自行从官方页面获取并遵守其许可协议。assets/browserless/ 已加入 .gitignore,不会被提交。

若未放入字体,LOCAL 模式启动出图时会直接报错终止(不静默回退),避免产出缺字体的卡片。

LOCAL 模式所需的文件(文件名固定,缺失即报错):

文件 版本 说明
MiSans-Bold.ttf 4.009 卡片主字体(名称固定)

图标已内嵌在模板里(inline SVG),渲染期不加载任何图标 JS/CSS,因此本地资源只需这一份字体。

入群审批 do 命令只接受裸命令 do accept 或 do reject [单个不含空白的原因词]。do 使用前缀无关的原生 on_regex,因此带全局前缀的 &do(例如 &do accept)不会匹配,多词拒绝原因也不会匹配。

用户信息卡主题

USER_CARD_AUTO_THEME 默认开启:按 Asia/Shanghai 时区,07:00(含)至 19:00(不含)使用浅色,其余时间使用深色,与服务器本地时区无关。设为 false 后,完全恢复被查询账户的 is_dark_mode 偏好。该开关只影响用户信息卡,不修改账户数据,也不影响其他图卡。

请求日志

把项目对外部服务请求的失败(HTTP / MongoDB / SDK)按天追加写入文本文件,方便事后排障。只记录失败:成功的外部查询不落盘,也没有独立的命令操作审计流水——操作者与命令信息只是附加在失败记录上的上下文。日志不影响任何业务结果。

配置(config.yml)

请求日志使用 YAML 配置(config.yml),不是 .env 环境变量:

request_logging:
  enabled: true                      # 是否启用,默认 true
  directory: ./logs/commspt-bot/     # 日志目录,默认 ./logs/commspt-bot/
字段 默认值 说明
enabled true 设为 false 时不创建日志目录、不写任何文件
directory ./logs/commspt-bot/ 日志目录。相对路径按项目根目录解析,也可以填绝对路径
  • 修改配置后需要重启机器人生效(不存在文件监听)。
  • 默认目录 logs/ 已在 .gitignore 中忽略,不会误入库。

文件与格式

  • 每自然日一个文件:<directory>/YYYY-MM-DD.log,日期按 Asia/Shanghai 计算(与服务器本地时区无关)。
  • 时间戳带毫秒与时区,形如 2026-01-02 03:04:05.678 +08:00。
  • 进程启动时向当日文件追加一行启动标记:--- 2026-01-02 03:04:05.678 +08:00 启动 ---(即 --- <详细时间> 启动 ---),因此同一个文件里能区分多次运行。
  • 日切(跨天)只切换到新的目标文件,不碰历史文件。
  • 只做 append:不 truncate、不 delete、不 rotate,也没有自动保留期(retention)。旧文件请自行归档或清理。

这里的「只追加」是代码层的写入行为约定(打开文件即 append 模式),不是操作系统级的防篡改或只追加文件系统保证;有权限的人仍然可以直接改写日志文件。

每条失败记录是单行,字段之间以 | 分隔,空字段省略:

2026-01-02 03:04:05.678 +08:00 | [失败] | 服务=LittleSkin 管理端 API | 方法=GET | 尝试=3/3 | 地址=https://example.com/api/users/123 | 状态=500 | 原因=HTTP 500 响应错误 | 详情=HTTPStatusError: ... | 操作者=群名片 | QQ=10001 | 角色=管理员 | 群=123456 | 命令=&user 123

字段固定为:时间戳、[失败]、服务、方法、尝试=N/M(参与重试时才有)、地址、状态、原因、详情,随后是操作者上下文字段(见下文)。动态文本中的换行等控制字符会被转义,保证一条记录只占一行。

记录范围

覆盖项目对外部服务请求的失败:

  • 外部 HTTP / SDK:玩家与 UID 查询(Yggdrasil、LittleSkin Admin API)、用户信息卡取数、皮肤渲染、版本查询(CSL / Authlib / Liberica Java)、飞书多维表格、云控配置、IP 归属地;
  • MongoDB:QQ↔UID 映射等读写。

不记录:

  • 成功的外部查询与成功的命令执行(没有操作审计日志,只有失败记录);
  • OneBot 协议端的 API 调用失败(发送消息、撤回、禁言、贴表情、审批等)——由协议端自身负责,本项目不采集;
  • 纯本地错误(参数解析失败、本地文件读取失败、模板渲染异常等不涉及外部调用的失败)。

操作者与触发上下文

失败记录会附带事件自带的上下文(拿不到时标注为系统),用于定位是谁触发了这次外部调用:

  • 操作者显示名:取事件自带的 card > nickname > QQ 号兜底,不额外调用任何接口去补全;
  • 操作者 QQ 号与角色(管理员 / 普通用户);
  • 群号;
  • 实际命令(用户实际发出的命令原文,保留 @ 等消息段)。

入群申请(request_type=group)的失败记录字段为:触发=入群申请(主群) 或 触发=入群申请(茶馆)(其他群显示 触发=入群申请(群<群号>))、申请人=<QQ>、群=<群号>、所填UID=<申请留言中的答案>;申请人昵称没有独立字段,可用时体现在上面的 操作者=(同样取事件自带的 card / nickname)。入群通知(group_increase)则记为 触发=入群通知 + 新成员=<QQ> + 群=<群号>。不记录邀请人 / 来源信息。

敏感值脱敏

日志中的 URL 与凭证类敏感值都会脱敏:URL 的 userinfo(user:pass@)、敏感查询参数与 fragment(token / access_token / sign / password 等)、Authorization / Bearer 类头部值、飞书多维表格路径中的 app_token,以及配置中的秘密值(LITTLESKIN_ADMIN_TOKEN、飞书 app_secret / app_token、云控 password、MongoDB 连接串凭证)在任意文本中都会被替换为 ***。脱敏只覆盖日志输出,不改变任何实际请求。

失败与重试

  • 同一次逻辑操作内的失败只落盘一次(嵌套调用不会重复记录);
  • 重试的中间失败只在控制台告警,最终失败才写日志文件;重试后成功则完全不写失败记录;
  • 「查无此人」等业务性结果不算请求失败,不落盘;
  • 日志实现不改变现有重试策略(FAILED_RETRY_TIME 的语义与生效范围保持不变,见上方配置项说明)。
  • 入群申请链路同样遵循上述规则:单个外部调用重试耗尽才落盘一次,其余环节成功则不记录。
HTTP 请求重试

HTTP 重试由 httpx-retries 在传输层统一实现(plugins/commspt_bot/utils/httpx_retry.py),业务代码不再手工包裹重试。

  • 次数:严格取自 FAILED_RETRY_TIME(1 次请求 + N 次重试)。httpx-retries 的 total 语义正是「首次之外的重试次数」,与配置语义一致,无需换算。
  • 范围:主站管理端 API、IP 属地、云控配置、LittleSkin CSL、飞书 API、版本查询(GitHub / authlib-injector / BellSoft)。映射数据库与 Yggdrasil SDK 不走 HTTP 传输层,仍由 call_with_retry 重试。
  • 触发条件:HTTP 429 与全部 5xx;4xx(429 除外)快速失败。httpx-retries 默认只认 429 / 502 / 503 / 504,本项目显式枚举 500–599 以保持原有语义。
  • 间隔:指数退避(首次 1 秒、第二次 2 秒…),单次等待上限 10 秒,累计等待预算 N × 10 秒。关闭抖动以保证行为可预测。
  • Retry-After:服务端给出的等待时间优先于指数退避,但同样受 10 秒上限截断,避免被超大值拖住。
  • 方法:仅重试 GET / HEAD / OPTIONS / PUT / DELETE / TRACE。POST 默认不重试(避免非幂等副作用);飞书写入接口单独放开,因为其 client_token 在整次逻辑请求内固定不变、服务端据此去重。
  • 不重试响应解析:传输层只重发 HTTP 请求,response.json() 与 pydantic 校验在传输层之外只执行一次。因此「响应体截断导致解析失败」不会被重试,这类问题由调用方按业务失败处理。
  • TLS 与 HTTP/2:改由内层 httpx.AsyncHTTPTransport(verify=VERIFY_CONTENT, http2=True) 承担。httpx.AsyncClient 一旦显式传入 transport=,会忽略 verify / http2 / limits / cert,这些参数必须写在 transport 上,否则会静默降级为系统证书 + HTTP/1.1。

写入失败的兜底

日志写入本身失败(目录不可写、磁盘满等)时只在控制台打印一条告警,不影响原业务:业务照常返回结果,不会因为日志失败而报错或中断。

飞书封禁记录(&banlog)

把管理员的封禁操作自动登记到飞书多维表格,便于团队复盘与查询。该功能不与 LittleSkin 数据库联通,UID 由命令直接给出。

命令(管理员,in_preset_cafe 群组可用):

命令 说明
&banlog <UID> <类别...> [备注...] 写入一条封禁记录
&banlog.fields 打印表格字段、类别选项与列名匹配结果(接入时自检用)

书写规则:

  • 第一个参数必须是纯数字 UID;
  • 其后连续属于表格「类别」列选项的 token 依次计入类别(该列是多选,可一次给多个);
  • 从第一个不属于类别的 token 起,剩余全部作为备注;
  • 备注首词恰好与某个类别同名时,用 --note 强制划分:&banlog 43228 账户异常 --note 上传材质 很多。

示例:

&banlog 43228 账户异常 批量上传大量材质
&banlog 43228 昵称 角色名 同时封禁昵称和角色名

写入的列:UID(数字)、类别(多选)、备注(文本)、自动化处理人员(文本,取触发命令的管理员 QQ)。 「处理人」是人员字段,只接受 open_id / user_id / union_id,应用身份无法写入,故该列留空。

类别选项在运行时从表格字段读取(缓存 5 分钟),因此表格改选项后无需改代码。

前置配置

  1. 在 开放平台 创建企业自建应用,取得 App ID / App Secret,填入 .env:

    API_FEISHU__APP_ID=cli_xxxxxxxxxxxxxxxx
    API_FEISHU__APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2. 为应用开通权限 bitable:app(查看、评论、编辑和管理多维表格)。该权限属免审权限,开通即生效。

  3. 发布应用版本。要在多维表格的「添加文档应用」里搜到该应用,必须有已发布的版本,且同时满足:

    • 已开启机器人能力(应用管理 → 添加应用能力 → 机器人);
    • 发布版本的可用范围包含多维表格的所有者。
  4. 打开多维表格 → 右上角 ⋯ → 更多 → 添加文档应用 → 搜索该应用 → 权限设为 可编辑。 ⚠️ 只给「可阅读」时,读接口正常但写入会返回 403 / 91403 Forbidden。

  5. 在 .env 中填写表格标识与列名映射:

    FEISHU_BAN__APP_TOKEN=bascnxxxxxxxxxxxxxxxxxxxxxxxx
    FEISHU_BAN__TABLE_ID=tblxxxxxxxxxxxxxx
    FEISHU_BAN__FIELDS__UID=UID
    FEISHU_BAN__FIELDS__CATEGORY=类别
    FEISHU_BAN__FIELDS__NOTE=备注
    FEISHU_BAN__FIELDS__AUTO_OPERATOR=自动化处理人员

    列名留空则按内置别名自动探测,自动探测结果可用 &banlog.fields 查看。

    若表格在知识库中(链接含 /wiki/),需先用 wiki/v2/spaces/get_node 换取真实的 app_token(obj_token)。

实现要点

  • 凭证缓存:tenant_access_token 在进程内缓存,提前 5 分钟刷新;遇到 99991663 会强制刷新并重试一次。
  • 幂等写入:每次新增记录生成 uuid4 作为 client_token,网络超时后的重试不会在表格中产生重复行。
  • 列名不硬编码:按别名自动匹配表格列名,.env 的 FEISHU_BAN__FIELDS__* 显式指定优先;配置了但表格中不存在的列、或必需字段映射到了只读列,都会被视为缺列并明确报错,不会静默写歪。
  • 只读列保护:公式、查找引用、创建/修改时间、关联字段等系统列在组装请求体时自动跳过。
  • 错误翻译:91403(未加为协作者)、1254302(高级权限未授权)、1254018(字段名不存在)等错误码会翻译成可操作的中文提示。

封禁记录附加展示

当被查询账号处于封禁状态(permission == -1)时,以下位置会额外带上该 UID 在表格中的封禁记录:

位置 触发条件 发送到
&user 查询落到封禁账号 当前群(&user 本身仅限 commspt_group / dev_group)
主群入群审批通知 申请所填 UID 对应账号被封禁 commspt_group

茶馆群入群审批、新成员欢迎通知(发往 notification_channel)不展示封禁记录。

展示格式:

📋 UID 43228 在封禁记录表中有 2 条记录:
· 类别:昵称、角色名 / 备注:批量违规 / 处理人:841968078 / 时间:2026-10-17 20:15
· 类别:账户异常 / 备注:批量上传材质 / 处理人:841968078 / 时间:2026-10-16 11:02

最多展示 5 条,超出部分只提示总数。该信息属于附加内容:飞书接口异常、表格未配置或缺少 UID 列时一律静默跳过,绝不影响 &user 出图或入群审批本身。

入群审批

当入群申请无法自动处理(答案为非纯数字、UID 不存在、Admin API 查询失败等)时,机器人会把申请详情发到管理群(主群 → commspt_group,茶馆群 → littleskin_cafe),并为该通知消息贴上 👍 / 👎 引导表情,供管理员审批。

申请人始终显示事件原始的 user_id;若事件携带整数类型的 invited_id(包括 0),则追加候选提示,例如 申请人 0 (也可能是: 123456)。该提示不要求 user_id 为零,也不会把 invited_id 当作已确认的申请人或用于 QQ/UID 映射。当原始 user_id=0 时,主群与茶馆群均不自动批准,而是发送上述人工审核通知;主群云控自动同意策略也不能绕过此限制。

每个外部依赖(主站管理端 API、映射数据库、云控配置、IP 属地、出图、飞书封禁记录)都独立降级:任一环节失败只影响该环节,且不会再把整条申请笼统报成「主站/映射数据库服务请求失败」。可靠性相关的行为:

  • 重试严格遵循 FAILED_RETRY_TIME(1 次请求 + N 次重试,0 表示不重试),主站查询、映射数据库读写、云控拉取、IP 属地查询均适用;审批动作本身不重试。
  • 申请留言为空(None / 空串 / 纯空白 / 只有 答案:)时转人工审核,不会因为取不到 UID 而丢单。
  • QMail 校验通过即自动同意;若随后映射写入失败,仍照常自动同意,只补发一条纯文本提示,建议用 &setuid 补写映射。
  • 云控拉取失败时不使用任何缓存或历史值,仅跳过云控自动同意路径;QMail 的确定性校验仍然生效。
  • IP 属地查询失败只让通知卡片显示「未获取」,不影响出图与审批。
  • 审批动作前没有任何人为延时。

管理员有两种等价的操作方式,二者共用同一执行核心,同一条申请只会被处理一次:

  1. 文字命令:回复该通知消息,发送 do accept 或 do reject [原因](裸命令,不受全局前缀影响)。
  2. 表情回应:直接对该通知消息贴 👍(批准)或 👎(拒绝)。仅发起审批的管理员本人、且在允许的群聊内有效;Bot 自身贴表情不会触发。

表情审批的处理要点:

  • 凭证按 message_id 登记,弹出即消费,确保每条申请只被处理一次;凭证默认保留 24 小时,期间惰性清理过期条目。
  • 单表情直接判定意图;多种表情并存时调用 get_msg_emoji_likes 复核操作者本人贴的表情,查询失败时按 fail-closed 处理(不审批、保留凭证供重试)。
  • 审批结果在群内 @ 操作管理员播报;成功时撤销通知消息上的 👍/👎 引导表情,执行出错则保留凭证便于重试。
  • 协议端不支持 set_msg_emoji_like / group_msg_emoji_like 时静默降级,不影响 do 命令审批。
  • 自动同意的结果通知为纯文本(不贴 👍/👎、不登记审批凭证),避免对已消费的 flag 二次审批。

3. 运行

uv run nb run                    # 或
uv run python -m nonebot ...     # 由 nb-cli 生成启动脚本

4. 测试

uv run pytest -q

测试覆盖插件加载、Alconna 命令 + 1 个静态问答分发器(服务 commspt_simple_response.json 中的全部问答命令)的注册、命令参数解析(Match / At / 默认值 / 别名)、 原生命令(&mute / &unmute / &recall / &muteall / &unmuteall / do)的规则匹配与参数解析、 群白名单、管理员权限、业务逻辑直连测试(禁言 / 撤回 / 全员禁言的 API 调用参数), 以及入群审批公共执行核心(execute_join_action 的批准/拒绝参数、异常归类)、表情审批凭证索引 (remember_approval / pop_approval 的弹出即消费、过期清理等)与用户信息卡的数据模型与渲染。

简易问答内容配置(开发者)

commspt_simple_response.json 是一个 JSON 对象,键为命令名,值为一条问答条目。

条目字段

字段 类型 说明
aliases string[](可选) 别称命令名,例如 log.csl 的别名 csl.log
reply bool(可选,默认 false) 是否引用回复触发消息
text string(可选) 文本内容,\n 表示换行
images string[](可选) 图片路径数组,相对于 ASSETS_DIR(例如 images/browser.png);渲染时图片在前、文本在后
messages 有序片段数组(可选) 支持多张图片与图文任意交错;与 text/images 互斥

messages 与 text/images 不能同时出现在同一条目中,加载时会跳过冲突条目并记录错误日志。

messages 片段格式

每个片段是一个带 type 的对象:

  • {"type": "text", "content": "..."} 文本片段
  • {"type": "image", "path": "images/xxx.png"} 图片片段(path 相对于 ASSETS_DIR)

片段按数组顺序渲染,单条回复因此可以携带多张图片,也可以任意交错图文。

示例

经典 text + images 条目:

"browser": {
  "images": [
    "images/browser.png"
  ],
  "text": "详见 https://manual.littlesk.in/faq/site#broken-webpage"
}

有序多图条目(messages):

"demo": {
  "messages": [
    {"type": "image", "path": "images/browser.png"},
    {"type": "text",  "content": "网页显示异常请先看手册:"},
    {"type": "image", "path": "images/rtfm.png"}
  ]
}

运行时热重载(&sreload)

编辑 commspt_simple_response.json 后,管理员在群内发送配置前缀对应的 sreload 命令(默认为 &sreload)即可在不重启机器人的情况下让变更生效。支持的变更类型包括:新增命令、删除命令、重命名命令(修改键名或 aliases)、修改回复文本或图片。

热重载失败(文件格式错误、JSON 解析失败等)时,机器人会向管理员报告错误原因,保留原配置继续服务,不会中断现有命令。

注意事项:

  • 键名或别名与 sreload 同名的条目会被跳过并记录警告(sreload 为保留命令名)。
  • 键名或别名与其他已注册命令(Alconna 或原生 OneBot V11 群管命令)同名的条目也会被跳过并记录警告,不会触发双重响应。
  • 热重载是手动操作,不存在文件监听或自动触发机制。

平台支持说明

能力 实现方式 平台范围
命令解析、文本/图片/@/引用 Alconna + UniMessage(UniSeg) 泛平台(任何已支持适配器)
跨群发送 UniMessage.send(Target.group(...)) 泛平台
禁言 / 解除禁言 / 全员禁言 原生 on_message + bot.set_group_ban / set_group_whole_ban OneBot V11
撤回消息 原生 on_message + bot.delete_msg OneBot V11
入群审批(自动/手动) GroupRequestEvent.approve() / set_group_add_request OneBot V11
审批通知表情回应 原生 on_notice + set_msg_emoji_like / get_msg_emoji_likes OneBot V11(需协议端支持 group_msg_emoji_like)

泛平台消息层已就绪;如需接入其它平台,只需在 utils/messenger.py 与相关子插件中 增加对应平台的动作实现。

从 Avilla 版移植的对应关系

Avilla NoneBot + Alconna
@alcommand(Alconna(...)) on_alconna(Alconna(...)) + @matcher.handle()
@dispatcher_from_preset_cafe rule=in_preset_cafe
@dispather_by_admin_only permission=admin_only
ctx: Context / message: Message event: MessageEvent / bot: Bot
Args["x", int | Notice] Args["x", int | At]
ctx.scene.send_message(x, reply=msg) matcher.send(x, reply_to=True)
ctx.scene.into(f"::group({g})").send_message(x) UniMessage(x).send(Target.group(str(g), ...))
Picture(RawResource(b)) UniMessage.image(raw=b)
MuteCapability / RequestCapability bot.set_group_ban / event.approve()
无(NoneBot 新增) 审批通知表情回应:on_notice + set_msg_emoji_like / get_msg_emoji_likes
原生命令(on_command 语义) on_message(rule=native_command_rule(...))(基于 get_plaintext,兼容 & 前缀)+ extract_command_args
S_ = Setting(**yaml.safe_load(...)) S_ = get_plugin_config(Setting)(值来自 .env)+ C_(列表值来自 config.yml)

原版 log_file.py(实验性)以占位形式保留为 commspt_log_file.py。

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages