koishi-plugin-qq-github-bridge 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -2
  3. package/client/env.d.ts +5 -0
  4. package/client/index.ts +6 -0
  5. package/client/page.vue +164 -0
  6. package/client/tsconfig.json +15 -0
  7. package/dist/index.js +1 -0
  8. package/dist/style.css +1 -0
  9. package/docs/ARCHITECTURE.md +100 -0
  10. package/docs/RELEASING.md +68 -0
  11. package/docs/RESEARCH.md +37 -0
  12. package/docs/REVIEW.md +49 -0
  13. package/docs/TESTING.md +92 -0
  14. package/lib/config.d.ts +35 -0
  15. package/lib/config.js +99 -0
  16. package/lib/config.js.map +1 -0
  17. package/lib/console.d.ts +7 -0
  18. package/lib/console.js +73 -0
  19. package/lib/console.js.map +1 -0
  20. package/lib/database/models.d.ts +37 -0
  21. package/lib/database/models.js +21 -0
  22. package/lib/database/models.js.map +1 -0
  23. package/lib/handlers/commands.d.ts +28 -0
  24. package/lib/handlers/commands.js +184 -0
  25. package/lib/handlers/commands.js.map +1 -0
  26. package/lib/handlers/message.d.ts +4 -0
  27. package/lib/handlers/message.js +58 -0
  28. package/lib/handlers/message.js.map +1 -0
  29. package/lib/index.d.ts +7 -0
  30. package/lib/index.js +44 -0
  31. package/lib/index.js.map +1 -0
  32. package/lib/rpc.d.ts +8 -0
  33. package/lib/rpc.js +3 -0
  34. package/lib/rpc.js.map +1 -0
  35. package/lib/services/github.d.ts +22 -0
  36. package/lib/services/github.js +201 -0
  37. package/lib/services/github.js.map +1 -0
  38. package/lib/services/permission.d.ts +12 -0
  39. package/lib/services/permission.js +56 -0
  40. package/lib/services/permission.js.map +1 -0
  41. package/lib/services/repository.d.ts +8 -0
  42. package/lib/services/repository.js +22 -0
  43. package/lib/services/repository.js.map +1 -0
  44. package/lib/services/storage.d.ts +13 -0
  45. package/lib/services/storage.js +75 -0
  46. package/lib/services/storage.js.map +1 -0
  47. package/lib/types.d.ts +93 -0
  48. package/lib/types.js +3 -0
  49. package/lib/types.js.map +1 -0
  50. package/lib/utils/errors.d.ts +8 -0
  51. package/lib/utils/errors.js +28 -0
  52. package/lib/utils/errors.js.map +1 -0
  53. package/lib/utils/lock.d.ts +11 -0
  54. package/lib/utils/lock.js +38 -0
  55. package/lib/utils/lock.js.map +1 -0
  56. package/lib/utils/parser.d.ts +9 -0
  57. package/lib/utils/parser.js +87 -0
  58. package/lib/utils/parser.js.map +1 -0
  59. package/package.json +100 -4
  60. package/src/config.ts +111 -0
  61. package/src/console.ts +65 -0
  62. package/src/database/models.ts +45 -0
  63. package/src/handlers/commands.ts +177 -0
  64. package/src/handlers/message.ts +60 -0
  65. package/src/index.ts +37 -0
  66. package/src/rpc.ts +10 -0
  67. package/src/services/github.ts +189 -0
  68. package/src/services/permission.ts +47 -0
  69. package/src/services/repository.ts +22 -0
  70. package/src/services/storage.ts +67 -0
  71. package/src/types.ts +75 -0
  72. package/src/utils/errors.ts +18 -0
  73. package/src/utils/lock.ts +30 -0
  74. package/src/utils/parser.ts +71 -0
@@ -0,0 +1,37 @@
1
+ # 官方接口调研记录
2
+
3
+ 核对日期:2026-10-25。实现以当次实际安装的包及其类型声明为准;下列官方网页用于确认设计与接口语义。
4
+
5
+ ## Koishi
6
+
7
+ - [官方仓库](https://github.com/koishijs/koishi):读取 README、package.json 和 Session 源码,确认 TypeScript 插件生态、工作区与消息表示。
8
+ - [开发环境](https://koishi.chat/zh-CN/guide/develop/setup.html):使用独立 npm 包交付,插件运行要求明确设为 Node >=22。
9
+ - [Schema](https://koishi.chat/zh-CN/guide/plugin/schema.html):导出 `Config`,分组 Schema、secret Token 与中文说明。
10
+ - [事件系统](https://koishi.chat/zh-CN/guide/basic/events.html)、[中间件](https://koishi.chat/zh-CN/guide/basic/middleware.html):前置中间件接收命令,普通聊天向下游传递。
11
+ - [服务与依赖](https://koishi.chat/zh-CN/guide/plugin/service.html)、[生命周期](https://koishi.chat/zh-CN/guide/plugin/lifecycle.html):声明必需服务,动态注入控制台,在 dispose 清理额外副作用。
12
+ - [数据库](https://koishi.chat/zh-CN/guide/database/)、[模型](https://koishi.chat/zh-CN/guide/database/model.html):`model.extend`、显式主键和数据库 CRUD,不要求外部 Redis / 队列。
13
+ - [扩展控制台](https://koishi.chat/zh-CN/guide/console/)、[数据交互与鉴权](https://koishi.chat/zh-CN/guide/console/data.html):`addEntry`、`addListener`、客户端 `ctx.page` 和 `send`。
14
+ - [官方控制台仓库](https://github.com/koishijs/webui):读取 auth 与 client 构建源码,并核对 console 已发布版本的实现。
15
+ - [发布要求](https://koishi.chat/zh-CN/guide/develop/publish.html):符合 `koishi-plugin-*` 名称、语义版本、Koishi peer dependency 和发布文件组织。
16
+
17
+ 实际依赖:Koishi 4.18.11、console/client 5.30.11、auth 4.1.7、SQLite 4.7.0、HTTP 0.6.3。锁文件固定本次安装结果。
18
+
19
+ ## 通过实际源码和运行发现的差异
20
+
21
+ 1. 当前 HTTP 是 fetch 风格的 Cordis 服务,`ctx.http(url, options)` 返回含 `status` / `data` / `Headers` 的响应;不能照搬旧版 axios 错误对象处理。
22
+ 2. Satori 在分发时将旧 `message` 事件规范化为 `message-created`。两者均接纳,原始 OneBot 消息类型仍必须为群消息。
23
+ 3. OneBot 6.0.2 使用 `session.setInternal('onebot', payload)` 暴露 `session.onebot`,昵称、ID 与群号从真实适配器字段读取。测试调用了该版本的真实 `adaptSession`。
24
+ 4. console 的 `addListener` 只修改 listeners 字典,不自动随插件卸载;实现显式清理,并通过禁用 auth 的集成测试验证。
25
+ 5. client 5.30.11 的 `./global` exports 指向未随包发布的文件;不依赖该文件,而是从安装的官方 TS/Vue 源码生成类型声明供插件客户端严格检查。
26
+ 6. 生产控制台只允许特定位置的资源(包括 node_modules)。独立消费者测试使用实际 npm 安装的 `.tgz`;源码预览通过官方 devMode。
27
+ 7. 官方 OneBot 6.0.2 npm 包标记了 `official support for onebot has been dropped`。因此记录的是已发布适配器的兼容性验证,不将其描述为仍由官方维护的最新方案。
28
+
29
+ ## GitHub 与正则
30
+
31
+ - [Issue comments](https://docs.github.com/en/rest/issues/comments#create-an-issue-comment):Issue 和 PR 普通评论共用端点。
32
+ - [Issues](https://docs.github.com/en/rest/issues/issues#update-an-issue)、[Pull requests](https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request):分别更新状态,PR 需额外处理 merged。
33
+ - [REST 最佳实践](https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api):串行请求、写入间隔、限流头与退避。为严格仓库绑定与凭据隔离,本插件对重定向要求部署者修改绑定,而不自动跟随。
34
+ - [PAT 官方说明](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens):Fine-grained 单资源所有者限制、仓库选择与组织策略。
35
+ - [Google RE2 WASM](https://github.com/google/re2-wasm):使用无灾难性回溯的引擎;以限制语法换取任意群消息输入下的可预测匹配时间。
36
+
37
+ 未使用第三方博客作为 API 真实性依据。调研未获取任何用户真实 GitHub Token、QQ 登录状态或生产配置。
package/docs/REVIEW.md ADDED
@@ -0,0 +1,49 @@
1
+ # 第一版需求复核
2
+
3
+ 复核日期:2026-10-26(Asia/Shanghai)。依据用户提供的开发任务、当前源码、实际安装依赖及官方文档。
4
+
5
+ ## 结论
6
+
7
+ 核心实现符合第一版功能与架构要求,具备作为独立 Koishi 插件公开发布的基础。未发现阻止源码公开的明显功能缺口或普通用户越权路径。完整验收尚未完成:真实 QQ / NapCat 消息投递、真实 GitHub 评论与状态变更未实际执行,不能声称生产环境全部验证通过。
8
+
9
+ 原始交付缺少 Git 版本管理、仓库 / 维护者 npm 元数据和自动发布 CI。本次补齐公开仓库元数据、版本管理、跨平台 CI、发布包加载检查、标签发布工作流和维护者发布文档。
10
+
11
+ ## 按需求检查
12
+
13
+ | 需求 | 源码与自动化结论 | 主要证据 |
14
+ | --- | --- | --- |
15
+ | 独立 TypeScript 插件及生态元数据 | 符合;Koishi peer dependency、Schema、生命周期与 CJS 入口齐全 | package.json、src/index.ts、src/config.ts |
16
+ | 单 Token、多仓库、群绑定 | 符合;目标只从绑定解析,禁止重定向突破绑定 | services/repository.ts、services/github.ts、bridge / runtime 测试 |
17
+ | OneBot QQ 群真实身份检查 | 符合;核对原始事件与 Session 的 QQ / 群 / 消息标识,过滤私聊、匿名、自身和非文本 | handlers/message.ts、runtime 测试 |
18
+ | Issue / PR 普通评论与正文保留 | 符合;共用 issue-comments 接口,固定转义身份头与原正文分开 | services/github.ts、bridge 测试 |
19
+ | 自定义前缀 / 完整正则 | 符合;RE2、命名组、长度和全文匹配校验,特权指令优先 | utils/parser.ts、parser 测试 |
20
+ | 独立 QQ 管理员、递归授权与超级管理员撤权 | 符合;不读取 QQ authority,持久化管理员与添加者,数据库失败不宣称成功 | services/permission.ts、bridge 测试 |
21
+ | 动态后台维护 | 符合;官方 console/auth RPC 与 QQ 共用数据库,登录状态与当前权限重新检查 | console.ts、client/page.vue、console / client 测试 |
22
+ | 审计与保留 | 符合;pending / success / noop / failed / unknown,定期清理,不记录评论正文 | services/storage.ts、bridge 测试 |
23
+ | 关闭 / 重开 Issue 与 PR | 符合;使用对应端点、已达状态不写入,merged PR 拒绝重开;未实现 merge | services/github.ts、bridge / github-errors 测试 |
24
+ | 去重 / 冷却 / 有限重试 | 符合;数据库事件主键、跨重启记录、串行有界队列,写入不自动重试 | storage.ts、utils/lock.ts、bridge / github-errors 测试 |
25
+ | Token 隔离和错误处理 | 符合检查范围;不回显上游错误对象,拒发包含部署 Token 的正文,覆盖常见 HTTP 错误 | utils/errors.ts、github-errors / console 测试 |
26
+ | 文档与可发布包 | 符合;安装、Token 权限、边界和真实验收说明齐全 | README、TESTING、RELEASING、check-package.cjs |
27
+
28
+ 后台等级 5 是 Koishi 网页部署管理入口的鉴权,与 QQ 角色体系分开;符合“不依赖 Koishi authority 管理 QQ 管理员”的要求。
29
+
30
+ ## 本次重新执行的验证
31
+
32
+ Windows、PowerShell 7.6.9、Node 24.15.0、npm 12.0.2:
33
+
34
+ - `npm ci`:成功。
35
+ - 初次 `npm run check`:后端与 Vue 类型检查、构建成功;6 个测试文件、125 项测试通过。随后增加社区版 OneBot 6.9.4 解码兼容测试,完整 126 项测试与类型检查均通过;补充改动在 Ubuntu / Node 22、Ubuntu / Node 24、Windows / Node 24 三组 CI 也全部通过。
36
+ - `npm audit --omit=dev`:0 项;增加社区适配器测试依赖后,完整开发依赖树为 26 项(19 moderate、7 high)。新增一项是社区适配器经由既有 Koishi peer dependency 产生的归因;未新增生产直接依赖。Koishi 等宿主依赖在此项目中作为 dev / peer 依赖,不能据此推导实际部署无漏洞。
37
+ - 包文件与提取后 Koishi / SQLite 加载检查:`npm run check:package` 本地通过,并纳入 CI。
38
+ - 独立消费者安装:从 GitHub Release 下载实际 `v1.0.0` 安装包,在隔离目录通过 npm 安装及独立解析 Koishi / HTTP / SQLite 依赖,启动插件并读写管理员表成功;没有沿用源码目录的 node_modules。
39
+ - `v1.0.0` 的 Ubuntu / Node 22、Ubuntu / Node 24、Windows / Node 24 三组 CI 均通过,包含 125 项测试和包加载检查;GitHub Release 由标签流水线成功创建。后续版本以仓库 Actions、GitHub Releases 和 npm registry 为准。
40
+
41
+ ## 仍需真实环境验收的边界
42
+
43
+ 1. 尚未连接真实 NapCat / QQ。现有 runtime 测试使用官方 OneBot 6.0.2、社区版 OneBot 6.9.4 的解码器和人工事件;前者已被上游标为停止官方支持。实际部署仍需实测所选实现的字段与消息链路。
44
+ 2. GitHub HTTP 测试请求本机模拟服务器,未用真实 PAT 评论或修改 Issue / PR。
45
+ 3. 本次没有重复执行完整后台浏览器交互验收;已有 Vue 组件及带真实 auth / SQLite 的 RPC 测试通过。历史交付的浏览器检查记录见 TESTING,不把历史记录描述成本次重新实测。
46
+ 4. 并发队列按插件实例工作;去重主键可跨实例,但多进程共享数据库的权限 / 冷却 / 状态更新事务不在第一版承诺范围。推荐同一 Koishi 应用只启用一个桥接实例。
47
+ 5. Token 属于一个 GitHub 账号;Fine-grained PAT 的单资源所有者限制需要部署者按绑定仓库安排。
48
+
49
+ 真实环境验收步骤见 TESTING。公开发布是分发验证,不能代替这些运行验收。
@@ -0,0 +1,92 @@
1
+ # 原始交付的测试与验收报告(2026-10-25)
2
+
3
+ 本文保留 2026-10-25 原始交付的历史记录,以下“未发布”等状态属于该日期。2026-10-26 的重新检查、GitHub / npm 发布准备与验收结论见 [本次需求复核](REVIEW.md),当前发布配置见 [发布说明](RELEASING.md)。
4
+
5
+ 验证日期:2026-10-25(Asia/Shanghai)。本报告区分真实运行的框架与模拟的外部系统。
6
+
7
+ ## 已执行结果
8
+
9
+ | 层级 | 结果 | 具体范围 |
10
+ | --- | --- | --- |
11
+ | 后端 TypeScript 静态检查 | 通过 | 严格模式,含后端与自动化测试源码 |
12
+ | Vue / 前端 TypeScript 静态检查 | 通过 | 使用由实际官方 SDK 生成的声明,严格检查本插件客户端 |
13
+ | 插件构建 | 通过 | `tsc` 输出 CommonJS / 声明 / source map,官方 client 构建器输出 JS / CSS |
14
+ | 自动化测试 | **125 项通过,6 个测试文件通过** | `npm run check`,最后完整运行耗时约 4.53 秒(不含检查与构建) |
15
+ | 真实 Koishi 框架 | 通过本地集成验证 | Context、服务注入、消息分发、中间件、auth、console、停用与重新启用;不是线上生产部署验收 |
16
+ | 真实 SQLite 驱动 | 通过 | 内存数据库 CRUD、临时磁盘数据库关闭后重新打开,管理员 / 去重记录保持 |
17
+ | 真实 Koishi HTTP 服务 | 通过 | 请求本机模拟 GitHub HTTP 服务,验证路径、方法、请求头、状态、超时和中止 |
18
+ | 官方 OneBot 适配器解码 | 通过 | 调用 6.0.2 发布包的 `adaptSession` 解码人工构造的 OneBot 群消息 |
19
+ | 独立 npm 消费者安装 | 通过 | 用实际 `.tgz` 安装到独立目录,从 node_modules 加载插件并启动 console / auth / SQLite |
20
+ | 后台实际页面 | 已验证加载与显示 | 在本机 Koishi 的生产资源模式登录测试账号,打开插件页面,读取数据库并检查渲染;留有界面截图 |
21
+ | 后台新增 / 撤销交互 | 通过组件和 RPC 自动化测试 | 编译的 Vue 页面在 Happy DOM 内验证确认、取消、成功刷新、错误展示;RPC 使用真实 Koishi auth 和数据库 |
22
+ | 真实 OneBot / NapCat / QQ | **未验证** | 没有启动或登录真实 QQ;未测真实消息投递与 QQ 回执 |
23
+ | 真实 GitHub 写入 | **未验证** | 未使用真实 PAT,未向任何真实仓库评论或修改状态 |
24
+ | npm / 插件市场公开发布 | **未执行** | 已生成本地可安装包;未注册 / 登录 / 发布到外部账户 |
25
+
26
+ 本机 Node.js 24.19.0、npm 11.17.0、Windows;Koishi 4.18.11、SQLite 4.7.0、HTTP 0.6.3、console/client 5.30.11、auth 4.1.7。仅验证了当前平台,未执行 Linux / macOS 或多数据库驱动矩阵。
27
+
28
+ 浏览器实操中原生 `window.confirm` 阻塞了内嵌浏览器的控制通道,因此改为页面内确认。最终的新增、取消、撤销流程通过组件测试和服务端 RPC 测试验证;不把这描述成完整的真人浏览器点击端到端验收。
29
+
30
+ ## 自动化覆盖
31
+
32
+ | 测试文件 | 主要验证 |
33
+ | --- | --- |
34
+ | `test/parser.test.ts` | 默认 / 自定义前缀、命名组、多行、空正文、错误编号、命令冲突、嵌套量词线性匹配、危险正则与配置拒绝 |
35
+ | `test/bridge.test.ts` | Issue / PR 评论、多群路由、正文保留、昵称注入、递归授权、撤权边界、并发授权、状态端点、merged PR、去重、冷却、审计失败、SQLite 重启 |
36
+ | `test/github-errors.test.ts` | 401/403/404/422/429/503、限流暂停、禁止重定向、连接中断、写超时、有限 GET 重试、停用中止、响应异常、企业 / 代理返回链接 |
37
+ | `test/runtime.test.ts` | 真正消息管线、OneBot 解码、热重载、重新绑定、低 authority 仍可评论、忽略私聊 / 非 OneBot / 匿名 / 不一致身份 / 自身 / 媒体 / 历史消息 |
38
+ | `test/console.test.ts` | 未登录、过期会话、会话撤销、旧 socket 被降权、QQ 与后台共享数据库、输入校验、auth 停用后 RPC 清理 |
39
+ | `test/client.test.ts` | 编译 Vue 页面的二次确认、取消、撤销、刷新、错误反馈、选择绑定检查 Token、元数据按文本渲染 |
40
+
41
+ 使用本地模拟服务器,不依赖外部 GitHub 或真实 QQ。权限测试使用真实 Koishi 数据库接口;故障注入只在明确的失败场景替换对应方法。HTTP 测试不是简单地替换 `github.comment()` 返回成功。
42
+
43
+ ## 复现
44
+
45
+ 在源码目录运行:
46
+
47
+ ```sh
48
+ npm ci
49
+ npm run typecheck
50
+ npm run build
51
+ npm test
52
+ npm pack
53
+ ```
54
+
55
+ 也可运行 `npm run check` 一次执行前三项验证。查看某一测试:
56
+
57
+ ```sh
58
+ npm test -- test/runtime.test.ts --reporter=verbose
59
+ ```
60
+
61
+ 本地后台预览:
62
+
63
+ ```sh
64
+ npm run preview
65
+ ```
66
+
67
+ 脚本仅监听本机,控制台端口从 5147 到 5167 选择。测试登录为 `bridge-preview` / `local-preview-only`,使用内存数据与模拟 GitHub,只为开发检查准备,不可作为生产启动配置。退出后测试数据不保留。
68
+
69
+ ## 依赖审计的实际边界
70
+
71
+ - 源码完整开发依赖树:**25 条告警(18 moderate、7 high)**。
72
+ - 本插件仓库执行 `npm audit --omit=dev`:0 条。这里 Koishi / console / auth 同时作为开发测试宿主和 peer dependency,因而这条命令**不代表实际部署的整个 Koishi 应用没有漏洞**。
73
+ - 独立消费者安装 Koishi、console、auth、SQLite 和本插件后的树:**19 条告警(14 moderate、5 high)**。
74
+ - 告警主要来自宿主 / 工具链的 `file-type`、`zod` / `koa-body`、`js-yaml`、`uuid`,开发客户端另含 `element-plus`、旧 Vite / esbuild 的传递问题。部分是旧主版本无法直接修复的依赖约束。
75
+ - 已尝试遵循现有版本范围的 `npm audit fix`,未通过 `--force` 强行降级或替换框架。没有声称所有告警均已修复,也没有针对每条上游告警做可利用性验证。
76
+
77
+ 依赖锁文件随源码交付。正式上线时应复核宿主的当前安全更新;仅使用 `npm audit --omit=dev` 的零告警作为安全结论是不充分的。
78
+
79
+ ## 真实环境验收步骤(尚未执行)
80
+
81
+ 1. 使用隔离测试 QQ 群、真实 NapCat / OneBot 连接与专用 GitHub 测试仓库。先确认原始 `message_id`、`time`、`user_id`、`sender.user_id`、`group_id` 和 `self_id` 均正常。
82
+ 2. 为测试仓库配置最小权限 Fine-grained PAT,在后台检查身份与仓库可读性。
83
+ 3. 向 Issue 和 PR 分别发送多行 `re编号 正文`,检查 GitHub 正文、身份头和 QQ 返回的评论链接。
84
+ 4. 普通用户尝试 close / open / ghadmin,确认 GitHub 无变化。
85
+ 5. 超级管理员授权 A,A 授权 B,B 再授权 C;重启 Koishi 后检查权限。普通管理员撤权应失败,超级管理员撤权立即生效。
86
+ 6. 按提示确认关闭和重开 Issue、普通 PR;对已合并 PR、源分支已删除 PR 检查实际 GitHub 错误与 QQ 反馈。
87
+ 7. 将第二个群绑定另一个仓库,验证路由隔离;禁用或删除绑定,确认不再调用 GitHub。
88
+ 8. 对相同 OneBot 事件重复投递、重连后回放、插件停用重启测试,检查不会重复评论。模拟提交后断网,核对不确定提示。
89
+ 9. 在真实浏览器手动完成后台新增、取消、撤销、分页、Token 检查,并检查窄屏 / 实际主题;确认普通控制台账号和未登录访客无法读写管理数据。
90
+ 10. 检查 Koishi / NapCat 全链路日志、配置备份保护与日志保留,确保部署中的其他插件不记录请求凭据。
91
+
92
+ 以上验收需要部署者实际环境与凭据,本次没有假设或伪造结果。
@@ -0,0 +1,35 @@
1
+ import { Schema } from 'koishi';
2
+ export declare const QQ_PATTERN: RegExp;
3
+ export declare const DEFAULT_TEMPLATE = "\u4EE5\u4E0B\u6B63\u6587\u7531 QQ \u7FA4\u6210\u5458\u901A\u8FC7 GitHub Bridge \u63D0\u4EA4\u3002\n\n{body}";
4
+ export interface Binding {
5
+ groupId: string;
6
+ owner: string;
7
+ repo: string;
8
+ enabled: boolean;
9
+ }
10
+ export interface Config {
11
+ token: string;
12
+ apiBaseUrl: string;
13
+ apiVersion: string;
14
+ requestTimeout: number;
15
+ readRetries: number;
16
+ commentTemplate: string;
17
+ bindings: Binding[];
18
+ replyPrefix: string;
19
+ replyPattern: string;
20
+ closePrefix: string;
21
+ openPrefix: string;
22
+ adminPrefix: string;
23
+ superAdmins: string[];
24
+ enableAdminCommands: boolean;
25
+ userCooldown: number;
26
+ maxCommentLength: number;
27
+ allowMultiline: boolean;
28
+ auditEnabled: boolean;
29
+ auditRetentionDays: number;
30
+ dedupWindow: number;
31
+ confirmHighRisk: boolean;
32
+ confirmationTimeout: number;
33
+ }
34
+ export declare const Config: Schema<Partial<Config>, Config>;
35
+ export declare function validateConfig(config: Config): Config;
package/lib/config.js ADDED
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Config = exports.DEFAULT_TEMPLATE = exports.QQ_PATTERN = void 0;
4
+ exports.validateConfig = validateConfig;
5
+ const koishi_1 = require("koishi");
6
+ const errors_1 = require("./utils/errors");
7
+ exports.QQ_PATTERN = /^[1-9][0-9]{4,19}$/;
8
+ exports.DEFAULT_TEMPLATE = '以下正文由 QQ 群成员通过 GitHub Bridge 提交。\n\n{body}';
9
+ const prefix = (value, description) => koishi_1.Schema.string().default(value).description(description);
10
+ const qq = () => koishi_1.Schema.string().pattern(exports.QQ_PATTERN).description('真实数字 QQ 号,按字符串填写。');
11
+ exports.Config = koishi_1.Schema.intersect([
12
+ koishi_1.Schema.object({
13
+ token: koishi_1.Schema.string().role('secret').default('').description('统一 GitHub Personal Access Token。支持 Fine-grained PAT;不要在 QQ 中发送。'),
14
+ apiBaseUrl: koishi_1.Schema.string().default('https://api.github.com').description('GitHub REST API 根地址;企业版通常为 https://主机/api/v3。必须使用 HTTPS(本机测试除外)。'),
15
+ apiVersion: koishi_1.Schema.string().default('2026-03-10').description('GitHub API 版本。企业版可按服务器支持情况修改。'),
16
+ requestTimeout: koishi_1.Schema.number().min(1000).max(60000).step(1).default(15000).description('单次请求超时(毫秒)。写入不会自动重试。'),
17
+ readRetries: koishi_1.Schema.number().min(0).max(3).step(1).default(2).description('只读请求遇到网络故障或 5xx 时的重试次数。'),
18
+ commentTemplate: koishi_1.Schema.string().role('textarea').default(exports.DEFAULT_TEMPLATE).description('正文模板,必须且只能包含一次 {body}。固定身份头由插件生成,不可覆盖;原始正文原样替换。'),
19
+ }).description('GitHub 设置'),
20
+ koishi_1.Schema.object({
21
+ bindings: koishi_1.Schema.array(koishi_1.Schema.object({
22
+ groupId: qq().description('QQ群号;一个群仅能出现一次。'),
23
+ owner: koishi_1.Schema.string().required().description('仓库所属用户或组织,不填 URL。'),
24
+ repo: koishi_1.Schema.string().required().description('仓库名称,不填 URL 或 owner/。'),
25
+ enabled: koishi_1.Schema.boolean().default(true).description('启用此群的仓库绑定。'),
26
+ })).role('table').default([]).description('多个群可绑定同一仓库;未绑定或禁用的群完全忽略。'),
27
+ }).description('仓库绑定'),
28
+ koishi_1.Schema.object({
29
+ replyPrefix: prefix('re', '普通评论前缀,例如 re1003 内容。'),
30
+ replyPattern: koishi_1.Schema.string().role('textarea').default('').description('留空使用回复前缀。自定义 RE2 表达式须以 ^ 开始、$ 结束,并有命名组 (?<number>...) 和 (?<body>...)。不支持回溯引用与前后查找;固定使用 u 模式。'),
31
+ closePrefix: prefix('close', '关闭前缀,例如 close1003。'),
32
+ openPrefix: prefix('open', '重开前缀,例如 open1003。'),
33
+ adminPrefix: prefix('ghadmin', '管理员指令前缀,例如 ghadmin add 123456789。二次确认使用此前缀 confirm 确认码。'),
34
+ }).description('命令设置'),
35
+ koishi_1.Schema.object({
36
+ superAdmins: koishi_1.Schema.array(qq()).min(1).required().description('至少配置一个超级管理员 QQ。通过本页恢复控制权;QQ 指令不能撤销。'),
37
+ enableAdminCommands: koishi_1.Schema.boolean().default(true).description('允许在已绑定群使用管理员新增、撤销和列表命令。'),
38
+ }).description('独立权限设置'),
39
+ koishi_1.Schema.object({
40
+ userCooldown: koishi_1.Schema.number().min(0).max(3600000).step(1).default(5000).description('每位 QQ 用户的操作冷却(毫秒),跨群共用;确认指令不额外冷却。'),
41
+ maxCommentLength: koishi_1.Schema.number().min(1).max(30000).step(1).default(6000).description('评论正文最大字符长度(UTF-16 单元)。'),
42
+ allowMultiline: koishi_1.Schema.boolean().default(true).description('允许多行评论和 Markdown。'),
43
+ auditEnabled: koishi_1.Schema.boolean().default(true).description('保存操作审计到 Koishi 数据库;不存 Token、昵称或评论正文。'),
44
+ auditRetentionDays: koishi_1.Schema.number().min(1).max(365).step(1).default(30).description('审计保留天数,每小时自动清理过期记录。'),
45
+ dedupWindow: koishi_1.Schema.number().min(300000).max(604800000).step(1).default(86400000).description('消息去重时间窗口(毫秒),默认 24 小时。过期事件不执行;记录跨重启保留。'),
46
+ confirmHighRisk: koishi_1.Schema.boolean().default(true).description('关闭、重开、新增和撤销管理员前要求同一 QQ、同一群在限时内确认。'),
47
+ confirmationTimeout: koishi_1.Schema.number().min(10000).max(300000).step(1).default(60000).description('高风险操作确认有效期(毫秒);重载后失效。'),
48
+ }).description('安全与可靠性'),
49
+ ]);
50
+ function validateConfig(config) {
51
+ const invalid = (message) => { throw new errors_1.BridgeError('config', message); };
52
+ if (!config.token || config.token.length > 500 || /\s|[^\x21-\x7e]/.test(config.token)) {
53
+ invalid('请在后台填写有效格式的 GitHub Token(不可为空或包含空白)。');
54
+ }
55
+ let base;
56
+ try {
57
+ base = new URL(config.apiBaseUrl);
58
+ }
59
+ catch {
60
+ return invalid('GitHub API 地址不是有效 URL。');
61
+ }
62
+ const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(base.hostname);
63
+ if (base.protocol !== 'https:' && !(base.protocol === 'http:' && loopback))
64
+ invalid('GitHub API 必须使用 HTTPS。');
65
+ if (base.username || base.password || base.search || base.hash)
66
+ invalid('GitHub API 地址不得包含用户名、密码、查询参数或片段。');
67
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(config.apiVersion))
68
+ invalid('API 版本必须采用 YYYY-MM-DD 格式。');
69
+ const prefixes = [config.replyPrefix, config.closePrefix, config.openPrefix, config.adminPrefix];
70
+ if (prefixes.some(value => !/^[A-Za-z][A-Za-z0-9_-]{0,31}$/.test(value)))
71
+ invalid('指令前缀须以英文字母开头,最多 32 位,仅含英文字母、数字、下划线和连字符。');
72
+ if (prefixes.some((p, i) => prefixes.some((q, j) => i !== j && p.startsWith(q))))
73
+ invalid('指令前缀不得相同或互为前缀。');
74
+ if (!config.superAdmins.length || config.superAdmins.some(id => !exports.QQ_PATTERN.test(id)))
75
+ invalid('必须配置至少一个有效的超级管理员 QQ。');
76
+ if (new Set(config.superAdmins).size !== config.superAdmins.length)
77
+ invalid('超级管理员列表存在重复 QQ。');
78
+ const groups = new Set();
79
+ if (config.bindings.length > 500)
80
+ invalid('最多允许配置 500 个群绑定。');
81
+ for (const binding of config.bindings) {
82
+ if (!exports.QQ_PATTERN.test(binding.groupId))
83
+ invalid('群绑定中存在无效的 QQ 群号。');
84
+ if (groups.has(binding.groupId))
85
+ invalid('同一 QQ 群只能绑定一次。');
86
+ groups.add(binding.groupId);
87
+ if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,99}$/.test(binding.owner) ||
88
+ !/^[A-Za-z0-9_.-]{1,100}$/.test(binding.repo) || ['.', '..'].includes(binding.repo))
89
+ invalid('仓库 Owner 或名称不合法;请分别填写名称,不要填写 URL。');
90
+ }
91
+ if (config.commentTemplate.length > 4000 || config.commentTemplate.split('{body}').length !== 2)
92
+ invalid('评论模板最多 4000 字符,并且必须且只能包含一次 {body}。');
93
+ if (/\{(?!body\})[^{}]*\}/.test(config.commentTemplate))
94
+ invalid('评论模板仅支持 {body} 占位符。');
95
+ if (config.commentTemplate.includes(config.token))
96
+ invalid('评论模板不能包含 GitHub Token。');
97
+ return config;
98
+ }
99
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Context } from 'koishi';
2
+ import type { Client } from '@koishijs/plugin-console';
3
+ import type { Actor } from './types';
4
+ import type { Bridge } from './handlers/commands';
5
+ /** Web deployer access is separate from the QQ administrator model. No QQ authority is used. */
6
+ export declare function authenticateConsole(ctx: Context, client: Client): Promise<Actor>;
7
+ export declare function registerConsole(ctx: Context, bridge: Bridge): void;
package/lib/console.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authenticateConsole = authenticateConsole;
4
+ exports.registerConsole = registerConsole;
5
+ const node_path_1 = require("node:path");
6
+ const config_1 = require("./config");
7
+ const errors_1 = require("./utils/errors");
8
+ /** Web deployer access is separate from the QQ administrator model. No QQ authority is used. */
9
+ async function authenticateConsole(ctx, client) {
10
+ if (!ctx.get('auth'))
11
+ throw new errors_1.BridgeError('console-auth', '控制台登录服务未启用,管理接口已停用。');
12
+ const auth = client.auth;
13
+ if (!auth || auth.expiredAt <= Date.now())
14
+ throw new errors_1.BridgeError('console-auth', '请先以部署管理员账号登录控制台。');
15
+ const [token] = await ctx.database.get('token', { id: auth.id, token: auth.token, expiredAt: { $gt: Date.now() } }, ['id']);
16
+ const [user] = await ctx.database.get('user', { id: auth.id }, ['id', 'authority']);
17
+ if (!token || !user || user.authority < 5)
18
+ throw new errors_1.BridgeError('console-auth', '只有已登录的控制台部署管理员(等级 5)可以维护此插件。');
19
+ const [binding] = await ctx.database.get('binding', { aid: user.id, platform: 'onebot' }, { limit: 1 });
20
+ return {
21
+ source: 'console', id: String(user.id), qq: binding && config_1.QQ_PATTERN.test(binding.pid) ? binding.pid : '',
22
+ nickname: '', groupId: '', repository: '',
23
+ };
24
+ }
25
+ function registerConsole(ctx, bridge) {
26
+ // Without the official auth service, even the listeners do not exist.
27
+ ctx.inject(['console', 'auth'], ctx => {
28
+ ctx.console.addEntry({ dev: (0, node_path_1.resolve)(__dirname, '../client/index.ts'), prod: (0, node_path_1.resolve)(__dirname, '../dist') });
29
+ const safe = async (task) => {
30
+ try {
31
+ return await task();
32
+ }
33
+ catch (error) {
34
+ // Console logs thrown objects, so replace them rather than rethrowing raw errors.
35
+ throw new Error((0, errors_1.redact)((0, errors_1.publicError)(error).message, bridge.config.token));
36
+ }
37
+ };
38
+ ctx.console.addListener('qq-github-bridge/dashboard', function (page = 1) {
39
+ return safe(async () => {
40
+ if (!Number.isSafeInteger(page) || page < 1 || page > 100000)
41
+ throw new errors_1.BridgeError('input', '页码无效。');
42
+ return bridge.dashboard(() => authenticateConsole(ctx, this), page);
43
+ });
44
+ }, { authority: 5 });
45
+ ctx.console.addListener('qq-github-bridge/admin', function (action, qq) {
46
+ return safe(async () => {
47
+ if (!['add', 'remove'].includes(action) || typeof qq !== 'string' || !config_1.QQ_PATTERN.test(qq))
48
+ throw new errors_1.BridgeError('input', '管理员操作或 QQ 号无效。');
49
+ return bridge.manage(() => authenticateConsole(ctx, this), action, qq);
50
+ });
51
+ }, { authority: 5 });
52
+ ctx.console.addListener('qq-github-bridge/validate', function (groupId) {
53
+ return safe(async () => {
54
+ if (groupId !== undefined && (typeof groupId !== 'string' || !config_1.QQ_PATTERN.test(groupId)))
55
+ throw new errors_1.BridgeError('input', 'QQ群号无效。');
56
+ return bridge.validateToken(() => authenticateConsole(ctx, this), groupId);
57
+ });
58
+ }, { authority: 5 });
59
+ // Console 5.30 addListener() assigns into a dictionary and does NOT register
60
+ // a disposal effect. Explicitly remove only this activation's own listeners.
61
+ const listeners = ctx.console.listeners;
62
+ const owned = ['dashboard', 'admin', 'validate'].map(name => {
63
+ const key = `qq-github-bridge/${name}`;
64
+ return [key, listeners[key]];
65
+ });
66
+ ctx.on('dispose', () => {
67
+ for (const [key, listener] of owned)
68
+ if (listeners[key] === listener)
69
+ delete listeners[key];
70
+ });
71
+ });
72
+ }
73
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.js","sourceRoot":"","sources":["../src/console.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Context } from 'koishi';
2
+ export interface AdminRow {
3
+ qq: string;
4
+ addedBy: string;
5
+ createdAt: Date;
6
+ }
7
+ export interface EventRow {
8
+ id: string;
9
+ expiresAt: Date;
10
+ }
11
+ export interface CooldownRow {
12
+ qq: string;
13
+ expiresAt: Date;
14
+ }
15
+ export interface AuditRow {
16
+ id: string;
17
+ actorId: string;
18
+ actorQQ: string;
19
+ source: string;
20
+ action: string;
21
+ target: string;
22
+ groupId: string;
23
+ repository: string;
24
+ createdAt: Date;
25
+ result: string;
26
+ detail: string;
27
+ }
28
+ declare module 'koishi' {
29
+ interface Tables {
30
+ qgh_admin: AdminRow;
31
+ qgh_event: EventRow;
32
+ qgh_cooldown: CooldownRow;
33
+ qgh_audit: AuditRow;
34
+ }
35
+ }
36
+ /** Additive schema, stable keys. Future renamed fields should use Minato's legacy migration. */
37
+ export declare function registerModels(ctx: Context): void;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerModels = registerModels;
4
+ /** Additive schema, stable keys. Future renamed fields should use Minato's legacy migration. */
5
+ function registerModels(ctx) {
6
+ ctx.model.extend('qgh_admin', {
7
+ qq: 'string(20)', addedBy: 'string(64)', createdAt: 'timestamp',
8
+ }, { primary: 'qq' });
9
+ ctx.model.extend('qgh_event', {
10
+ id: 'string(64)', expiresAt: 'timestamp',
11
+ }, { primary: 'id' });
12
+ ctx.model.extend('qgh_cooldown', {
13
+ qq: 'string(20)', expiresAt: 'timestamp',
14
+ }, { primary: 'qq' });
15
+ ctx.model.extend('qgh_audit', {
16
+ id: 'string(36)', actorId: 'string(64)', actorQQ: 'string(20)', source: 'string(16)',
17
+ action: 'string(32)', target: 'string(100)', groupId: 'string(20)', repository: 'string(201)',
18
+ createdAt: 'timestamp', result: 'string(24)', detail: 'string(255)',
19
+ }, { primary: 'id' });
20
+ }
21
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/database/models.ts"],"names":[],"mappings":";;AA6BA,wCAeC;AAhBD,gGAAgG;AAChG,SAAgB,cAAc,CAAC,GAAY;IACzC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC5B,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW;KAChE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACrB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC5B,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW;KACzC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACrB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE;QAC/B,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW;KACzC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACrB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC5B,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY;QACpF,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa;QAC7F,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa;KACpE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;AACvB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Context } from 'koishi';
2
+ import type { Config } from '../config';
3
+ import type { Actor, Dashboard, IncomingMessage } from '../types';
4
+ import { RepositoryResolver } from '../services/repository';
5
+ export declare class Bridge {
6
+ private readonly ctx;
7
+ readonly config: Config;
8
+ readonly repositories: RepositoryResolver;
9
+ private readonly parser;
10
+ private readonly permissions;
11
+ private readonly storage;
12
+ private readonly github;
13
+ private readonly queue;
14
+ private readonly confirmations;
15
+ constructor(ctx: Context, config: Config);
16
+ dispose(): Promise<void>;
17
+ private target;
18
+ private authorize;
19
+ private audited;
20
+ private perform;
21
+ private confirmKey;
22
+ private pruneConfirmations;
23
+ handle(message: IncomingMessage): Promise<string | null>;
24
+ dashboard(authenticate: () => Promise<Actor>, page: number): Promise<Dashboard>;
25
+ manage(authenticate: () => Promise<Actor>, action: 'add' | 'remove', qq: string): Promise<string>;
26
+ validateToken(authenticate: () => Promise<Actor>, groupId?: string): Promise<import("../types").TokenStatus>;
27
+ cleanup(): Promise<void>;
28
+ }