10 KiB
QQBot Bilibili 卡片解析插件设计
背景
QQ 群内转发 Bilibili 内容时,NapCat/OneBot 上报不一定是普通文本链接。实际消息可能是纯文本、share 链接分享、json 卡片、xml 卡片,或轻应用卡片。当前 QQBot 插件体系已经支持事件插件与 worker 隔离,但还没有一个专门解析 Bilibili 卡片链接的插件。
本次目标是在第三期插件架构内新增一个内置事件插件,不把解析逻辑塞回 QQBot core,也不做命令层转接。
外部依据:
- OneBot v11 消息段包含链接分享、XML 消息和 JSON 消息,链接分享段携带
url/title/content/image字段。 - NapCat 消息格式兼容表中,
share <JSON>、json、lightapp <JSON>都可能作为收到的卡片消息类型。 - Bilibili 视频基本信息可通过
https://api.bilibili.com/x/web-interface/view?bvid=...或aid=...获取,返回title、owner、duration、stat、desc等字段。
目标
- 新增内置插件
bilibili-card,源根为src/modules/qqbot/plugins/bilibili-card。 - 插件以事件方式监听
message,只在账号绑定后生效。 - 从 QQ 文本、链接分享、JSON/XML/轻应用卡片中提取 Bilibili URL。
- 支持直接 BV/av 链接与
b23.tv短链。 - 解析成功后向来源会话回复一条纯文本视频信息摘要。
- 插件代码遵守第三期插件边界,不依赖 Nest、Host 服务实现、
axios、fs或旧 builtins 目录。 - 插件新增或触碰的函数、方法、事件处理器和导出函数都补齐有实际参数语义的 JSDoc。
非目标
- 不做图片渲染卡片。纯文本回复足以先完成稳定解析链路。
- 不新增 QQBot 命令入口,例如
/解析B站。 - 不自动绑定所有账号,启停仍由插件平台的账号绑定控制。
- 不保存用户分享的原始
b23.tv短链,避免回显潜在分享追踪参数。 - 不解析番剧
ep/ss/md、动态、专栏、直播间等非视频对象。
插件结构
新增目录:
src/modules/qqbot/plugins/bilibili-card/
plugin.json
src/
index.ts
application/
bilibili-card-application.ts
config/
bilibili-card-config.ts
domain/
bilibili-card.types.ts
bilibili-reply-formatter.ts
bilibili-url-extractor.ts
bilibili-url-parser.ts
events/
message/
bilibili-card-message.handler.ts
infrastructure/
integration/
bilibili-card-host.ts
bilibili-video-client.ts
职责划分:
plugin.json是插件 key、事件、权限、运行时预算和配置项的唯一 manifest 来源。index.ts只创建插件实例,适配 worker 通用 host,不写业务分支。events/message只负责把 worker 的message事件交给 application。application负责绑定判断、去重、解析编排、调用 Bilibili client、发送回复和 warn。domain只放纯解析、格式化、类型与去重 key 计算,不触碰网络和平台 host。infrastructure/integration只通过插件 host 调用 HTTP、重定向解析、发消息和配置读取。
Manifest 与权限
插件 key 使用 bilibili-card。
事件定义:
{
"key": "bilibili-card.message",
"name": "Bilibili 卡片解析",
"eventName": "message",
"handlerName": "handleMessage",
"description": "解析 QQ 中的 Bilibili 视频链接卡片并回复视频摘要。"
}
权限:
qqbot.event.receive:接收 message 事件。qqbot.send:发送解析结果。runtime.http:请求 Bilibili 接口与短链重定向。plugin.config.read:读取超时、去重和文本截断配置。
运行时预算:
timeoutMs: 10000。maxConcurrency: 1。
配置项:
QQBOT_BILIBILI_CARD_HTTP_TIMEOUT_MS: 默认6000,限制 Bilibili 接口请求。QQBOT_BILIBILI_CARD_MAX_REDIRECTS: 默认5,限制短链跳转层数。QQBOT_BILIBILI_CARD_DEDUPE_TTL_MS: 默认600000,同会话同链接 10 分钟去重。QQBOT_BILIBILI_CARD_DESC_MAX_LENGTH: 默认80,限制简介回复长度。
消息提取流程
插件收到 QqbotNormalizedMessage 后按顺序处理:
- 如果
message.selfId === message.userId,直接忽略,避免机器人解析自己的回复。 - 通过 host 查询当前
selfId绑定的事件插件列表;没有绑定bilibili-card时直接返回。 - 从以下来源收集字符串候选:
message.messageTextmessage.rawMessagerawEvent.message数组中的text.data.textshare.data.url/title/contentjson.data.data解析后的嵌套字段xml.data.data原文中的 URLlightapp.data.data解析后的嵌套字段rawEvent中键名包含url、jumpUrl、qqdocurl、sourceUrl的字符串字段
- 从候选字符串中提取 URL,统一去除 HTML 实体残留、尾随标点和 QQ 卡片包裹字符。
- 只保留
bilibili.com、m.bilibili.com、www.bilibili.com、b23.tv域名。 - 对 URL 去重,并只处理第一条可解析为视频的 URL。
现有 core 的 extractMessageText 只拼接 text 段,本插件必须读取 rawEvent,不能依赖 core 改成卡片感知。
URL 解析与短链处理
直接链接解析规则:
/video/BV...提取bvid。/video/av...提取aid。- URL query、hash、尾随斜杠和卡片转义字符不影响解析。
短链解析规则:
- 如果
b23.tv路径本身包含 BV 或 av,直接解析。 - 如果
b23.tv是随机短码,插件调用 host 的resolveRedirect。 resolveRedirect返回最终 URL 和跳转链,不返回 body,不暴露底层 HTTP client。resolveRedirect只接受http:和https:,最多跳转配置指定次数。- 最终 URL 必须仍属于允许域名集合,否则插件忽略该消息并记录 warn。
为支持短链,需要在插件平台增加通用 host 能力:
resolveRedirect(input: {
url: string;
maxRedirects?: number;
timeoutMs?: number;
}): Promise<{ finalUrl: string; redirects: string[] }>;
该能力属于平台通用 HTTP 能力,不包含 Bilibili 业务判断。插件侧负责判断域名与视频 ID。
Bilibili 接口访问
BilibiliVideoClient 只通过 host 访问:
- BV 链接请求
https://api.bilibili.com/x/web-interface/view?bvid=<bvid>。 - av 链接请求
https://api.bilibili.com/x/web-interface/view?aid=<aid>。 - 请求超时使用
QQBOT_BILIBILI_CARD_HTTP_TIMEOUT_MS。 - 只接受
code === 0且data.bvid存在的响应。 -400、-403、-404、62002、62004、62012等错误只记录 warn,不在群内刷失败消息。
回复格式
成功回复纯文本:
Bilibili 视频解析
标题:<title>
UP:<owner.name>
时长:<mm:ss 或 hh:mm:ss>
播放:<view> 弹幕:<danmaku> 点赞:<like>
链接:https://www.bilibili.com/video/<bvid>
简介:<截断后的 desc>
规则:
- 缺失字段显示为
未知或省略简介行。 - 数字按中文阅读习惯做轻量格式化,例如
1.2万。 - 简介去掉多余换行,长度由配置限制。
- 不回显原始短链。
去重与降噪
去重 key:
<selfId>:<messageType>:<targetId>:<normalizedVideoId>
行为:
- 命中去重 TTL 时不请求 Bilibili 接口,也不发送回复。
- 每条消息最多回复一次。
- 网络失败、接口失败和非视频链接都不写入去重,避免短暂失败后无法重试。
数据与管理面
SQL seed 增加 qqbot_plugin、qqbot_plugin_version、qqbot_plugin_installation、qqbot_plugin_event_handler 对应记录,使新库能在插件平台页面看到 bilibili-card。
不新增 qqbot_command 或 qqbot_plugin_operation 记录,因为本插件没有命令 operation。
Admin 现有插件平台页面应能通过 manifest/seed 展示插件、事件能力和账号绑定;本次不新增专门页面。
错误处理
- 消息结构异常:忽略并 warn,不能抛出到 worker 队列导致事件分发失败。
- JSON 卡片解析失败:继续使用原始字符串抽 URL。
- XML 卡片解析失败:按普通字符串抽 URL。
- 短链跳转超过上限:warn 并忽略。
- 跳转到非允许域名:warn 并忽略。
- Bilibili 接口错误:warn 并忽略。
- 发消息失败:warn,不重试,避免重复回复。
测试策略
TDD 顺序:
- URL 提取测试先失败:覆盖文本链接、
share段、json卡片、xml卡片、b23.tv短链和非 B 站链接。 - URL 解析测试先失败:覆盖 BV、av、尾随标点、query/hash、非法域名。
- 应用测试先失败:未绑定不处理、绑定后解析成功发送摘要、self message 忽略、同会话同视频去重。
- Bilibili client 测试先失败:
code === 0成功、错误 code warn、不接受缺失bvid的响应。 - host
resolveRedirect测试先失败:跟随 302、相对 Location、超过上限、非法协议。 - 架构门禁测试更新:新插件必须有
plugin.json、src/index.ts,不能导入 Nest、axios、fs、旧 builtins 或 host 内部实现。 - SQL/manifest 测试更新:内置插件列表包含
bilibili-card,seed/verify 能检查插件存在。
聚焦验证命令:
pnpm exec jest --runTestsByPath test/modules/qqbot/plugins/bilibili-card/bilibili-url-extractor.spec.ts test/modules/qqbot/plugins/bilibili-card/bilibili-card-application.spec.ts test/modules/qqbot/plugin-platform/plugin-host-bridge.spec.ts --runInBand
pnpm exec jest --runTestsByPath test/modules/qqbot/plugins/plugin-platform-migration.spec.ts test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts --runInBand
pnpm run typecheck
git diff --check
验收标准
- 本地单测覆盖所有解析分支并通过。
pnpm run typecheck通过。git diff --check通过。- 新插件可被插件平台发现并显示事件能力。
- 线上部署后,测试账号绑定
bilibili-card,向绑定会话发送 Bilibili QQ 卡片或链接,Bot 回复视频摘要。 - 发送普通非 B 站链接不会回复。
- 发送同一个视频不会在 TTL 内重复刷屏。