Skip to content

Releases: cheesehaqi/dsh-qq-onebot-bridge

v0.8.3 — 让控制台自己会体检

Choose a tag to compare

@cheesehaqi cheesehaqi released this 04 Nov 01:08

一句话

把「能不能更新、端口通不通、配置长什么样、删掉的东西去哪了」变成页面上能看见的事实。

全部在 control/ 与 test/,lib/ 一行未动 —— 对打补丁的人零重锚成本。

体检

  • 版本更新检查(关于页):读 GitHub 最新 release 与本地版本比,10 分钟缓存;网络不通如实说「查不到」,不假装已是最新
  • 端口占用(网络配置):onebot11 里出现的端口逐个查 netstat + tasklist,标出「监听中(进程名)」或「未监听」
  • 控制台自身(关于页):版本 / 端口 / PID / 已运行分钟 / Node / 平台
  • napcat.json 只读预览(系统配置):只回顶层键值、值截断 80 字,命中 token/secret/password/cookie/key/auth 一律「(已隐藏)」

看得到才管得住

  • 插件目录条目显示 [config] [pages] 标记;NapCat 离线也能看(原来失败分支直接 return)
  • 文件管理:文件数 / 目录数 / 合计体积,随筛选变化
  • 关于页:显示数据目录,可一键复制

回收站(不硬删)

  • GET /api/accounts/trash 列出 config/_trash/<时间戳>/<文件>
  • 还原回 config/,重名不覆盖(加 .restored-<时间>)
  • 「送系统回收站」用 Shell InvokeVerb(delete);失败就报错,绝不回退到硬删

少点几下

  • 复制诊断摘要(登录状态 / 账号 / 账号数 / WebUI / 最近错误)
  • 网络未保存提示、复制连接地址
  • 实时测试「只看失败」+ 事件过滤(traceId / 阶段 / 原因 / 模块),可叠加
  • Ctrl+K 面板新增「检查更新」「复制诊断摘要」

修复

  • 场景注入的群号/QQ 号一直被丢弃:页面发扁平字段,服务端只从 params 取 → 已改
  • 场景下拉一直是空的:loadScenarios() 没在打开分区时调用;场景名取错字段(title→name)
  • loadPlugins 失败分支过早 return,导致 NapCat 离线时看不到插件目录

读数

72 套 / 4329 断言全绿;元素时序 138 个 id 全就位,40 个前端 /api 调用全部有实现。

v0.8.2 — 少点几下,且能被测试抓住

Choose a tag to compare

@cheesehaqi cheesehaqi released this 03 Nov 01:21

一句话

一批「能少点几下」的小功能,外加两条用真实事故反向验证过的守卫。

全部在 control/ 与 test/,lib/ 一行未动 —— 对打补丁的人零重锚成本。

查找与筛选

  • 文件管理:按文件名过滤(支持 *.json 通配),徽标显示「命中/总数」
  • 账号配置:按 QQ 号过滤;标题旁显示「当前自动登录」的号
  • 运行日志:时间范围下拉(5 分钟 / 1 小时 / 24 小时);下载跟随当前筛选

记录与导出

  • 操作记录:导出 .txt、清空(二次确认)
  • 最近一次错误卡片:从最近 200 条 trace 捞 warn/error,显示时间 · 级别 · 阶段 · 原因,可一键跳到日志
  • 关于页:导出诊断包(脱敏)

键盘与偏好

  • Ctrl+K 命令面板:11 个分区跳转 + 每个账号的快捷跳转(自动按号过滤)+ 5 个常用动作;↑↓ 选择、Enter 执行、Esc 关闭
  • 日志筛选偏好持久化:级别 / 关键词 / 时间范围 / 折叠 / 只看 Info 写 localStorage,刷新自动恢复

插件目录

  • 显示 plugins/ 一级结构,条目可点:点一下把完整路径复制到剪贴板(不开文件、不读内容)

两条新守卫(都有反向验证)

  1. DOM 时序断言:$('id') 引用的元素必须在 <script> 之前出现。
    事故原型:命令面板 HTML 放到 </script> 之后 → $('palette') 为 null → 初始化中断 → LOADERS TDZ,整页停在「正在读配置」。
  2. 运行时冒烟:内联脚本放进 vm + Proxy DOM 桩真跑一遍,抓「语法对但一跑就崩」的问题。
    事故原型:keyword 是局部 const,偏好代码直接读 → ReferenceError。

读数

72 套 / 4329 断言全绿(新增 1 套 accounts-runtime-unit)。

v0.8.1 — 让控制台能自证

Choose a tag to compare

@cheesehaqi cheesehaqi released this 01 Nov 15:42

一句话

把「已经写进磁盘但看不到、管不了」的东西补上:备份、操作记录、日志高亮、诊断包,以及网络配置的服务器段。

这一版全部在 control/,lib/ 一行未动 —— 对打补丁的人零重锚成本。

新增

  • 配置备份管理:GET /api/accounts/backups + …/restore + …/trash。页面在「文件管理」下列出 config/*.bak-*;还原前先把当前文件另存 .pre-restore-<时间>(还原也可撤销),删除是移到 config/_trash/<时间戳>/,不是真删
  • 操作记录持久化:写 localStorage(最多 200 条),刷新页面不再清空
  • 运行日志:关键词命中处高亮(先转义再包 <mark>);新增「只看 Info 以上」,一键藏掉 DEBUG
  • 导出诊断包(脱敏):「关于」页按钮,走 /api/export?redact=1,QQ 号掩码、消息只留长度,可直接发人
  • WS 服务器 / HTTP 服务器段可编辑:名称 / 端口 / 监听地址 / 启停 / 新建,接口 …/network/servers、…/network/http

界面整理

  • 「新建」入口统一到顶部按钮(弹窗选类型 + 账号),去掉散落的「新建 X」卡
  • 「全部」视图不再为每个空段渲染卡片;一条连接都没有的账号收成一张小卡
  • 文案统一成「新建」(页面里「新增」0 处)

修复

  • 空段的 else 分支在「全部」下仍执行 → 出现「共 0 个」空卡
  • 导出按钮只加了 DOM 未绑事件(死按钮),已补绑定并验证 redact=1

护栏

写配置一律:校验(端口 1–65535、≤4 条、名称 ≤40 字)→ 写前 .bak 备份 → 写失败不落盘;「保存全部」逐账号各自备份。

测试

71 套 / 4325 断言全绿。

v0.8.0 — 本地账号中心

Choose a tag to compare

@cheesehaqi cheesehaqi released this 01 Nov 11:45

一句话

把「登录、账号、网络、插件、日志」从各自一条命令,变成一个本地页面(control/,127.0.0.1:8799/accounts)。

新增

  • 账号中心 11 个分区:基础信息 / 账号配置 / 网络配置 / 文件管理 / 插件管理 / 运行日志 / 实时测试 / 运行与设备 / 系统配置 / 操作记录 / 关于
  • 可写能力(都带护栏):网络配置增删改与启停(整页「保存全部」+ .bak 备份)、webui.json 端口与回环地址、插件启停/卸载
  • 实时测试:单发一次 / 即时调试(注入后跟 15 秒),事件可点开看完整 trace 链路
  • 运行日志:SSE 实时流 + 历史、级别筛选、折叠超长行、终端二维码折叠并内联显示真图
  • 主题(跟随/亮/暗)、hash 直达、页面热读取(改 HTML 刷新即生效)

修复

  • assistantText 在 content 非数组时崩溃(reading filter),会吞掉整条模型回复 —— trace 里出现过 43 次
  • 页面级故障 6 处:内联脚本语法、绑定空元素、LOADERS TDZ、右侧大留白、日志重复二维码、账号表登录状态不刷新
  • 控制台宿主端口写死 3080 → 改为从 netstat + tasklist 自动识别
  • 隐私:/api/napcat/webui 曾明文返回 NapCat token → 改为裸地址 + /api/napcat/open 302(token 只在服务端拼接)

移除

  • 「登录 / 扫码」分区(交给 NapCat 自己的页面)、「插件商店」分区(改为直达 NapCat 商店)、「设备与版本」(并入「运行与设备」)

测试

71 套 / 4325 断言全绿;新增 7 套(账号、网络配置编辑、页面自检等)。

v0.7.10 — 能力体检(只读探针)+ 两处测试口径修正

Choose a tag to compare

@cheesehaqi cheesehaqi released this 31 Oct 12:36

一、/能力体检:把"被动记账"补成"主动体检"

0.7.9 的能力矩阵只回答"哪个动作失败过",回答不了"这功能是真不支持,还是我还没用到"。
这一版加了 21 个只读探针 + 一个命令:

  • 探针清单(lib/conformance.js):get_status、get_version_info、get_login_info、群/好友列表、
    群信息、成员列表/详情、陌生人信息、群荣誉、精华列表、群公告(读)、群文件目录、群历史消息、
    @全体余量、禁言名单、群打卡名册、群相册列表、群系统消息、被忽略通知、可疑好友申请(读);
  • 分类:ok / unsupported / not-found(retcode 1404)/ bad-params(100)/ permission / timeout / error;
    1404 刻意不判成"不支持"(也可能是目标群/成员不存在);
  • 落点:/能力体检(群里仅管理员)、trace(stage: capability)、面板「高级与调试」状态行、
    knownUnsupported 集合(让后续提示更准);
  • 文档:docs/COMPAT.md 中英把"为什么不做主动探针"改写成"只做只读体检"。

安全边界是代码级的:探针清单一律 get_* / _get_*,并且 assertProbeSafety() 会拒绝任何命中
send_ / set_ / delete_ / upload_ / mark_ / kick … 前缀的条目 —— 将来若有人手滑加一条写操作,
体检会直接拒跑并在 trace 里说明。写操作永远只靠被动账本回答。

重锚提示:lib/onebot.js 新增 callRaw(socket, action, params)(纯新增,紧邻 currentSocket()),
走的是原有 #call,账本/超时/dry-run 全保留;sendSegments 本次未再改动。

二、两处测试口径修正(来自第三方 fork 的实测)

  • test/inject-guard-unit.mjs:7 处用 followups.includes(text)(数组全等)反推会话身份。
    只要宿主在正文前面加一行(例如每轮现算的时钟行),身份就找不到 → records.get('') 为 undefined → 整套从第一条断言崩。
    改成后缀匹配 followupEndsWith():容忍前缀,且不会把 注入的消息 认成 注入的消息二
    (.some(t => t.includes(text)) 反而会假通过)。另补 3 条回归断言。
  • test/privacy-unit.mjs:npm pack 会把 .gitignore 剔除,所以 npm 包形态的副本里"运行产物被忽略"这条必然假红。
    改为分两种:git 检出里缺 .gitignore 仍照红(真 bug);非 git 副本里缺则显式跳过并写明原因。

套数 64 → 65,断言 4190 → 4224。面板不变(11 组 / 84 行 / 87 键)。


v0.7.9 — 收尾两件:换桥会怎样(能力矩阵)+ 防线三(出站脱敏)

Choose a tag to compare

@cheesehaqi cheesehaqi released this 29 Oct 15:58

0.7 这条线讲的是"实验性与扩展",收口收在两个把隐含假设写出来的地方。

一、能力矩阵:换 OneBot 实现之后,哪些功能会失效

起因是一个很实际的问题:"我要是换个桥(NapCat → LLOneBot / Lagrange / 自研),这些功能是不是全废了?"
查了一遍才发现插件一共调 71 个 OneBot action,其中只有 32 个是 v11 标准动作,另外 39 个是实现扩展 ——
而在此之前,换实现后只能"用到某功能才炸",且失败信息埋在 trace 里没人翻。

  • 静态分级(lib/capability.js):standard 32 / extension 31 / napcat 4,每条扩展动作配一句人话功能名;
  • 实现识别:连上后调一次 get_version_info(纯查询),认出 NapCat / LLOneBot / Lagrange / Shamrock / go-cqhttp / 未知;
  • 被动记账:onebot.js 唯一出口 #call 上挂账本 —— 成功计数、失败记次数与最后一条错误(不记参数,避免把消息内容带进统计);
  • 不许静默:某个 action 首次失败写一条 warn trace,并直接给判断:
    「动作失败(首次):上传到群相册(upload_to_qun_album,napcat)—— 大概率是当前实现(LLOneBot)不支持」;
    标准动作失败则提示「先查连接/权限/协议版本」;
  • 落点:QQ 里发 /health 多两行;面板「高级与调试」组状态行;新增 docs/COMPAT.md(中英)
    给出三层分级表 + 换实现检查清单。

为什么不做"主动探针"(挨个 action 试一遍):71 个里有一半会真的改变 QQ 状态(禁言、踢人、设精华…),
探针等于拿真实群试错。所以只做两件安全的事:读 get_version_info + 记账。

二、防线三:出站脱敏(默认关)

入站有敏感词过滤,注入有 turn 级 dry-run,污点有窗口 —— 但发出去之前一个字符都不查。
群里问"你配置怎么写的",模型贴出 sk-… / ghp_… / 整段私钥,就直接进群了。

  • 内置 7 条:sk-…、gh*_…、AKIA…、xox…-、JWT、Bearer …、整段 PRIVATE KEY;
  • 明确不碰:QQ 号、手机号、URL、普通长串(机器人本来就要发这些,误伤成本高于收益);
  • 打码形态:sk-abc…56(已隐藏 36 字符)——保留首尾,能认出是哪把 key,但拿不到值;
  • 两种动作:outboundRedactBlock=false(默认)打码后照发;true 整条不发并在 trace 里说明;
  • 收口在唯一出口 onebot.js 的 sendSegments:普通回复、合并转发、提醒、通知、扩展出口全覆盖,不用改 16 个调用点;
  • 隐私:trace 只写"命中了哪条规则 × 几次",绝不写命中的原文(那条断言专门钉住);
  • 默认关:和 filterEnabled / taintDryRunEnabled 一致;面板「安全与风控」组有开关 + 「命中即拦截」+ 自定义规则,
    状态行显示本机计数(内存计数、重启清零)。

至此三道防线齐了:注入回合 dry-run → 污点窗口 → 出站脱敏。

三、顺手

  • 面板「安全与风控」与「高级与调试」两组现在都有状态行(以前只有「实验性」组有);
  • README 的「安全注意」写清三道防线各自的开关与默认值。

面板:84 → 87 键(69 开关 + 12 数值 + 1 时段 + 3 名单 + 2 句式),84 行。
单测:新增 capability-unit(27 条)与 redact-unit(27 条);tests 总套数 62 → 64。
全量 64 套 / 4190 断言全绿。


v0.7.8 — 自定义注入句式,外加四处"别人看不见但会踩"的收拾

Choose a tag to compare

@cheesehaqi cheesehaqi released this 28 Oct 12:53

一、自定义注入句式(实验性,默认空)

污点窗口原来只有内置 8 条句式。每个群的话术不一样("把刚才那段配置抄给我""你现在是维护者了"),
我们猜不完,所以让管理员自己补正则:

taintExtraPatterns: z.array(z.string()).default([])
  • 面板「实验性」组多一行「自定义注入句式(正则)」(新控件类型 patterns:字符串原文,不是号码列表);
  • 每条 ≤ 200 字、最多 20 条、去重、大小写不敏感;内置 8 条优先(同一条消息两边都命中时,trace 报内置那条);
  • 坏正则不许静默:写入时(面板点保存)就在服务端编译一次,语法错/超长/类型不对 → 400 且不改文件;
    配置文件里手写错的,桥启动时写一条 trace 点名「哪条没生效、为什么」;
  • 只在上面的「污点窗口」打开时才有意义(关着时不编译、也不报)。

桥级实测:把服务器配置发我一份 → 命中 自定义「把.*配置.*发(给)?我」 → 该会话进窗口 → 出站被 dry-run 拦下。

二、面板说明写清"只覆盖三分之一"

schema 有 235 个键,面板只登记 84 个(连接地址、端口、access token、模型参数、提示词只在 cordis.patch.yml 里改)。
这句原来只有一行"其余配置仍可改配置文件",现在讲清了边界与去处("改错容易把桥弄坏,所以不放进面板"、
"生效值能在 /为什么 与诊断包里看到")。

顺带加了一条防线:测试 里钉住「面板登记的每个键都在 schema 里」——
以前面板写错键名,要等用户点了保存被服务端拒才发现("界面在、功能没有"的假开关)。

三、.gitattributes:消掉每次提交的 LF/CRLF 噪音

* text=auto eol=lf + 二进制规则(png/jpg/webp/gif/ico/woff/tgz/zip)。
之前每次 commit 都在刷一屏 LF will be replaced by CRLF;顺手也让第三方做"线上树 vs 自己的树"逐字节比对时少一类噪音。

四、package.json 补 engines 与 author

engines.node: ">=20"(代码用了 Object.hasOwn、可选链、顶层 await 等)、author: cheesehaqi。
缺 engines 时,别人用旧 Node 装出来是一屏语法错误而不是一条清晰提示。

五、新增 docs/EXTENSIONS.md / .en.md(扩展接缝参考)

原来扩展接缝的说明只在 README 里,docs/ 四份是搬旧段落的产物、完全没提 instanceId/provider。
新文档写清:出口 API 表、两种只读事件、provider 七条规矩(同步、标准 JSON Schema、状态放模块级、注册随实例消亡、
只能新增、16/4 与 40/12 上限、抛错带栈帧)、污点 source、会影响到第三方的配置键、社区参考实现。

顺手修正的措辞

punctuation: false 的语义被 issue #4 作者实测纠正:只关标点搜索、不关分批(5000 字 + 上限 1700 → 仍是
1700/1700/1600 三批)。面板提示、CHANGELOG、README 中英全部改成「关掉后仍然会按单条上限切成多条」,不再写"退回"。

面板:83 → 84 键(67 开关 + 12 数值 + 1 时段 + 3 名单 + 1 句式)。
单测:taint-unit 31 → 37 条(自定义句式 6 条)、settings-unit 288 → 293 条(面板↔schema 契约 3 条 + 句式写入 5 条)。
全量 62 套 / 4135 断言全绿。


v0.7.7 — 让"按标点断句"变成一个配置键

Choose a tag to compare

@cheesehaqi cheesehaqi released this 28 Oct 12:53

v0.7.6 给了 splitSegments(…, { punctuation }),但我们自己的调用点没传 ——
所以第三方想用这个开关,仍得在 #replyTo 里改一行。这版把它变成配置键,调用点直接读:

longReplySplitOnPunctuation: z.boolean().default(true)
  • 面板「对话基础」组多一行「长回复按标点断开」(默认开):关掉后仍然会按单条上限切成多条
    (分批不受影响),只是每批不再往回找标点、变成等长硬切;
  • 只有超过单条上限(默认 1700 字)的长回复才会触发分批,普通对话看不到区别;
  • 调用点改成 splitSegments(segments, limit, { punctuation: this.config.longReplySplitOnPunctuation !== false }),
    用 !== false 而不是 === true,保证老配置(没这个键)仍然是"按标点断"。

这是给 issue #4 作者的最后一块拼图:他原来要改我们的函数签名 + 调用点两处,现在两处都不用改,
他自己那个开关的语义和我们的默认值一致(按标点断),所以他甚至可以彻底删掉这条补丁。

面板:82 → 83 键(67 开关 + 12 数值 + 1 时段 + 3 名单)。
单测:taint-unit 29 → 31 条(源码级断言:调用点真的读这个键、schema 默认 true)。
全量 62 套 / 4120 断言全绿。


v0.7.6 — 给补丁作者收尾的三件小事

Choose a tag to compare

@cheesehaqi cheesehaqi released this 28 Oct 12:53

来源:issue #4 作者跟到 0.7.5 之后的回帖。他那边删掉了两条重复实现、只剩一个开关,这版把那一个开关也收进上游。

一、splitSegments(segments, limit, options):加 punctuation 开关

他的 config.splitLongReplies 需要一个"关掉按标点断句"的入口,但我们的函数签名不给他位置传,
只能自己改我们的函数——这是他为 0.7.5 保留的最后一条编辑。现在上游支持:

splitSegments(segments, limit)                          // 默认:句末 → 句内 → 硬切
splitSegments(segments, limit, { punctuation: false })  // 退回旧的纯字符硬切

默认行为不变(不传第三个参数时完全一致),所以他自己那条编辑可以删了。

二、README 社区链接跟到最新 tag,并写死"链接约定"

patches/0.7.4/ext → patches/0.7.5/ext。同时把作者确认过的约定写进 README,免得以后每次都要问:

分支永远叫 patches/<我们的 tag>、ext/ 永远在分支根目录;分支里是 1 个提交还是十几个都不影响这个地址。

三、约定:会让人重锚的改动,CHANGELOG 里点一句

他的补丁库靠"在 pristine 的某处唯一字符串上替换"定位,lib/bridge.js 顶部 import 块一动锚点就没了
(0.7.5 加 randomUUID 那次他就被迫重锚了一条)。所以他只提了一个要求:这类改动在 CHANGELOG 里点一句就行,不用迁就他。
这条已经写进 README 的「关于 fork」小节,以后我们照做。

单测:taint-unit 27 → 29 条(punctuation: false 退回硬切、默认行为不变)。
全量 62 套 / 4118 断言全绿。


v0.7.5 — 第三方要的五件事

Choose a tag to compare

@cheesehaqi cheesehaqi released this 28 Oct 12:53

来源:issue #4 作者第二次回帖(他把 0.7.4 的补丁重新锚定完,报了四条实测 + 一个提议)。
这版全部是增量,不动现有形状。

一、api.instanceId:桥实例标识

宿主重建桥(重启 / 热重载 / 面板点开关)后出口会换人,第三方原来只能靠 api.toolProviders() 前缀 + 5 秒轮询猜,
作者实测两个毛病:轮询窗口里可能连着看到两次相同快照(误判还在),两次轮询之间换得够快则根本看不到中间态。
现在 ctx.qqBridge.instanceId 是每实例一个随机串(randomUUID),直接比即可;extensionReport().instanceId 同步暴露。
纯增量,不影响任何现有字段。

二、provider 出问题不再静默

作者的包写的是 catch { return { tools: [], sections: [], reason } } —— 我们原来只读 tools/sections,
于是"包挂了"表现为注册成功但零工具,面板和 trace 全空,比抛错难查得多。两处改:

  • 返回对象里的 reason 会被记进 notes(进 trace),空贡献时也能看出是谁在报错;
  • provider 抛错时记下调用栈首帧(跳过 node:internal),trace 与面板状态行里都能看到 包名→抛错:x @ body.mjs:42:9。
    为此新增纯函数 stackFrameOf(),单测覆盖。

三、污点事件带 data.source

作者提的「同名不同源」:他自己的污点层是"这个会话刚读过外部内容",我们这套是"入站文本命中注入句式",
两边都往 trace 写 taint,混起来得先分辨是谁打的。现在本插件的污点事件一律带
data.source = 'inbound-injection'(常量 TAINT_SOURCE_INBOUND),第三方可写自己的来源值。

四、断句补上「句内标点」二级回退

中文群聊里「在吗,帮我看下这个报错是咋回事,我这边已经试过重启了」这种长句整句只有一个逗号,
只按句末断会把一大坨塞进同一条气泡。现在:先句末(窗口 60%)→ 再句内(窗口 50%,,,、))」』::)→ 最后硬切。
(这条原本是作者自己补丁里的实现;我们把行为收进上游后,他那三条编辑就可以删掉了。)

五、README 加「第三方扩展(社区)」

指路到作者那套 ext/ 包(四个包 / 20 个工具,按我们的 tag 维护),注明由作者本人维护、不在本项目支持范围,
并且链接指目录不指文件(他改实现时文件名会动)。

单测:extension-unit 51 → 57 条(instanceId 两个、provider reason、抛错栈帧),
taint-unit 25 → 27 条(data.source、句内标点回退)。全量 62 套 / 4116 断言全绿。