Skip to content
dsh.fish
Bundle

dsh-plugin-qqbridge-plus

QQbridge plus — 基于 DeepSeek Harness 与 qq-bridge 的插件:以水鱼(舞萌DX)查分为主(QQ 内 #查分 查 B50、出带曲绘的成绩图,OAuth 授权、不限管理员、不依赖桥接),另附聊天拓展功能(余额与自动关机、推理档位按 DeepSeek 计费峰谷自动切换、节假日表覆盖检查与自动维护、开机/关机提示词、#余额/#关机 管理命令、#功能 开关、#帮助)。判定逻辑直接复用 qq-bridge 的纯模块,不重复实现。

Source
xueyingneko
License
MIT
Updated
Updated 7 days ago

Readme

# qqbridge-plus —— 舞萌DX 查分 + 聊天拓展

**基于 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 与 [qq-bridge](https://github.com/Derpyu520/qq-bridge) 的插件:以水鱼(舞萌DX)查分为主,另附一些聊天拓展功能。**

拆开说:

- **水鱼查分** —— 在 QQ 里 `#查分` 查舞萌DX 成绩、出 B50 成绩图(带曲绘)。
  走 OAuth 授权,不限管理员,也**不依赖桥接**:桥接没装好,查分照样能用。
- **聊天拓展** —— 把 qq-bridge 的运维能力接进 AI 的工具面:余额与自动关机、
  推理档位随计费峰谷自动切换、节假日表、开机/关机提示词、`#功能` 开关。

> 本代码由 AI 生成。非常感谢 [Derpyu520](https://github.com/Derpyu520) 开发了
> [qq-bridge](https://github.com/Derpyu520/qq-bridge),没有它这个插件没有任何意义。

[English](README.en.md) | 中文

---

## 装它能得到什么

装好以后,你可以直接问 AI"机器人余额多少""现在什么状态",不用再去翻日志;
群里的人也能发 `#查分` 查舞萌DX 成绩、拿到一张带曲绘的 B50 成绩图。
管理员在 QQ 里发一句 `#功能 balance off` 就能关掉某项查询能力,改完立刻生效,
不用编辑文件也不用重启。

具体包括:

| 查询 | 内容 |
| --- | --- |
| 运行状态 | 当前模式、DSH 是否就绪、白名单、管理员、每个会话的未读和唤醒情况 |
| 余额 | 余额多少、数据多久前更新的、告警阈值、真要关机还有多久 |
| 推理档位 | 现在用的是哪一档。分时段功能会在计费高峰切到便宜档、空闲切回好档,省点钱 |
| 节假日表 | 表还够不够用;缺年份的时候可以顺手去国务院通知里抓一份来试算 |
| 开机 / 关机提示词 | 哪些人格配了、会发到哪里 |
| 管理命令 | `#余额` `#关机` `#功能` `#帮助` 的触发词和权限规则 |
| 舞萌DX 查分 | 在 QQ 里 `#查分` 查 B50、出成绩图(带曲绘);不依赖桥接,也不限管理员 |
| 桥接配置 | 配置内容,令牌已经打码 |

---

## 插件内部是怎么走的

计费峰谷、节假日解析、唤醒过滤、余额告警这些判定,插件里一个都没有重写,
全是直接 `import` qq-bridge 自己的纯模块。插件只负责取数、拼文本。

```
qqbridge-plus  ──import──▶  qq-bridge/src/wake-filters.mjs   (计费峰谷 / 节假日 / 唤醒过滤 / 余额决策)
               ──import──▶  qq-bridge/src/whale-balance.js    (余额账本读取)
               ──HTTP────▶  qq-bridge 控制台 127.0.0.1:3100   (运行态:模式、会话、生效档位)
               ◀──HTTP────  qq-bridge 转发来的 QQ 管理命令
```

这么做是因为 qq-bridge 才是 QQ 侧的权威:SnowLuma 连接、白名单、会话映射、计时器
都在它进程里。判定逻辑再写一份出来,两份迟早会对不上。开发时真撞上过一次:
余额读到 `NaN` 的时候,一边判成 `recovered`、另一边判成 `none`,
而这一步会直接决定要不要关机。功能键的清单同理,桥接那边不另存一份,
否则插件加了功能、桥接反而先把合法命令拒掉。

桥接没在跑的时候插件不会报错,工具会告诉你"控制台不可达"以及怎么排查。

---

## 怎么装

### 1. 前提

| 需要 | 说明 |
| --- | --- |
| Node.js ≥ 18 | DSH 与 qq-bridge 都要求它 |
| git | 用于克隆本仓库(手动安装可以不装,直接下 zip 也行) |
| [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | 插件宿主。**必须先启动过一次**,否则 profile 目录还不存在 |
| [qq-bridge](https://github.com/Derpyu520/qq-bridge) | 本插件直接 `import` 它的纯模块并调它的端点,**必须先装好** |

> qq-bridge 的位置不用提前告诉安装器:它默认去插件目录的隔壁找 `qq-bridge`,
> 找不到时用 `--bridge-dir` 指定。

### 2. 安装

#### 方式 A:自动安装(推荐)

**先干跑看一眼它要改什么**(第一次建议这么做):

```bat
node "%USERPROFILE%\dsh-plugins\qqbridge-plus\scripts\install.mjs" ^
     --dir "%USERPROFILE%\dsh-plugins\qqbridge-plus" --dry-run
```

确认没问题就去掉 `--dry-run`。完整流程(克隆 + 安装):

**Windows(cmd 或 PowerShell)**

```bat
git clone https://github.com/xueyingneko/qqbridge-plus.git "%USERPROFILE%\dsh-plugins\qqbridge-plus"
node "%USERPROFILE%\dsh-plugins\qqbridge-plus\scripts\install.mjs" --dir "%USERPROFILE%\dsh-plugins\qqbridge-plus"
```

**Linux / macOS / Git Bash**

```bash
git clone https://github.com/xueyingneko/qqbridge-plus.git ~/dsh-plugins/qqbridge-plus
node ~/dsh-plugins/qqbridge-plus/scripts/install.mjs --dir ~/dsh-plugins/qqbridge-plus
```

**qq-bridge 不在隔壁**时:

```bat
node "...\scripts\install.mjs" --dir "...\qqbridge-plus" --bridge-dir "D:\path\to\qq-bridge"
```

**要装进 web profile 而不是 desktop**:加 `--profile web`。

**已经在仓库目录里**,也可以直接用包装脚本(它会自动认出当前目录):

```bat
scripts\install.cmd --dry-run
```

安装器做的事,每一步都幂等,重复跑不会重复写入:

1. 克隆仓库,或对已有仓库执行 `git pull`;
2. 在插件目录装唯一的 npm 依赖 `@deepseek-ai/schemastery`;
3. 把包注册进 profile 的 `package.json`:`dependencies` 加 `link:` 项,`dsh.profile.bundles` 加包名;
4. 往 profile 的 `cordis.patch.yml` 追加插件 entry(已经存在就跳过);
5. 让包管理器在 profile 里建好 `node_modules` 链接(优先 pnpm,以匹配 profile 自己的 lockfile);
6. 检查 qq-bridge 是否就位,并打印后续步骤。

改 profile 文件之前会先备份成 `package.json.bak-<时间戳>`,写入用临时文件加 rename,
避免写一半被别的进程读到。

**卸载**:从 profile 的 `package.json` 里删掉 `dependencies` 与 `dsh.profile.bundles` 中的
`dsh-plugin-qqbridge-plus`,再删掉 `cordis.patch.yml` 里带
`# === dsh-plugin-qqbridge-plus (managed by install.mjs) ===` 标记的那条 entry,重启 DSH。

#### 方式 B:手动安装

要做精细控制(例如自己管软链接、或多 profile 同时装)就手动来。

**第一步,加入 profile**:把包加进 `~/.dsh/profiles/<profile>/package.json` 的
`dependencies`(用 `link:` 指向本仓库),同时加进 `dsh.profile.bundles`:

```json
{
  "dependencies": {
    "dsh-plugin-qqbridge-plus": "link:/abs/path/to/qqbridge-plus"
  },
  "dsh": {
    "profile": {
      "bundles": ["...", "dsh-plugin-qqbridge-plus"]
    }
  }
}
```

然后在 profile 目录跑一次 `pnpm install`(或 `npm install`)把链接建出来。

**第二步,在 profile 的 `cordis.patch.yml` 加一条 entry**:

```yaml
- id: qqbridge-plus
  name: dsh-plugin-qqbridge-plus
  config:
    bridgeDir: '/abs/path/to/qq-bridge'   # ← 改成你的 qq-bridge 路径
    consoleBase: 'http://127.0.0.1:3100'
    personaSection: true
    sectionOrder: 7
    features:
      status: true
      balance: true
      schedule: true
      holidays: true
      greeting: true
      commands: true
      config: true
```

`id` 必须写 `qqbridge-plus`(DSH 用 entry id 定位设置的命名空间),`name` 必须写包名。

第二步不能省。包内自带的 `cordis.patch.yml` 只描述"这个 bundle 有哪些行",
真正让 loader 建出 entry 的是 profile 层的 patch。只做第一步的话,
插件管理器会报 `installed=true`、行配置也齐全,但工具根本不存在,而且没有任何报错。

**第三步,重启 DSH。**

### 3. 验证装上了

重启 DSH 之后,任选一种确认:

- 问 AI"机器人状态",它应该能调到 `qqbridge` 工具;
- 或看桥接目录下的 `state/plugin-apply.json`,里面有本次装配的 `trace`:

  ```json
  { "trace": [ { "phase": "apply-called" }, { "phase": "tool-registered" },
               { "phase": "http-api-registered" }, { "phase": "apply-returned" } ] }
  ```

  只到 `apply-called` 就没了,说明装配中途失败;完全没有这个文件,说明插件没被加载。

### 4. 让 QQ 侧也能改开关(可选)

在 qq-bridge 的 `config.json` 里加:

```json
"featureCommand": {
  "triggers": ["#功能", "#开关"],
  "baseUrl": "http://127.0.0.1:19387"
},
"helpCommand": { "triggers": ["#帮助", "#help"] }
```

`baseUrl` 指向 DSH web,插件端点挂在它上面。改完重启 qq-bridge。

---

## 在 QQ 里使用

### 管理命令(仅管理员)

```
#帮助                     列出全部管理命令(触发词按当前配置渲染)
#功能                     查看全部开关(标 * 的是在 QQ 里改过的)
#功能 balance off         关闭某个功能(也接受 关 / 禁用 / off / 0)
#功能 balance 开          开启(也接受 启用 / on / 1)
#功能 reset               清空运行时覆盖,回到配置文件的值
```

- 触发词可配(`featureCommand.triggers` / `helpCommand.triggers`)。
- 帮助文本里的触发词是从配置读出来拼的,所以改过触发词之后它也会跟着变,
  不会给你一份对不上号的说明。
- 只有管理员(qq-bridge 的 `ownerQQ`)能用;没配 `ownerQQ` 时这些命令对谁都不可用。

### 查分(所有人)

```
#查分                          这台机器上查分能不能用
#查分 柚子                     直接写用户名 = 查公开 B50
#查分 b50 柚子                 同上,显式写子操作
#查分 b50                      查我自己(需先完成绑定)
#查分 b50 qq 12345678          查指定 QQ(对方需先绑定)
#查分 song 11823               查某首歌的定数
#查分 search ztn               模糊搜曲,支持简写
#查分 bind                     发起绑定,授权链接**私聊**发你
#查分 confirm 123456           用确认码收尾绑定
```

- **不限管理员**——谁都能查自己的成绩。但"查自己的"必须先完成一次绑定,
  而绑定要求**你本人**在水鱼页面上点「同意授权」。
- 触发词可配(`maimaiCommand.triggers`,默认 `#查分` / `#maimai`)。
- 句首的全角空格、零宽字符会被自动清掉——它们肉眼看不出,却足以让 `#查分` 匹配失败。
- `bind` / `confirm` 的链接与确认码**只走私聊**,群里最多留一句"已私发"。
  这不是多余的谨慎:链接谁都能转发,确认码只出现在点同意那个人的浏览器里。

---

## AI 能调什么

一个工具 `qqbridge`,10 个 action:

| action | 作用 |
| --- | --- |
| `status` | 总览:模式 / 就绪 / 管理员 / 白名单 / 余额 / 档位 / 各会话 |
| `balance` | 余额、观测新鲜度、阈值、自动关机倒计时 |
| `schedule` | 计费峰谷与档位;`testAt=[时间戳…]` 可试算任意时刻 |
| `holidays` | 节假日表覆盖;`fetch=true` 现场抓取解析试算(不落盘) |
| `greeting` | 开机 / 关机提示词配置、目标、各人格卡覆盖 |
| `commands` | `#余额` `#关机` 触发词与权限口径 |
| `config` | 桥接配置摘要(令牌已脱敏);`key="role.balanceAlarm"` 取子段 |
| `features` | 功能开关:查看,或用 `set`/`enabled` 修改,或 `reset` |
| `maimai` | 舞萌DX 查分(见下一节) |
| `firstRun` | 重看首次运行引导 |

---

## 舞萌DX 查分

接入[水鱼查分器](https://diving-fish.com)。这部分**与 qq-bridge 无关**——桥接没装好也能用。

```
qqbridge  action=maimai, sub=status                    这台机器上查分能不能用
qqbridge  action=maimai, sub=b50, username=某人        公开查询(对方需自行公开成绩)
qqbridge  action=maimai, sub=b50, qq=<QQ号>            查自己的(需先完成 OAuth 绑定)
qqbridge  action=maimai, sub=song, songId=11823        查某首歌的定数
qqbridge  action=maimai, sub=search, query=ztn         模糊搜曲(支持简写)
qqbridge  action=maimai, sub=bind, qq=<QQ号>           发起绑定,拿到授权链接
qqbridge  action=maimai, sub=confirm, qq=<QQ号>, code=<确认码>   用确认码收尾绑定
```

`sub=b50` 默认会**出一张成绩图**(1080 宽 PNG),写在系统临时目录下并把路径交给你;
不想要图就加 `image=false`。

### 为什么不支持"按 QQ 号随便查"

水鱼已经弃用开发者 token(`DIVINGFISH_TOKEN`)。原因是它**能按 QQ 号读取任意用户的成绩**,
而那些用户从未对机器人授权、也无法撤销——用它做出来的"查分",本质是一个无需同意就能
窥探他人成绩的工具。所以本插件**只实现 OAuth**:

| 环节 | 做法 |
| --- | --- |
| 机器人持有的凭据 | 只有你自己申请的应用 `clientId` / `clientSecret` |
| QQ 号 | 只以 `sha256("<clientId>:<QQ号>")` 摘要外发,**号码本身不离开机器人** |
| 用户令牌 | 只在**内存**缓存(5 分钟,提前 30 秒失效),**不落盘** |
| 绑定 | 用户发 `bind` 拿到链接 → 本人点「同意授权」→ 把页面给的**一次性确认码**发回来 |
| 撤销 | 用户随时可在 https://auth.diving-fish.com/apps 撤销 |
| 未绑定用户 | 仍可用公开查询(按用户名) |

那个确认码不是多余的手续:绑定链接谁都能转发,而确认码只出现在**点同意那个人的浏览器**里。
少了它,别人可以拿你的 `clientId` 造一条链接发给受害者、骗对方授权从而绑上别人的账号。

### 配置

```yaml
maimai:
  clientId: '你的应用 ID'          # https://auth.diving-fish.com/apps 申请
  clientSecret: '你的应用密钥'
  proxy: false                     # 境外服务器可开
```

申请应用时,**接入方式选「bot / 工具」**(设备码绑定 + 换票,无需回调地址)——这正是本插件的流程。
部署形态选「由你自己部署运行」(机密客户端,用 `client_secret`);

**需要勾的权限只有三项**(全部只读):

| 权限 | 为什么需要 |
| --- | --- |
| `profile` | 账号基本信息 |
| `prober.profile.read` | Rating、姓名框等汇总资料 |
| `prober.records.read` | **B50 的逐条成绩记录**(少这项查询会 403) |

**不要勾任何 `write` 权限**——本插件没有任何写入路径。而且水鱼会因此把申请转人工审核,
只会拖慢自己。`chunithm.*`(中二节奏)也与本插件无关。

留空时插件不会静默失败,而是明确告诉你去申请(`sub=status` 会报告当前状态)。

### 成绩图依赖 `@napi-rs/canvas`

**安装器会自动装上**(第 2 步),装完还会**实测渲染一张 PNG** 才报成功——不只看"命令返回 0"。
之所以要实测:它是原生模块(N-API 预编译),在少数平台/架构上可能装到却加载失败,
只报"install 成功"会让人以为出图可用,直到真去查分时才发现。

手动装也一样:

```bash
cd <插件目录> && npm install
```

万一它没装上(或平台不支持),**插件其余部分照常可用**——查分、搜曲、开关都不受影响,
只是不出图,`sub=status` 会明确告诉你去装什么。这是刻意的:**画图是附加价值,
不该让主功能一起挂掉**。

> 依赖只有这一个。`@deepseek-ai/schemastery`(Config 校验用)**刻意不列为依赖**:
> 它由 DSH 宿主提供,插件代码会用多路径回退去宿主 profile 里找,找不到还能降级成
> `Config = undefined`(跳过校验),本机实测两条路都能跑。把它列成依赖会连带拉进
> cosmokit 等一整套,安装从数十秒变成数分钟,而且**万一它被装进某个与宿主共享的目录,
> 还可能干扰宿主依赖树**(这个坑真踩过)。

### 曲库搜索

搜索用子序列匹配,所以简写也能命中:`ztn` → `Zitronectar`。排序按匹配质量
(完全相等 > 前缀 > 包含 > 艺人 > 子序列),查询短于 3 个字符时不走子序列兜底,避免误报。

### 关于曲绘

成绩图的歌曲封面是 **SEGA 的游戏素材**,本仓库是 MIT,**不能把它打包进来**。所以这里的
做法是:**素材由你自己准备,插件只负责读**。没配就没有封面,卡片退回"曲名推导出的稳定色条"
(同一首歌颜色恒定),功能不受影响。

配好之后效果:一屏 50 张卡的封面。实测对真实 B50 命中 **49/50**。

#### 怎么弄到曲绘

用一个命令从公开的国服曲数据库同步到本机:

```
#查分 sync            下载到 maimai.jacketDir 指定的目录
```

```
=== 曲绘同步 ===
目录:E:/qqbridge-plus/jackets
曲数据库共 1293 首,本地已有 0 张
本轮下载 52 张(3.1 MB)
还差 1241 张,再执行一次即可继续(增量,已下载的不会重下)。
(GitHub 接口额度 6/60,每小时重置)
```

> **那个曲数据库没有声明许可**(未声明即保留所有权利)。所以我既没有把它打包进本仓库,
> 也没有替你分发——**下载动作由你自己的机器发起、文件落在你自己的磁盘上**,本插件只是读取方。
> 需要商用或再分发时,请自行确认素材授权。
> 来源:<https://github.com/CrazyKidCN/maimaiDX-CN-songs-database>

**要有耐心**:GitHub 未认证接口**每小时只有 60 次请求**,而曲数据库有 1293 首。一次下不完,
所以同步是**增量可续**的——反复执行即可,已下载的不会重下。想让它自己慢慢下完,可以挂
常驻进程(它会自己按额度重置时间等待,而不是空转):

```bash
node scripts/sync-jackets.mjs --dir E:/qqbridge-plus/jackets
```

> 两个实测过的坑,都写进代码注释了:① `raw.githubusercontent.com` 在部分网络下恒返回
> 502,所以走 `api.github.com`;② Node 直连 GitHub 可能报 `UNABLE_TO_VERIFY_LEAF_SIGNATURE`
> (系统证书库里有一层 Node 自带 CA 集不认的链),插件会在同步时**自动合并系统 CA**,
> 无需加命令行参数。

#### 目录布局

文件名含**曲目 id** 最可靠(`11576.png`);也认 `by-title/<曲名>.png`(同步器用的就是这种)。
匹配**不区分大小写**——曲数据库里同时存在 `Trust` 与 `TRUST`,在 Windows 上它们是同一个
文件、在 Linux 上是两个。

---

## 开关

每个功能一个布尔开关,`false` 表示关闭,支持 `'*'` 兜底,默认全开。

生效优先级:

```
QQ / 运行时覆盖文件  >  组合行配置  >  代码默认(全开)
```

也就是"在 QQ 里改过的"压过"配置里写的"。覆盖写在 `qq-bridge/state/plugin-features.json`,
用临时文件加 rename 的方式原子写入,避免另一个进程读到半个 JSON;DSH 重启后仍然保留,
`#功能 reset` 可以清空。状态文件损坏时按"没有覆盖"处理并记录原因,不会让插件起不来。

八个键,默认全开:

| 键 | 对应能力 |
| --- | --- |
| `status` | 总览 |
| `balance` | 余额与自动关机 |
| `schedule` | 计费峰谷与推理档位 |
| `holidays` | 节假日表 |
| `greeting` | 开机/关机提示词 |
| `commands` | 管理命令现状 |
| `config` | 桥接配置摘要 |
| `maimai` | 舞萌DX 查分 |

### 关闭之后的表现

- action 仍然留在枚举里,调用时返回一句明确的话,而不是把 action 删掉让人猜:

  ```
  ⛔ 功能「余额与自动关机」已在插件配置里关闭。
  当前开启的功能:status(总览)、schedule(计费峰谷与推理档位)…
  ```

  不从枚举里删掉的原因:工具 schema 在 `defineTool()` 时就序列化好了,之后无法随配置增删。
  与其造成"配置改了但枚举没变"的假象,不如让关闭状态看得出来。

- `status` 里对应的段落会写明原因:`⛔ 已关闭(features.balance=false)`。
- 系统提示词按开关生成,已关闭的功能不会出现在里面,免得模型白白尝试。
- 开关名拼错会告警,并写进 `plugin-apply.json` 的 trace。这类开关最容易出的问题就是
  把 `schedule` 写成 `scheduler`,看起来"设了却没生效"。

---

## 三条硬规则

都是开发时踩出来的,改代码前建议先读一遍。

### 1. `inject` 里要列出所有会被访问的服务

Cordis 规定访问未在 `inject` 中声明的服务属性会直接抛错:

```
Error: cannot get property "systemPrompt" without inject
```

最初只声明了 `tools`,想用 `if (ctx.systemPrompt)` 做可选依赖兜底,
结果读取属性这一步就抛错了,兜底代码根本执行不到。而这类错误在插件管理器里
只表现为 `[failed]` 或 "did not activate",不显示原因。

### 2. 不要导出 `Config`,除非它是真正的 schemastery schema

loader 看到 `Config` 导出会拿它做校验,并且期望它来自 `@deepseek-ai/schemastery`。
给一个普通对象会在 apply 之前就失败(现象是 entry 建出来了、apply 一行没跑)。
而 schemastery 是裸包名依赖,插件目录如果没自己的 `node_modules` 就解析不到,
整个模块会在导入期加载失败。

还有一条实测出来的边界:`volatile` 字段会让 loader 把配置求值成 `{}`。
DSH 设置页要可写就要求字段带 `volatile`,但实测(原始证据留在 `lib/last-apply-config.json`)
loader 求值后所有带 volatile 的字段都变成了 `{}`,于是 `sectionOrder` 不再是数字,
`order must be a finite number` 直接让插件不出现;就算绕过这一点,
在设置页点"保存"也会把配置写坏。所以本插件的设置面板做成只读,
改开关交给 QQ 命令和配置文件。测试里有一条断言守着这件事。

### 3. `defineTool` 要用模块顶层的静态导入

`lib/index.js` 里用的是写死绝对路径的静态导入,和官方插件同一种做法。
最初为了"稳妥"改成运行时动态 `import()` 加 7 条回退路径,结果出现了最难查的一种失败:
某条路径报了 import 成功,但链上的 `.then` 永远不执行——不报错也不进 catch——
工具就这么一直没注册上。

纯 node 子进程读不到 `app.asar` 内部,所以这个模块没法在普通 node 里导入。
这是 Electron 的 asar 特性,不是缺陷;测试请用 `test/` 下不导入该文件的用例。

**排查用的自检**:装配过程会写 `qq-bridge/state/plugin-apply.json`,里面的 `trace` 数组
记录了走到哪一步。宿主 console 在 GUI 里看不到,loader 启动失败又不给原因,
这个文件是唯一不用猜的判据。

---

## 版本配套

插件和 qq-bridge 属于跨仓库依赖。qq-bridge 启动时会报出两边的版本:

```
[bridge] 版本:qq-bridge 0.2.0
[bridge]       插件:dsh-plugin-qqbridge-plus 1.1.1
```

两边都只以各自 `package.json` 的 `version` 为准,不另外维护一份。
这条也是踩出来的:曾在一个模块里硬写了版本常量,和 `package.json` 对不上,
等于没有可信的版本号。

---

## 测试

```bash
npm test                                             # 插件侧全部 7 套:190 项
npm run test:maimai                                   # 只跑舞萌查分的 4 套:99 项
node ../qq-bridge/scripts/test-feature-command.mjs   # 桥接侧纯函数:28 项
node ../qq-bridge/scripts/e2e-feature-command.mjs    # 端到端:10 项(需两端都在运行)
```

CI 只跑不依赖 qq-bridge 的那几套,需要桥接在跑的两套留在本地跑——
写一个必然失败的 CI 不如没有 CI。

舞萌查分的测试用**依赖注入**拦住了网络,所以不需要任何凭据就能跑;
成绩图那套还会在"没装画图库"时自动改测降级路径(7 项),装了就测完整路径(21 项)。

几条关键的安全与正确性断言:

| 断言 | 为什么重要 |
| --- | --- |
| `config` 摘要不得含未脱敏的 `accessToken` | 模型上下文会长期保存 |
| 拼错的开关键不得关掉任何功能 | "设了没生效"最难查 |
| 任何字段都不得带 `volatile` | 会让 loader 把配置求值成 `{}`,保存即损坏 |
| 端点无令牌时必须拒绝或不注册 | 它能放宽整个工具面 |
| `#功能xyz` 不得被当成命令 | 误判会吞掉普通发言,表现为"机器人忽然不理人" |
| 源码里不得出现 `developer-token` | 它能按 QQ 号读任意人成绩,用户无法撤销 |
| 绑定请求必须带 `handoff=code`,且请求体不含明文 QQ 号 | 少了确认码就能被转发链接骗授权 |
| 展示打码、`client_secret` 不得出现在任何输出里 | 输出会进模型上下文与 QQ 聊天记录 |
| 出图路由必须挡掉 `../` 与非 `.png` | 否则变成任意文件读取 |
| 出图失败不得让查分本身失败 | 图是附加价值 |

---

## 许可

[MIT](LICENSE)。第三方组件与合规边界(含 SnowLuma 许可说明、数据出机清单)见 LICENSE 后半部分。

Install

dsh plugin --profile web add github:xueyingneko/qqbridge-plus

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source