docs: 设计QQBot Bilibili卡片解析插件

This commit is contained in:
sunlei 2026-06-19 15:43:17 +08:00
parent da1ba28bf9
commit 55476c04f2

View File

@ -0,0 +1,245 @@
# 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`、动态、专栏、直播间等非视频对象。
## 插件结构
新增目录:
```text
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`
事件定义:
```json
{
"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` 后按顺序处理:
1. 如果 `message.selfId === message.userId`,直接忽略,避免机器人解析自己的回复。
2. 通过 host 查询当前 `selfId` 绑定的事件插件列表;没有绑定 `bilibili-card` 时直接返回。
3. 从以下来源收集字符串候选:
- `message.messageText`
- `message.rawMessage`
- `rawEvent.message` 数组中的 `text.data.text`
- `share.data.url/title/content`
- `json.data.data` 解析后的嵌套字段
- `xml.data.data` 原文中的 URL
- `lightapp.data.data` 解析后的嵌套字段
- `rawEvent` 中键名包含 `url`、`jumpUrl`、`qqdocurl`、`sourceUrl` 的字符串字段
4. 从候选字符串中提取 URL统一去除 HTML 实体残留、尾随标点和 QQ 卡片包裹字符。
5. 只保留 `bilibili.com`、`m.bilibili.com`、`www.bilibili.com`、`b23.tv` 域名。
6. 对 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 能力:
```ts
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不在群内刷失败消息。
## 回复格式
成功回复纯文本:
```text
Bilibili 视频解析
标题:<title>
UP<owner.name>
时长:<mm:ss hh:mm:ss>
播放:<view> 弹幕:<danmaku> 点赞:<like>
链接https://www.bilibili.com/video/<bvid>
简介:<截断后的 desc>
```
规则:
- 缺失字段显示为 `未知` 或省略简介行。
- 数字按中文阅读习惯做轻量格式化,例如 `1.2万`
- 简介去掉多余换行,长度由配置限制。
- 不回显原始短链。
## 去重与降噪
去重 key
```text
<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 顺序:
1. URL 提取测试先失败:覆盖文本链接、`share` 段、`json` 卡片、`xml` 卡片、`b23.tv` 短链和非 B 站链接。
2. URL 解析测试先失败:覆盖 BV、av、尾随标点、query/hash、非法域名。
3. 应用测试先失败未绑定不处理、绑定后解析成功发送摘要、self message 忽略、同会话同视频去重。
4. Bilibili client 测试先失败:`code === 0` 成功、错误 code warn、不接受缺失 `bvid` 的响应。
5. host `resolveRedirect` 测试先失败:跟随 302、相对 Location、超过上限、非法协议。
6. 架构门禁测试更新:新插件必须有 `plugin.json`、`src/index.ts`,不能导入 Nest、axios、fs、旧 builtins 或 host 内部实现。
7. SQL/manifest 测试更新:内置插件列表包含 `bilibili-card`seed/verify 能检查插件存在。
聚焦验证命令:
```powershell
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 内重复刷屏。