koishi-plugin-github-webhook-pusher 0.0.6 → 0.0.8
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.
- package/lib/commands/index.d.ts +6 -0
- package/lib/commands/index.js +12 -0
- package/lib/commands/subscription.d.ts +12 -0
- package/lib/commands/subscription.js +214 -0
- package/lib/commands/trust.d.ts +10 -0
- package/lib/commands/trust.js +119 -0
- package/lib/commands/utils.d.ts +12 -0
- package/lib/commands/utils.js +182 -0
- package/lib/config.d.ts +20 -0
- package/lib/config.js +30 -0
- package/lib/database.d.ts +35 -0
- package/lib/database.js +39 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +53 -0
- package/lib/message.d.ts +12 -0
- package/lib/message.js +250 -0
- package/lib/parser.d.ts +65 -0
- package/lib/parser.js +304 -0
- package/lib/pusher.d.ts +55 -0
- package/lib/pusher.js +128 -0
- package/lib/repository/delivery.d.ts +38 -0
- package/lib/repository/delivery.js +63 -0
- package/lib/repository/index.d.ts +6 -0
- package/lib/repository/index.js +22 -0
- package/lib/repository/subscription.d.ts +99 -0
- package/lib/repository/subscription.js +187 -0
- package/lib/repository/trust.d.ts +73 -0
- package/lib/repository/trust.js +133 -0
- package/lib/signature.d.ts +15 -0
- package/lib/signature.js +38 -0
- package/lib/types.d.ts +49 -0
- package/lib/types.js +40 -0
- package/lib/webhook.d.ts +21 -0
- package/lib/webhook.js +153 -0
- package/package.json +5 -2
package/lib/parser.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub Webhook 事件解析器
|
|
4
|
+
* 需求: 4.1-4.7
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseIssuesEvent = parseIssuesEvent;
|
|
8
|
+
exports.parseIssueCommentEvent = parseIssueCommentEvent;
|
|
9
|
+
exports.parseReleaseEvent = parseReleaseEvent;
|
|
10
|
+
exports.parsePushEvent = parsePushEvent;
|
|
11
|
+
exports.parsePullRequestEvent = parsePullRequestEvent;
|
|
12
|
+
exports.parsePullRequestReviewEvent = parsePullRequestReviewEvent;
|
|
13
|
+
exports.parsePullRequestReviewCommentEvent = parsePullRequestReviewCommentEvent;
|
|
14
|
+
exports.parseStarEvent = parseStarEvent;
|
|
15
|
+
exports.parseForkEvent = parseForkEvent;
|
|
16
|
+
exports.parseCreateEvent = parseCreateEvent;
|
|
17
|
+
exports.parseDeleteEvent = parseDeleteEvent;
|
|
18
|
+
exports.parseWorkflowRunEvent = parseWorkflowRunEvent;
|
|
19
|
+
exports.parseEvent = parseEvent;
|
|
20
|
+
const types_1 = require("./types");
|
|
21
|
+
/** Push 事件最大显示提交数 */
|
|
22
|
+
const MAX_COMMITS = 3;
|
|
23
|
+
/**
|
|
24
|
+
* 解析 Issues 事件
|
|
25
|
+
* 需求 4.1: 提取 issue 标题、编号、操作者和链接
|
|
26
|
+
*/
|
|
27
|
+
function parseIssuesEvent(payload) {
|
|
28
|
+
const { action, issue, repository, sender } = payload;
|
|
29
|
+
// 只处理 opened/closed/reopened/edited 动作
|
|
30
|
+
if (!['opened', 'closed', 'reopened', 'edited'].includes(action)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
type: 'issues',
|
|
35
|
+
displayType: (0, types_1.getDisplayType)('issues'),
|
|
36
|
+
repo: repository.full_name,
|
|
37
|
+
actor: sender.login,
|
|
38
|
+
action,
|
|
39
|
+
title: issue.title,
|
|
40
|
+
number: issue.number,
|
|
41
|
+
url: issue.html_url,
|
|
42
|
+
body: issue.body,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 解析 Issue Comment 事件
|
|
47
|
+
*/
|
|
48
|
+
function parseIssueCommentEvent(payload) {
|
|
49
|
+
const { action, issue, comment, repository, sender } = payload;
|
|
50
|
+
// 只处理 created/edited/deleted 动作
|
|
51
|
+
if (!['created', 'edited', 'deleted'].includes(action)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
type: 'issue_comment',
|
|
56
|
+
displayType: (0, types_1.getDisplayType)('issue_comment'),
|
|
57
|
+
repo: repository.full_name,
|
|
58
|
+
actor: sender.login,
|
|
59
|
+
action,
|
|
60
|
+
title: issue.title,
|
|
61
|
+
number: issue.number,
|
|
62
|
+
url: comment?.html_url || issue.html_url,
|
|
63
|
+
body: comment?.body,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 解析 Release 事件
|
|
68
|
+
* 需求 4.2: 提取版本号、发布者和下载链接
|
|
69
|
+
*/
|
|
70
|
+
function parseReleaseEvent(payload) {
|
|
71
|
+
const { action, release, repository, sender } = payload;
|
|
72
|
+
// 只处理 published/created 动作
|
|
73
|
+
if (!['published', 'created'].includes(action)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
type: 'release',
|
|
78
|
+
displayType: (0, types_1.getDisplayType)('release'),
|
|
79
|
+
repo: repository.full_name,
|
|
80
|
+
actor: sender.login,
|
|
81
|
+
action,
|
|
82
|
+
title: release.name || release.tag_name,
|
|
83
|
+
tagName: release.tag_name,
|
|
84
|
+
url: release.html_url,
|
|
85
|
+
body: release.body,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 解析 Push 事件
|
|
90
|
+
* 需求 4.3, 4.6: 提取分支名、提交列表(最多5条)和推送者信息
|
|
91
|
+
*/
|
|
92
|
+
function parsePushEvent(payload) {
|
|
93
|
+
const { ref, commits, repository, sender, compare } = payload;
|
|
94
|
+
// 提取分支名(去掉 refs/heads/ 前缀)
|
|
95
|
+
const branch = ref?.replace('refs/heads/', '') || '';
|
|
96
|
+
// 解析提交列表
|
|
97
|
+
const allCommits = (commits || []).map((commit) => ({
|
|
98
|
+
sha: commit.id?.substring(0, 7) || '',
|
|
99
|
+
message: commit.message?.split('\n')[0] || '', // 只取第一行
|
|
100
|
+
author: commit.author?.name || commit.author?.username || '',
|
|
101
|
+
url: commit.url || '',
|
|
102
|
+
}));
|
|
103
|
+
// 截断到最多 MAX_COMMITS 条
|
|
104
|
+
const displayCommits = allCommits.slice(0, MAX_COMMITS);
|
|
105
|
+
return {
|
|
106
|
+
type: 'push',
|
|
107
|
+
displayType: (0, types_1.getDisplayType)('push'), // 显示为 "Commit"
|
|
108
|
+
repo: repository.full_name,
|
|
109
|
+
actor: sender.login,
|
|
110
|
+
ref: branch,
|
|
111
|
+
commits: displayCommits,
|
|
112
|
+
totalCommits: allCommits.length,
|
|
113
|
+
url: compare || `https://github.com/${repository.full_name}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 解析 Pull Request 事件
|
|
118
|
+
* 需求 4.4: 提取 PR 标题、编号、操作者和链接
|
|
119
|
+
*/
|
|
120
|
+
function parsePullRequestEvent(payload) {
|
|
121
|
+
const { action, pull_request, repository, sender } = payload;
|
|
122
|
+
// 只处理 opened/closed/merged 动作
|
|
123
|
+
// 注意: merged 实际上是 closed + merged 标志
|
|
124
|
+
const validActions = ['opened', 'closed', 'reopened', 'edited'];
|
|
125
|
+
if (!validActions.includes(action)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
// 判断是否为合并
|
|
129
|
+
const actualAction = action === 'closed' && pull_request.merged ? 'merged' : action;
|
|
130
|
+
return {
|
|
131
|
+
type: 'pull_request',
|
|
132
|
+
displayType: (0, types_1.getDisplayType)('pull_request'),
|
|
133
|
+
repo: repository.full_name,
|
|
134
|
+
actor: sender.login,
|
|
135
|
+
action: actualAction,
|
|
136
|
+
title: pull_request.title,
|
|
137
|
+
number: pull_request.number,
|
|
138
|
+
url: pull_request.html_url,
|
|
139
|
+
body: pull_request.body,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 解析 Pull Request Review 事件
|
|
144
|
+
*/
|
|
145
|
+
function parsePullRequestReviewEvent(payload) {
|
|
146
|
+
const { action, review, pull_request, repository, sender } = payload;
|
|
147
|
+
// 只处理 submitted/edited/dismissed 动作
|
|
148
|
+
if (!['submitted', 'edited', 'dismissed'].includes(action)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
type: 'pull_request_review',
|
|
153
|
+
displayType: (0, types_1.getDisplayType)('pull_request_review'),
|
|
154
|
+
repo: repository.full_name,
|
|
155
|
+
actor: sender.login,
|
|
156
|
+
action: review?.state || action,
|
|
157
|
+
title: pull_request.title,
|
|
158
|
+
number: pull_request.number,
|
|
159
|
+
url: review?.html_url || pull_request.html_url,
|
|
160
|
+
body: review?.body,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 解析 Pull Request Review Comment 事件
|
|
165
|
+
*/
|
|
166
|
+
function parsePullRequestReviewCommentEvent(payload) {
|
|
167
|
+
const { action, comment, pull_request, repository, sender } = payload;
|
|
168
|
+
// 只处理 created/edited/deleted 动作
|
|
169
|
+
if (!['created', 'edited', 'deleted'].includes(action)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
type: 'pull_request_review_comment',
|
|
174
|
+
displayType: (0, types_1.getDisplayType)('pull_request_review_comment'),
|
|
175
|
+
repo: repository.full_name,
|
|
176
|
+
actor: sender.login,
|
|
177
|
+
action,
|
|
178
|
+
title: pull_request.title,
|
|
179
|
+
number: pull_request.number,
|
|
180
|
+
url: comment?.html_url || pull_request.html_url,
|
|
181
|
+
body: comment?.body,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 解析 Star 事件
|
|
186
|
+
* 需求 4.5: 提取操作者和当前 star 数量
|
|
187
|
+
*/
|
|
188
|
+
function parseStarEvent(payload) {
|
|
189
|
+
const { action, repository, sender } = payload;
|
|
190
|
+
// star 事件只有 created 和 deleted 动作
|
|
191
|
+
if (!['created', 'deleted'].includes(action)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
type: 'star',
|
|
196
|
+
displayType: (0, types_1.getDisplayType)('star'),
|
|
197
|
+
repo: repository.full_name,
|
|
198
|
+
actor: sender.login,
|
|
199
|
+
action,
|
|
200
|
+
starCount: repository.stargazers_count,
|
|
201
|
+
url: repository.html_url,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 解析 Fork 事件
|
|
206
|
+
*/
|
|
207
|
+
function parseForkEvent(payload) {
|
|
208
|
+
const { forkee, repository, sender } = payload;
|
|
209
|
+
return {
|
|
210
|
+
type: 'fork',
|
|
211
|
+
displayType: (0, types_1.getDisplayType)('fork'),
|
|
212
|
+
repo: repository.full_name,
|
|
213
|
+
actor: sender.login,
|
|
214
|
+
action: 'forked',
|
|
215
|
+
title: forkee?.full_name,
|
|
216
|
+
url: forkee?.html_url || repository.html_url,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 解析 Create 事件
|
|
221
|
+
*/
|
|
222
|
+
function parseCreateEvent(payload) {
|
|
223
|
+
const { ref, ref_type, repository, sender } = payload;
|
|
224
|
+
return {
|
|
225
|
+
type: 'create',
|
|
226
|
+
displayType: (0, types_1.getDisplayType)('create'),
|
|
227
|
+
repo: repository.full_name,
|
|
228
|
+
actor: sender.login,
|
|
229
|
+
action: 'created',
|
|
230
|
+
ref: ref ? `${ref_type}:${ref}` : ref_type,
|
|
231
|
+
url: repository.html_url,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 解析 Delete 事件
|
|
236
|
+
*/
|
|
237
|
+
function parseDeleteEvent(payload) {
|
|
238
|
+
const { ref, ref_type, repository, sender } = payload;
|
|
239
|
+
return {
|
|
240
|
+
type: 'delete',
|
|
241
|
+
displayType: (0, types_1.getDisplayType)('delete'),
|
|
242
|
+
repo: repository.full_name,
|
|
243
|
+
actor: sender.login,
|
|
244
|
+
action: 'deleted',
|
|
245
|
+
ref: ref ? `${ref_type}:${ref}` : ref_type,
|
|
246
|
+
url: repository.html_url,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* 解析 Workflow Run 事件
|
|
251
|
+
*/
|
|
252
|
+
function parseWorkflowRunEvent(payload) {
|
|
253
|
+
const { action, workflow_run, repository, sender } = payload;
|
|
254
|
+
// 只处理 requested/completed 动作
|
|
255
|
+
if (!['requested', 'completed'].includes(action)) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const conclusion = workflow_run?.conclusion ? `/${workflow_run.conclusion}` : '';
|
|
259
|
+
return {
|
|
260
|
+
type: 'workflow_run',
|
|
261
|
+
displayType: (0, types_1.getDisplayType)('workflow_run'),
|
|
262
|
+
repo: repository.full_name,
|
|
263
|
+
actor: sender.login,
|
|
264
|
+
action: `${action}${conclusion}`,
|
|
265
|
+
title: workflow_run?.name,
|
|
266
|
+
url: workflow_run?.html_url || repository.html_url,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 统一的事件解析入口函数
|
|
271
|
+
* @param eventName X-GitHub-Event 头的值
|
|
272
|
+
* @param payload 解析后的 JSON 负载
|
|
273
|
+
* @returns 解析后的事件数据,不支持的事件返回 null
|
|
274
|
+
*/
|
|
275
|
+
function parseEvent(eventName, payload) {
|
|
276
|
+
switch (eventName) {
|
|
277
|
+
case 'issues':
|
|
278
|
+
return parseIssuesEvent(payload);
|
|
279
|
+
case 'issue_comment':
|
|
280
|
+
return parseIssueCommentEvent(payload);
|
|
281
|
+
case 'release':
|
|
282
|
+
return parseReleaseEvent(payload);
|
|
283
|
+
case 'push':
|
|
284
|
+
return parsePushEvent(payload);
|
|
285
|
+
case 'pull_request':
|
|
286
|
+
return parsePullRequestEvent(payload);
|
|
287
|
+
case 'pull_request_review':
|
|
288
|
+
return parsePullRequestReviewEvent(payload);
|
|
289
|
+
case 'pull_request_review_comment':
|
|
290
|
+
return parsePullRequestReviewCommentEvent(payload);
|
|
291
|
+
case 'star':
|
|
292
|
+
return parseStarEvent(payload);
|
|
293
|
+
case 'fork':
|
|
294
|
+
return parseForkEvent(payload);
|
|
295
|
+
case 'create':
|
|
296
|
+
return parseCreateEvent(payload);
|
|
297
|
+
case 'delete':
|
|
298
|
+
return parseDeleteEvent(payload);
|
|
299
|
+
case 'workflow_run':
|
|
300
|
+
return parseWorkflowRunEvent(payload);
|
|
301
|
+
default:
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
package/lib/pusher.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息推送器
|
|
3
|
+
* 需求: 5.1, 5.2, 5.6, 5.7
|
|
4
|
+
*/
|
|
5
|
+
import { Context, Element } from 'koishi';
|
|
6
|
+
import { EventType, ParsedEvent } from './types';
|
|
7
|
+
import { PushTarget } from './repository/subscription';
|
|
8
|
+
/** 推送结果 */
|
|
9
|
+
export interface PushResult {
|
|
10
|
+
target: PushTarget;
|
|
11
|
+
success: boolean;
|
|
12
|
+
error?: Error;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 查询订阅目标
|
|
16
|
+
* 需求 5.1: 查询所有订阅该仓库的目标会话
|
|
17
|
+
* 需求 5.2: 根据订阅的事件类型过滤目标
|
|
18
|
+
* @param ctx Koishi 上下文
|
|
19
|
+
* @param repo 仓库名 (owner/repo)
|
|
20
|
+
* @param eventType 事件类型
|
|
21
|
+
* @returns 推送目标列表
|
|
22
|
+
*/
|
|
23
|
+
export declare function queryTargets(ctx: Context, repo: string, eventType: EventType): Promise<PushTarget[]>;
|
|
24
|
+
/**
|
|
25
|
+
* 并发推送消息到多个目标
|
|
26
|
+
* 需求 5.6: 使用 Promise.allSettled 并发推送并限制并发数
|
|
27
|
+
* 需求 5.7: 推送失败记录错误日志但不影响其他目标的推送
|
|
28
|
+
* @param ctx Koishi 上下文
|
|
29
|
+
* @param targets 推送目标列表
|
|
30
|
+
* @param message 消息内容
|
|
31
|
+
* @param concurrency 并发数限制
|
|
32
|
+
* @returns 推送结果列表
|
|
33
|
+
*/
|
|
34
|
+
export declare function pushWithConcurrency(ctx: Context, targets: PushTarget[], message: Element[], concurrency: number): Promise<PushResult[]>;
|
|
35
|
+
/**
|
|
36
|
+
* 推送消息到所有订阅目标
|
|
37
|
+
* @param ctx Koishi 上下文
|
|
38
|
+
* @param event 解析后的事件
|
|
39
|
+
* @param concurrency 并发数限制
|
|
40
|
+
* @returns 推送结果列表
|
|
41
|
+
*/
|
|
42
|
+
export declare function pushMessage(ctx: Context, event: ParsedEvent, concurrency?: number): Promise<PushResult[]>;
|
|
43
|
+
/**
|
|
44
|
+
* 推送事件(完整流程)
|
|
45
|
+
* 整合事件解析、消息构建和推送
|
|
46
|
+
* @param ctx Koishi 上下文
|
|
47
|
+
* @param event 解析后的事件
|
|
48
|
+
* @param concurrency 并发数限制
|
|
49
|
+
* @returns 推送结果
|
|
50
|
+
*/
|
|
51
|
+
export declare function pushEvent(ctx: Context, event: ParsedEvent, concurrency?: number): Promise<{
|
|
52
|
+
pushed: number;
|
|
53
|
+
failed: number;
|
|
54
|
+
results: PushResult[];
|
|
55
|
+
}>;
|
package/lib/pusher.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 消息推送器
|
|
4
|
+
* 需求: 5.1, 5.2, 5.6, 5.7
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.queryTargets = queryTargets;
|
|
8
|
+
exports.pushWithConcurrency = pushWithConcurrency;
|
|
9
|
+
exports.pushMessage = pushMessage;
|
|
10
|
+
exports.pushEvent = pushEvent;
|
|
11
|
+
const koishi_1 = require("koishi");
|
|
12
|
+
const subscription_1 = require("./repository/subscription");
|
|
13
|
+
const message_1 = require("./message");
|
|
14
|
+
const logger = new koishi_1.Logger('github-webhook-pusher');
|
|
15
|
+
/**
|
|
16
|
+
* 查询订阅目标
|
|
17
|
+
* 需求 5.1: 查询所有订阅该仓库的目标会话
|
|
18
|
+
* 需求 5.2: 根据订阅的事件类型过滤目标
|
|
19
|
+
* @param ctx Koishi 上下文
|
|
20
|
+
* @param repo 仓库名 (owner/repo)
|
|
21
|
+
* @param eventType 事件类型
|
|
22
|
+
* @returns 推送目标列表
|
|
23
|
+
*/
|
|
24
|
+
async function queryTargets(ctx, repo, eventType) {
|
|
25
|
+
return (0, subscription_1.queryTargets)(ctx, repo, eventType);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 发送消息到单个目标
|
|
29
|
+
* @param ctx Koishi 上下文
|
|
30
|
+
* @param target 推送目标
|
|
31
|
+
* @param message 消息内容
|
|
32
|
+
* @returns 推送结果
|
|
33
|
+
*/
|
|
34
|
+
async function sendMessage(ctx, target, message) {
|
|
35
|
+
try {
|
|
36
|
+
// 获取对应平台的 bot
|
|
37
|
+
const bot = ctx.bots.find(b => b.platform === target.platform);
|
|
38
|
+
if (!bot) {
|
|
39
|
+
throw new Error(`未找到平台 ${target.platform} 的 bot`);
|
|
40
|
+
}
|
|
41
|
+
// 发送消息到目标频道
|
|
42
|
+
await bot.sendMessage(target.channelId, message, target.guildId);
|
|
43
|
+
return { target, success: true };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
target,
|
|
48
|
+
success: false,
|
|
49
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 并发推送消息到多个目标
|
|
55
|
+
* 需求 5.6: 使用 Promise.allSettled 并发推送并限制并发数
|
|
56
|
+
* 需求 5.7: 推送失败记录错误日志但不影响其他目标的推送
|
|
57
|
+
* @param ctx Koishi 上下文
|
|
58
|
+
* @param targets 推送目标列表
|
|
59
|
+
* @param message 消息内容
|
|
60
|
+
* @param concurrency 并发数限制
|
|
61
|
+
* @returns 推送结果列表
|
|
62
|
+
*/
|
|
63
|
+
async function pushWithConcurrency(ctx, targets, message, concurrency) {
|
|
64
|
+
if (targets.length === 0) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const results = [];
|
|
68
|
+
const queue = [...targets];
|
|
69
|
+
// 创建工作协程
|
|
70
|
+
const workers = Array(Math.min(concurrency, queue.length))
|
|
71
|
+
.fill(null)
|
|
72
|
+
.map(async () => {
|
|
73
|
+
while (queue.length > 0) {
|
|
74
|
+
const target = queue.shift();
|
|
75
|
+
if (!target)
|
|
76
|
+
break;
|
|
77
|
+
const result = await sendMessage(ctx, target, message);
|
|
78
|
+
results.push(result);
|
|
79
|
+
// 需求 5.7: 推送失败记录错误日志但不影响其他目标
|
|
80
|
+
if (!result.success && result.error) {
|
|
81
|
+
logger.error(`推送失败 [${target.platform}:${target.channelId}]: ${result.error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
await Promise.all(workers);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 推送消息到所有订阅目标
|
|
90
|
+
* @param ctx Koishi 上下文
|
|
91
|
+
* @param event 解析后的事件
|
|
92
|
+
* @param concurrency 并发数限制
|
|
93
|
+
* @returns 推送结果列表
|
|
94
|
+
*/
|
|
95
|
+
async function pushMessage(ctx, event, concurrency = 5) {
|
|
96
|
+
// 查询订阅目标
|
|
97
|
+
const targets = await queryTargets(ctx, event.repo, event.type);
|
|
98
|
+
if (targets.length === 0) {
|
|
99
|
+
logger.debug(`仓库 ${event.repo} 的 ${event.type} 事件没有订阅目标`);
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
logger.info(`准备推送 ${event.type} 事件到 ${targets.length} 个目标`);
|
|
103
|
+
// 构建消息
|
|
104
|
+
const message = (0, message_1.buildMessage)(event);
|
|
105
|
+
// 并发推送
|
|
106
|
+
const results = await pushWithConcurrency(ctx, targets, message, concurrency);
|
|
107
|
+
// 统计结果
|
|
108
|
+
const successCount = results.filter(r => r.success).length;
|
|
109
|
+
const failCount = results.filter(r => !r.success).length;
|
|
110
|
+
logger.info(`推送完成: 成功 ${successCount}, 失败 ${failCount}`);
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 推送事件(完整流程)
|
|
115
|
+
* 整合事件解析、消息构建和推送
|
|
116
|
+
* @param ctx Koishi 上下文
|
|
117
|
+
* @param event 解析后的事件
|
|
118
|
+
* @param concurrency 并发数限制
|
|
119
|
+
* @returns 推送结果
|
|
120
|
+
*/
|
|
121
|
+
async function pushEvent(ctx, event, concurrency = 5) {
|
|
122
|
+
const results = await pushMessage(ctx, event, concurrency);
|
|
123
|
+
return {
|
|
124
|
+
pushed: results.filter(r => r.success).length,
|
|
125
|
+
failed: results.filter(r => !r.success).length,
|
|
126
|
+
results,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 投递记录数据访问层
|
|
3
|
+
* 需求: 1.6
|
|
4
|
+
*/
|
|
5
|
+
import { Context } from 'koishi';
|
|
6
|
+
import { Delivery } from '../database';
|
|
7
|
+
/**
|
|
8
|
+
* 记录投递
|
|
9
|
+
* 需求 1.6: 记录 Delivery ID 用于去重
|
|
10
|
+
* @param ctx Koishi 上下文
|
|
11
|
+
* @param deliveryId GitHub Delivery ID
|
|
12
|
+
* @param repo 仓库名 (owner/repo)
|
|
13
|
+
* @param event 事件类型
|
|
14
|
+
* @returns 创建的投递记录
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordDelivery(ctx: Context, deliveryId: string, repo: string, event: string): Promise<Delivery>;
|
|
17
|
+
/**
|
|
18
|
+
* 检查是否已投递
|
|
19
|
+
* 需求 1.6: 检查 Delivery ID 是否已处理过,用于去重
|
|
20
|
+
* @param ctx Koishi 上下文
|
|
21
|
+
* @param deliveryId GitHub Delivery ID
|
|
22
|
+
* @returns 是否已投递
|
|
23
|
+
*/
|
|
24
|
+
export declare function isDelivered(ctx: Context, deliveryId: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* 获取投递记录
|
|
27
|
+
* @param ctx Koishi 上下文
|
|
28
|
+
* @param deliveryId GitHub Delivery ID
|
|
29
|
+
* @returns 投递记录,不存在返回 null
|
|
30
|
+
*/
|
|
31
|
+
export declare function getDelivery(ctx: Context, deliveryId: string): Promise<Delivery | null>;
|
|
32
|
+
/**
|
|
33
|
+
* 清理旧的投递记录
|
|
34
|
+
* @param ctx Koishi 上下文
|
|
35
|
+
* @param beforeDate 清理此日期之前的记录
|
|
36
|
+
* @returns 清理的记录数
|
|
37
|
+
*/
|
|
38
|
+
export declare function cleanupDeliveries(ctx: Context, beforeDate: Date): Promise<number>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 投递记录数据访问层
|
|
4
|
+
* 需求: 1.6
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.recordDelivery = recordDelivery;
|
|
8
|
+
exports.isDelivered = isDelivered;
|
|
9
|
+
exports.getDelivery = getDelivery;
|
|
10
|
+
exports.cleanupDeliveries = cleanupDeliveries;
|
|
11
|
+
/**
|
|
12
|
+
* 记录投递
|
|
13
|
+
* 需求 1.6: 记录 Delivery ID 用于去重
|
|
14
|
+
* @param ctx Koishi 上下文
|
|
15
|
+
* @param deliveryId GitHub Delivery ID
|
|
16
|
+
* @param repo 仓库名 (owner/repo)
|
|
17
|
+
* @param event 事件类型
|
|
18
|
+
* @returns 创建的投递记录
|
|
19
|
+
*/
|
|
20
|
+
async function recordDelivery(ctx, deliveryId, repo, event) {
|
|
21
|
+
const now = new Date();
|
|
22
|
+
await ctx.database.create('github_deliveries', {
|
|
23
|
+
deliveryId,
|
|
24
|
+
repo,
|
|
25
|
+
event,
|
|
26
|
+
receivedAt: now,
|
|
27
|
+
});
|
|
28
|
+
const [created] = await ctx.database.get('github_deliveries', { deliveryId });
|
|
29
|
+
return created;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 检查是否已投递
|
|
33
|
+
* 需求 1.6: 检查 Delivery ID 是否已处理过,用于去重
|
|
34
|
+
* @param ctx Koishi 上下文
|
|
35
|
+
* @param deliveryId GitHub Delivery ID
|
|
36
|
+
* @returns 是否已投递
|
|
37
|
+
*/
|
|
38
|
+
async function isDelivered(ctx, deliveryId) {
|
|
39
|
+
const deliveries = await ctx.database.get('github_deliveries', { deliveryId });
|
|
40
|
+
return deliveries.length > 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 获取投递记录
|
|
44
|
+
* @param ctx Koishi 上下文
|
|
45
|
+
* @param deliveryId GitHub Delivery ID
|
|
46
|
+
* @returns 投递记录,不存在返回 null
|
|
47
|
+
*/
|
|
48
|
+
async function getDelivery(ctx, deliveryId) {
|
|
49
|
+
const deliveries = await ctx.database.get('github_deliveries', { deliveryId });
|
|
50
|
+
return deliveries.length > 0 ? deliveries[0] : null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 清理旧的投递记录
|
|
54
|
+
* @param ctx Koishi 上下文
|
|
55
|
+
* @param beforeDate 清理此日期之前的记录
|
|
56
|
+
* @returns 清理的记录数
|
|
57
|
+
*/
|
|
58
|
+
async function cleanupDeliveries(ctx, beforeDate) {
|
|
59
|
+
const result = await ctx.database.remove('github_deliveries', {
|
|
60
|
+
receivedAt: { $lt: beforeDate },
|
|
61
|
+
});
|
|
62
|
+
return result.removed ?? 0;
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 数据访问层导出
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./trust"), exports);
|
|
21
|
+
__exportStar(require("./subscription"), exports);
|
|
22
|
+
__exportStar(require("./delivery"), exports);
|