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

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 +48 -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 +99 -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,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Bridge = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const github_1 = require("../services/github");
6
+ const permission_1 = require("../services/permission");
7
+ const repository_1 = require("../services/repository");
8
+ const storage_1 = require("../services/storage");
9
+ const parser_1 = require("../utils/parser");
10
+ const lock_1 = require("../utils/lock");
11
+ const errors_1 = require("../utils/errors");
12
+ class Bridge {
13
+ ctx;
14
+ config;
15
+ repositories;
16
+ parser;
17
+ permissions;
18
+ storage;
19
+ github;
20
+ // One bounded queue serializes permission changes and state writes in this instance.
21
+ // It also prevents different bound groups racing against the same target.
22
+ queue = new lock_1.SerialQueue();
23
+ confirmations = new Map();
24
+ constructor(ctx, config) {
25
+ this.ctx = ctx;
26
+ this.config = config;
27
+ this.repositories = new repository_1.RepositoryResolver(config.bindings);
28
+ this.parser = new parser_1.MessageParser(config);
29
+ this.permissions = new permission_1.Permissions(ctx, config);
30
+ this.storage = new storage_1.Storage(ctx, config);
31
+ this.github = new github_1.GitHub(ctx, config);
32
+ }
33
+ async dispose() {
34
+ this.queue.close();
35
+ this.github.dispose();
36
+ this.confirmations.clear();
37
+ await this.queue.drain();
38
+ }
39
+ target(command) {
40
+ if ('number' in command)
41
+ return String(command.number);
42
+ if ('qq' in command)
43
+ return command.qq;
44
+ return '';
45
+ }
46
+ async authorize(actor, command) {
47
+ if (command.type.startsWith('admin-') && actor.source === 'qq' && !this.config.enableAdminCommands) {
48
+ throw new errors_1.BridgeError('admin-disabled', '部署者已关闭 QQ 管理员指令,请使用后台管理。');
49
+ }
50
+ if (command.type !== 'comment')
51
+ await this.permissions.requireAdmin(actor, command.type === 'admin-remove');
52
+ }
53
+ async audited(actor, command, task) {
54
+ const id = await this.storage.begin(actor, command.type, this.target(command));
55
+ try {
56
+ const outcome = await task();
57
+ const saved = await this.storage.finish(id, outcome.result || 'success', 'completed');
58
+ return outcome.message + (saved ? '' : '\n操作已完成,但审计结果更新失败;后台记录暂为 pending。');
59
+ }
60
+ catch (error) {
61
+ const safe = (0, errors_1.publicError)(error);
62
+ await this.storage.finish(id, safe.uncertain ? 'unknown' : 'failed', safe.code);
63
+ throw safe;
64
+ }
65
+ }
66
+ async perform(message, command) {
67
+ await this.authorize(message.actor, command);
68
+ if (command.type === 'comment')
69
+ return this.github.comment(message.repository, command.number, message.actor, command.body, message.eventKey);
70
+ if (command.type === 'close' || command.type === 'open')
71
+ return this.github.state(message.repository, command.number, command.type === 'close' ? 'closed' : 'open');
72
+ if (command.type === 'admin-add')
73
+ return this.permissions.add(message.actor, command.qq);
74
+ return this.permissions.remove(message.actor, command.qq);
75
+ }
76
+ confirmKey(message) { return `${message.actor.qq}:${message.actor.groupId}`; }
77
+ pruneConfirmations() {
78
+ for (const [key, pending] of this.confirmations)
79
+ if (pending.expires < Date.now())
80
+ this.confirmations.delete(key);
81
+ }
82
+ async handle(message) {
83
+ const command = this.parser.parse(message.text);
84
+ if (!command)
85
+ return null;
86
+ try {
87
+ const result = await this.queue.run(async () => {
88
+ if (Date.now() - message.timestamp > this.config.dedupWindow)
89
+ return '';
90
+ if (!await this.storage.claim(message))
91
+ return '';
92
+ if (command.type === 'invalid')
93
+ return command.message;
94
+ this.pruneConfirmations();
95
+ if (command.type === 'confirm') {
96
+ const key = this.confirmKey(message);
97
+ const pending = this.confirmations.get(key);
98
+ if (!pending || pending.code !== command.code || pending.actorId !== message.actor.qq ||
99
+ pending.groupId !== message.actor.groupId || pending.repository !== message.repository.fullName) {
100
+ throw new errors_1.BridgeError('confirmation', '确认码无效或已过期。请由原操作者在原群重新发起操作。');
101
+ }
102
+ // Consume before any asynchronous work; a failed / uncertain write cannot reuse it.
103
+ this.confirmations.delete(key);
104
+ return this.audited(message.actor, pending.command, () => this.perform(message, pending.command));
105
+ }
106
+ await this.storage.cooldown(message.actor.qq);
107
+ return this.audited(message.actor, command, async () => {
108
+ await this.authorize(message.actor, command);
109
+ if (command.type === 'admin-list') {
110
+ const admins = await this.permissions.list((command.page - 1) * 20, 21);
111
+ const rows = admins.slice(0, 20).map(row => `${row.qq}(添加者:${row.addedBy};${row.createdAt.toISOString()})`);
112
+ return { message: `超级管理员:${this.config.superAdmins.join('、')}\n普通管理员(第 ${command.page} 页):\n${rows.join('\n') || '无'}${admins.length > 20 ? `\n下一页:${this.config.adminPrefix} list ${command.page + 1}` : ''}` };
113
+ }
114
+ if (command.type !== 'comment' && this.config.confirmHighRisk) {
115
+ if (this.confirmations.size >= 1000)
116
+ throw new errors_1.BridgeError('busy', '待确认操作过多,请稍后再试。');
117
+ const code = (0, node_crypto_1.randomBytes)(6).toString('hex');
118
+ this.confirmations.set(this.confirmKey(message), {
119
+ command, code, expires: Date.now() + this.config.confirmationTimeout,
120
+ actorId: message.actor.qq, groupId: message.actor.groupId, repository: message.repository.fullName,
121
+ });
122
+ const action = { close: '关闭', open: '重新打开', 'admin-add': '添加管理员', 'admin-remove': '撤销管理员' }[command.type];
123
+ return {
124
+ message: `待确认:${action} ${this.target(command)}${'number' in command ? `(${message.repository.fullName})` : ''}。\n请在 ${this.config.confirmationTimeout / 1000} 秒内由 QQ ${message.actor.qq} 在本群发送:\n${this.config.adminPrefix} confirm ${code}`,
125
+ result: 'noop',
126
+ };
127
+ }
128
+ return this.perform(message, command);
129
+ });
130
+ });
131
+ return (0, errors_1.redact)(result, this.config.token);
132
+ }
133
+ catch (error) {
134
+ const safe = (0, errors_1.publicError)(error);
135
+ // Do not give HTTP errors (including headers) to Koishi's logger.
136
+ this.ctx.logger('qq-github-bridge').warn('操作未完成,错误类别:%s', safe.code);
137
+ return (0, errors_1.redact)(safe.message, this.config.token);
138
+ }
139
+ }
140
+ async dashboard(authenticate, page) {
141
+ return this.queue.run(async () => {
142
+ await authenticate();
143
+ const rows = await this.permissions.list((page - 1) * 50, 51);
144
+ const audits = await this.ctx.database.get('qgh_audit', {}, { sort: { createdAt: 'desc', id: 'desc' }, limit: 100 });
145
+ return {
146
+ admins: rows.slice(0, 50).map(row => ({ ...row, createdAt: row.createdAt.toISOString() })),
147
+ hasMoreAdmins: rows.length > 50,
148
+ superAdmins: [...this.config.superAdmins],
149
+ bindings: this.config.bindings.map(b => ({ groupId: b.groupId, repository: `${b.owner}/${b.repo}`, enabled: b.enabled })),
150
+ audits: audits.map(row => ({ ...row, createdAt: row.createdAt.toISOString() })),
151
+ auditEnabled: this.config.auditEnabled, auditRetentionDays: this.config.auditRetentionDays,
152
+ };
153
+ });
154
+ }
155
+ async manage(authenticate, action, qq) {
156
+ return this.queue.run(async () => {
157
+ const actor = await authenticate();
158
+ const command = { type: action === 'add' ? 'admin-add' : 'admin-remove', qq };
159
+ return this.audited(actor, command, () => action === 'add' ? this.permissions.add(actor, qq) : this.permissions.remove(actor, qq));
160
+ });
161
+ }
162
+ async validateToken(authenticate, groupId) {
163
+ return this.queue.run(async () => {
164
+ await authenticate();
165
+ const repository = groupId ? this.repositories.resolve(groupId) : undefined;
166
+ if (groupId && !repository)
167
+ throw new errors_1.BridgeError('binding', '所选群没有启用的仓库绑定。');
168
+ return this.github.validate(repository);
169
+ });
170
+ }
171
+ async cleanup() {
172
+ try {
173
+ await this.queue.run(async () => {
174
+ this.pruneConfirmations();
175
+ await this.storage.cleanup();
176
+ });
177
+ }
178
+ catch {
179
+ this.ctx.logger('qq-github-bridge').warn('过期记录清理未完成,将在下一周期重试。');
180
+ }
181
+ }
182
+ }
183
+ exports.Bridge = Bridge;
184
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/handlers/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Session } from 'koishi';
2
+ import type { IncomingMessage } from '../types';
3
+ import type { RepositoryResolver } from '../services/repository';
4
+ export declare function readMessage(session: Session, resolver: RepositoryResolver, window: number, now?: number): IncomingMessage | null;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readMessage = readMessage;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const config_1 = require("../config");
6
+ function numericId(value) {
7
+ if (typeof value === 'number' && !Number.isSafeInteger(value))
8
+ return;
9
+ if (typeof value !== 'string' && typeof value !== 'number')
10
+ return;
11
+ const text = String(value);
12
+ if (config_1.QQ_PATTERN.test(text))
13
+ return text;
14
+ }
15
+ function readMessage(session, resolver, window, now = Date.now()) {
16
+ // Satori 4 normalizes the legacy 'message' event to 'message-created' at dispatch.
17
+ if (session.platform !== 'onebot' || !['message', 'message-created'].includes(session.type) || session.isDirect || !session.guildId)
18
+ return null;
19
+ // setInternal('onebot', payload) is how the official OneBot adapter exposes raw events.
20
+ const raw = session.onebot;
21
+ if (!raw || raw.post_type !== 'message' || raw.message_type !== 'group' || raw.anonymous || raw.sub_type === 'anonymous')
22
+ return null;
23
+ const qq = numericId(raw.user_id);
24
+ const sender = numericId(raw.sender?.user_id);
25
+ const groupId = numericId(raw.group_id);
26
+ if (!qq || qq !== sender || qq !== session.userId || !groupId || groupId !== session.guildId || groupId !== session.channelId)
27
+ return null;
28
+ if (numericId(raw.self_id) !== session.selfId || qq === session.selfId)
29
+ return null;
30
+ if (session.bot.ctx.bots.some(bot => bot.platform === 'onebot' && bot.selfId === qq))
31
+ return null;
32
+ const repository = resolver.resolve(groupId);
33
+ if (!repository)
34
+ return null;
35
+ const messageId = raw.message_id;
36
+ if ((typeof messageId !== 'string' && typeof messageId !== 'number') ||
37
+ !/^-?[0-9]{1,30}$/.test(String(messageId)) || (typeof messageId === 'number' && !Number.isSafeInteger(messageId)))
38
+ return null;
39
+ if (String(messageId) !== session.messageId)
40
+ return null;
41
+ const timestamp = typeof raw.time === 'number' && Number.isSafeInteger(raw.time) ? raw.time * 1000 : 0;
42
+ if (!timestamp || now - timestamp > window || timestamp - now > 60000)
43
+ return null;
44
+ // Parse decoded text elements, never serialized Satori markup or raw CQ codes.
45
+ // Attachments / quotes / mentions are ignored as a whole so nothing is silently discarded.
46
+ if (session.quote || !session.elements?.length || session.elements.some(element => element.type !== 'text'))
47
+ return null;
48
+ const text = session.elements.map(element => element.attrs.content).join('');
49
+ const eventKey = (0, node_crypto_1.createHash)('sha256').update(`onebot:${groupId}:${messageId}`).digest('hex');
50
+ return {
51
+ actor: {
52
+ source: 'qq', id: qq, qq, groupId, repository: repository.fullName,
53
+ nickname: raw.sender?.nickname || raw.sender?.card || qq,
54
+ },
55
+ repository, eventKey, timestamp, text,
56
+ };
57
+ }
58
+ //# sourceMappingURL=message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/handlers/message.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './config';
3
+ export { Config } from './config';
4
+ export declare const name = "qq-github-bridge";
5
+ export declare const inject: string[];
6
+ export declare const usage = "\n\u901A\u8FC7 OneBot / NapCat QQ \u7FA4\u8BC4\u8BBA\u3001\u5173\u95ED\u548C\u91CD\u5F00 GitHub Issue / PR\u3002\n\n1. \u542F\u7528\u6301\u4E45\u6570\u636E\u5E93\uFF08\u63A8\u8350 database-sqlite\uFF09\u3001OneBot \u9002\u914D\u5668\u3002\n2. \u586B\u5199\u7EDF\u4E00 GitHub Token\u3001\u81F3\u5C11\u4E00\u4F4D\u8D85\u7EA7\u7BA1\u7406\u5458\u3001\u7FA4\u4E0E\u4ED3\u5E93\u7ED1\u5B9A\u3002\n3. \u542F\u7528\u5B98\u65B9 console \u548C auth\uFF0C\u5E76\u7528\u90E8\u7F72\u7BA1\u7406\u5458\u8D26\u53F7\u767B\u5F55\uFF0C\u5DE6\u4FA7\u5373\u53EF\u6253\u5F00\u300CQQ GitHub Bridge\u300D\u7EF4\u62A4\u7BA1\u7406\u5458\u3001\u67E5\u770B\u5BA1\u8BA1\u548C\u9A8C\u8BC1 Token\u3002\n\n\u9ED8\u8BA4\u9AD8\u98CE\u9669\u64CD\u4F5C\u9700\u8981\u540C\u4E00 QQ \u5728\u539F\u7FA4\u53D1\u9001\u786E\u8BA4\u7801\u3002\u8BE6\u7EC6\u547D\u4EE4\u4E0E\u6743\u9650\u8BF4\u660E\u89C1 README\u3002\n";
7
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usage = exports.inject = exports.name = exports.Config = void 0;
4
+ exports.apply = apply;
5
+ const koishi_1 = require("koishi");
6
+ const config_1 = require("./config");
7
+ const models_1 = require("./database/models");
8
+ const console_1 = require("./console");
9
+ const commands_1 = require("./handlers/commands");
10
+ const message_1 = require("./handlers/message");
11
+ var config_2 = require("./config");
12
+ Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return config_2.Config; } });
13
+ exports.name = 'qq-github-bridge';
14
+ exports.inject = ['database', 'http'];
15
+ exports.usage = `
16
+ 通过 OneBot / NapCat QQ 群评论、关闭和重开 GitHub Issue / PR。
17
+
18
+ 1. 启用持久数据库(推荐 database-sqlite)、OneBot 适配器。
19
+ 2. 填写统一 GitHub Token、至少一位超级管理员、群与仓库绑定。
20
+ 3. 启用官方 console 和 auth,并用部署管理员账号登录,左侧即可打开「QQ GitHub Bridge」维护管理员、查看审计和验证 Token。
21
+
22
+ 默认高风险操作需要同一 QQ 在原群发送确认码。详细命令与权限说明见 README。
23
+ `;
24
+ function apply(ctx, config) {
25
+ (0, config_1.validateConfig)(config);
26
+ (0, models_1.registerModels)(ctx);
27
+ const bridge = new commands_1.Bridge(ctx, config);
28
+ ctx.middleware(async (session, next) => {
29
+ const message = (0, message_1.readMessage)(session, bridge.repositories, config.dedupWindow);
30
+ if (!message)
31
+ return next();
32
+ const reply = await bridge.handle(message);
33
+ if (reply === null)
34
+ return next();
35
+ // Treat responses as literal text, so API links / database fields cannot inject CQ elements.
36
+ if (reply)
37
+ return koishi_1.h.text(reply);
38
+ }, true);
39
+ (0, console_1.registerConsole)(ctx, bridge);
40
+ ctx.on('ready', () => bridge.cleanup());
41
+ ctx.setInterval(() => void bridge.cleanup(), 3600000);
42
+ ctx.on('dispose', () => bridge.dispose());
43
+ }
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAoBA,sBAgBC;AApCD,mCAAmC;AACnC,qCAAiD;AACjD,8CAAkD;AAClD,uCAA2C;AAC3C,kDAA4C;AAC5C,gDAAgD;AAEhD,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACF,QAAA,IAAI,GAAG,kBAAkB,CAAA;AACzB,QAAA,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;AAC7B,QAAA,KAAK,GAAG;;;;;;;;CAQpB,CAAA;AAED,SAAgB,KAAK,CAAC,GAAY,EAAE,MAAc;IAChD,IAAA,uBAAc,EAAC,MAAM,CAAC,CAAA;IACtB,IAAA,uBAAc,EAAC,GAAG,CAAC,CAAA;IACnB,MAAM,MAAM,GAAG,IAAI,iBAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACtC,GAAG,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,IAAA,qBAAW,EAAC,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;QAC7E,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,EAAE,CAAA;QAC3B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1C,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,EAAE,CAAA;QACjC,6FAA6F;QAC7F,IAAI,KAAK;YAAE,OAAO,UAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjC,CAAC,EAAE,IAAI,CAAC,CAAA;IACR,IAAA,yBAAe,EAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC5B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IACvC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAA;IACrD,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;AAC3C,CAAC"}
package/lib/rpc.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { Dashboard, TokenStatus } from './types';
2
+ declare module '@koishijs/console' {
3
+ interface Events {
4
+ 'qq-github-bridge/dashboard'(this: Client, page?: number): Promise<Dashboard>;
5
+ 'qq-github-bridge/admin'(this: Client, action: 'add' | 'remove', qq: string): Promise<string>;
6
+ 'qq-github-bridge/validate'(this: Client, groupId?: string): Promise<TokenStatus>;
7
+ }
8
+ }
package/lib/rpc.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=rpc.js.map
package/lib/rpc.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import type { Context } from 'koishi';
2
+ import type { Config } from '../config';
3
+ import type { Actor, Outcome, Repository, TokenStatus } from '../types';
4
+ export declare class GitHub {
5
+ private readonly ctx;
6
+ private readonly config;
7
+ private readonly abort;
8
+ private readonly base;
9
+ private blockedUntil;
10
+ private lastMutation;
11
+ constructor(ctx: Context, config: Config);
12
+ dispose(): void;
13
+ private sleep;
14
+ private request;
15
+ private path;
16
+ private link;
17
+ private target;
18
+ formatComment(actor: Actor, body: string, eventKey: string): string;
19
+ comment(repo: Repository, number: number, actor: Actor, body: string, eventKey: string): Promise<Outcome>;
20
+ state(repo: Repository, number: number, desired: 'open' | 'closed'): Promise<Outcome>;
21
+ validate(repo?: Repository): Promise<TokenStatus>;
22
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHub = void 0;
4
+ const promises_1 = require("node:timers/promises");
5
+ const errors_1 = require("../utils/errors");
6
+ function metadata(value) {
7
+ return value.slice(0, 80).replace(/[\r\n\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2066-\u2069]/g, ' ')
8
+ .replace(/[&<>"'`*_\[\]{}()|@#!\\]/g, character => `&#${character.codePointAt(0)};`);
9
+ }
10
+ class GitHub {
11
+ ctx;
12
+ config;
13
+ abort = new AbortController();
14
+ base;
15
+ blockedUntil = 0;
16
+ lastMutation = 0;
17
+ constructor(ctx, config) {
18
+ this.ctx = ctx;
19
+ this.config = config;
20
+ this.base = config.apiBaseUrl.replace(/\/+$/, '');
21
+ }
22
+ dispose() { this.abort.abort(); }
23
+ async sleep(ms) {
24
+ try {
25
+ await (0, promises_1.setTimeout)(ms, undefined, { signal: this.abort.signal });
26
+ }
27
+ catch {
28
+ throw new errors_1.BridgeError('disposed', '插件正在停用,请稍后再试。');
29
+ }
30
+ }
31
+ async request(method, path, data) {
32
+ if (this.abort.signal.aborted)
33
+ throw new errors_1.BridgeError('disposed', '插件正在停用,请稍后再试。');
34
+ if (this.blockedUntil > Date.now())
35
+ throw new errors_1.BridgeError('rate-limit', `GitHub 限流中,请在约 ${Math.ceil((this.blockedUntil - Date.now()) / 1000)} 秒后再试。`);
36
+ const writing = method !== 'GET';
37
+ if (writing) {
38
+ const wait = 1000 - (Date.now() - this.lastMutation);
39
+ if (wait > 0)
40
+ await this.sleep(wait);
41
+ this.lastMutation = Date.now();
42
+ }
43
+ for (let attempt = 0;; attempt++) {
44
+ let response;
45
+ try {
46
+ response = await this.ctx.http(this.base + path, {
47
+ method, data,
48
+ headers: {
49
+ Authorization: `Bearer ${this.config.token}`,
50
+ Accept: 'application/vnd.github+json',
51
+ 'X-GitHub-Api-Version': this.config.apiVersion,
52
+ 'User-Agent': 'koishi-plugin-qq-github-bridge/1.0.0',
53
+ },
54
+ timeout: this.config.requestTimeout,
55
+ signal: this.abort.signal,
56
+ // Never send credentials or execute writes at a redirected/unbound repository.
57
+ redirect: 'manual',
58
+ validateStatus: () => true,
59
+ });
60
+ }
61
+ catch {
62
+ if (!writing && attempt < this.config.readRetries && !this.abort.signal.aborted) {
63
+ await this.sleep(300 * 2 ** attempt);
64
+ continue;
65
+ }
66
+ throw new errors_1.BridgeError(writing ? 'write-unknown' : 'network', writing
67
+ ? 'GitHub 写入请求未得到确认(网络超时、连接失败或插件停用)。操作可能已成功;请先到 GitHub 核对,不要直接重发。'
68
+ : '无法连接 GitHub(超时、DNS 或连接故障),请稍后重试或联系部署者。', writing);
69
+ }
70
+ if (response.status >= 200 && response.status < 300)
71
+ return response.data;
72
+ const status = response.status;
73
+ const getHeader = (name) => response.headers?.get?.(name);
74
+ const serverMessage = response.data?.message;
75
+ const rateLimited = status === 429 || (status === 403 && (getHeader('x-ratelimit-remaining') === '0' ||
76
+ !!getHeader('retry-after') || (typeof serverMessage === 'string' && /rate limit|abuse/i.test(serverMessage))));
77
+ if (rateLimited) {
78
+ const retryHeader = getHeader('retry-after');
79
+ const seconds = Number(retryHeader);
80
+ const retryAt = retryHeader && !Number.isFinite(seconds) ? Date.parse(retryHeader) : Date.now() + seconds * 1000;
81
+ const resetAt = getHeader('x-ratelimit-remaining') === '0' ? Number(getHeader('x-ratelimit-reset')) * 1000 : 0;
82
+ this.blockedUntil = Math.max(Date.now() + 60000, Number.isFinite(retryAt) ? retryAt : 0, Number.isFinite(resetAt) ? resetAt : 0);
83
+ throw new errors_1.BridgeError('rate-limit', 'GitHub 请求限流,已暂停后续请求,请稍后再试。');
84
+ }
85
+ if (status >= 500) {
86
+ if (!writing && attempt < this.config.readRetries) {
87
+ await this.sleep(300 * 2 ** attempt);
88
+ continue;
89
+ }
90
+ throw new errors_1.BridgeError(writing ? 'write-unknown' : 'github-server', writing
91
+ ? 'GitHub 服务端异常,写入结果无法确认。请先到 GitHub 核对,插件不会自动重试。'
92
+ : 'GitHub 服务端暂时异常,有限重试后仍未恢复。', writing);
93
+ }
94
+ const errors = {
95
+ 401: ['token-invalid', 'GitHub Token 无效或已过期,请部署者在后台更新。'],
96
+ 403: ['github-forbidden', 'GitHub 拒绝访问:Token 权限不足、仓库或组织访问受限;请部署者检查权限和组织审批。'],
97
+ 404: ['not-found', 'GitHub 仓库或目标不存在,或当前 Token 无权访问。'],
98
+ 410: ['gone', '目标功能或资源已不可用(例如仓库关闭了 Issues)。'],
99
+ 422: ['validation', 'GitHub 拒绝请求:数据不合法、触发反滥用限制,或当前目标不允许此状态转换。请在 GitHub 核对目标状态和 PR 源分支。'],
100
+ };
101
+ if (status >= 300 && status < 400)
102
+ throw new errors_1.BridgeError('redirect', 'GitHub 返回重定向;为保持群绑定边界已停止请求,请部署者更新仓库绑定或 API 地址。');
103
+ const [code, message] = errors[status] || ['github-error', `GitHub 请求失败(HTTP ${status})。请部署者检查配置。`];
104
+ throw new errors_1.BridgeError(code, message);
105
+ }
106
+ }
107
+ path(repo) {
108
+ return `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}`;
109
+ }
110
+ link(value) {
111
+ if (typeof value !== 'string' || value.length > 2000 || value.includes(this.config.token))
112
+ return;
113
+ try {
114
+ const url = new URL(value);
115
+ // html_url is returned by the trusted API, never used as a request target.
116
+ // Proxies / Enterprise Cloud can have a different API and web hostname.
117
+ if (url.username || url.password || url.search || decodeURIComponent(value).includes(this.config.token))
118
+ return;
119
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)))
120
+ return;
121
+ return url.href;
122
+ }
123
+ catch {
124
+ return;
125
+ }
126
+ }
127
+ async target(repo, number) {
128
+ const issue = await this.request('GET', `${this.path(repo)}/issues/${number}`);
129
+ if (!issue || issue.number !== number || !['open', 'closed'].includes(issue.state)) {
130
+ throw new errors_1.BridgeError('github-response', 'GitHub 返回了不符合预期的目标数据,已停止操作。');
131
+ }
132
+ return issue;
133
+ }
134
+ formatComment(actor, body, eventKey) {
135
+ if (body.includes(this.config.token))
136
+ throw new errors_1.BridgeError('secret-in-body', '评论包含部署 Token,已拒绝发送。请移除敏感信息。');
137
+ const nickname = metadata((0, errors_1.redact)(actor.nickname, this.config.token));
138
+ const header = `<sub>QQ Bridge 已验证发送者 · 昵称:「${nickname}」 · QQ:${actor.qq} · QQ群:${actor.groupId}</sub>`;
139
+ const comment = `${header}\n\n---\n\n${this.config.commentTemplate.replace('{body}', () => body)}\n\n<!-- qq-github-bridge:${eventKey} -->`;
140
+ // GitHub imposes its own limit; also bound bytes to safely accommodate Unicode text.
141
+ if (comment.length > 65000 || Buffer.byteLength(comment, 'utf8') > 65000) {
142
+ throw new errors_1.BridgeError('comment-too-large', '渲染后的评论过长,请缩短正文或评论模板。');
143
+ }
144
+ return comment;
145
+ }
146
+ async comment(repo, number, actor, body, eventKey) {
147
+ const comment = this.formatComment(actor, body, eventKey);
148
+ const issue = await this.target(repo, number);
149
+ const result = await this.request('POST', `${this.path(repo)}/issues/${number}/comments`, { body: comment });
150
+ const link = this.link(result?.html_url);
151
+ if (!link || !Number.isSafeInteger(result?.id) || result.id <= 0) {
152
+ throw new errors_1.BridgeError('write-unknown', 'GitHub 返回了成功状态,但评论标识或链接不完整,无法确认评论结果。请先到 GitHub 核对,不要直接重发。', true);
153
+ }
154
+ return {
155
+ message: `已在 ${repo.fullName} ${issue.pull_request ? 'PR' : 'Issue'} #${number} 发布评论。\n${link}`,
156
+ result: 'success',
157
+ };
158
+ }
159
+ async state(repo, number, desired) {
160
+ let target = await this.target(repo, number);
161
+ const isPR = !!target.pull_request;
162
+ if (isPR) {
163
+ target = await this.request('GET', `${this.path(repo)}/pulls/${number}`);
164
+ if (!target || target.number !== number || !['open', 'closed'].includes(target.state) || typeof target.merged !== 'boolean') {
165
+ throw new errors_1.BridgeError('github-response', 'GitHub 返回了不符合预期的 PR 状态,已停止操作。');
166
+ }
167
+ if (desired === 'open' && target.merged)
168
+ throw new errors_1.BridgeError('pr-merged', '该 PR 已合并,不能重新打开。');
169
+ }
170
+ const label = `${repo.fullName} ${isPR ? 'PR' : 'Issue'} #${number}`;
171
+ if (target.state === desired)
172
+ return { message: `${label} 当前已经${desired === 'open' ? '打开' : '关闭'},无需重复操作。`, result: 'noop' };
173
+ const updated = await this.request('PATCH', `${this.path(repo)}/${isPR ? 'pulls' : 'issues'}/${number}`, { state: desired });
174
+ if (updated?.number !== number || updated?.state !== desired) {
175
+ throw new errors_1.BridgeError('write-unknown', 'GitHub 未确认预期状态,操作结果不确定;请到 GitHub 核对。', true);
176
+ }
177
+ return { message: `已${desired === 'open' ? '重新打开' : '关闭'} ${label}。`, result: 'success' };
178
+ }
179
+ async validate(repo) {
180
+ const user = await this.request('GET', '/user');
181
+ if (!user || !/^[A-Za-z0-9][A-Za-z0-9-]{0,99}$/.test(user.login))
182
+ throw new errors_1.BridgeError('github-response', 'Token 验证响应无效,请检查 API 地址。');
183
+ const repositories = [];
184
+ if (repo) {
185
+ try {
186
+ const data = await this.request('GET', this.path(repo));
187
+ if (data?.full_name?.toLowerCase() !== repo.fullName.toLowerCase())
188
+ throw new errors_1.BridgeError('repo-mismatch', '仓库响应与群绑定不一致。');
189
+ repositories.push({ repository: repo.fullName, accessible: true, message: '仓库可读取。' });
190
+ }
191
+ catch (error) {
192
+ if (!(error instanceof errors_1.BridgeError))
193
+ throw error;
194
+ repositories.push({ repository: repo.fullName, accessible: false, message: error.message });
195
+ }
196
+ }
197
+ return { login: (0, errors_1.redact)(user.login, this.config.token), repositories, message: 'Token 身份已验证。只读检测不能证明写权限;Issues 和 Pull requests 均需 Read and write。' };
198
+ }
199
+ }
200
+ exports.GitHub = GitHub;
201
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/services/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Context } from 'koishi';
2
+ import type { Config } from '../config';
3
+ import type { Actor, Outcome } from '../types';
4
+ export declare class Permissions {
5
+ private readonly ctx;
6
+ private readonly superAdmins;
7
+ constructor(ctx: Context, config: Config);
8
+ requireAdmin(actor: Actor, superOnly?: boolean): Promise<void>;
9
+ add(actor: Actor, qq: string): Promise<Outcome>;
10
+ remove(actor: Actor, qq: string): Promise<Outcome>;
11
+ list(offset?: number, limit?: number): Promise<import("minato").FlatPick<import("../database/models").AdminRow, any>[]>;
12
+ }