koishi-plugin-github-webhook-pusher 0.0.8 → 0.0.9

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/config.d.ts CHANGED
@@ -6,8 +6,6 @@ export interface Config {
6
6
  path: string;
7
7
  /** GitHub Webhook Secret */
8
8
  secret: string;
9
- /** 显示用基础 URL */
10
- baseUrl?: string;
11
9
  /** 默认订阅事件 */
12
10
  defaultEvents: EventType[];
13
11
  /** 调试模式 */
@@ -16,5 +14,9 @@ export interface Config {
16
14
  allowUntrusted: boolean;
17
15
  /** 推送并发数 */
18
16
  concurrency: number;
17
+ /** 投递记录保留天数(<=0 表示不清理) */
18
+ deliveryRetentionDays: number;
19
+ /** 投递记录清理间隔(小时) */
20
+ deliveryCleanupIntervalHours: number;
19
21
  }
20
22
  export declare const Config: Schema<Config>;
package/lib/config.js CHANGED
@@ -20,11 +20,12 @@ const EVENT_TYPES = [
20
20
  exports.Config = koishi_1.Schema.object({
21
21
  path: koishi_1.Schema.string().default('/github/webhook').description('Webhook 接收路径'),
22
22
  secret: koishi_1.Schema.string().required().description('GitHub Webhook Secret'),
23
- baseUrl: koishi_1.Schema.string().description('显示用基础 URL'),
24
23
  defaultEvents: koishi_1.Schema.array(koishi_1.Schema.union(EVENT_TYPES.map(e => koishi_1.Schema.const(e))))
25
24
  .default(['issues', 'release', 'push'])
26
25
  .description('默认订阅事件'),
27
26
  debug: koishi_1.Schema.boolean().default(false).description('调试模式'),
28
27
  allowUntrusted: koishi_1.Schema.boolean().default(false).description('允许非信任仓库'),
29
28
  concurrency: koishi_1.Schema.number().default(5).description('推送并发数'),
29
+ deliveryRetentionDays: koishi_1.Schema.number().default(30).description('投递记录保留天数(<=0 表示不清理)'),
30
+ deliveryCleanupIntervalHours: koishi_1.Schema.number().default(24).description('投递记录清理间隔(小时)'),
30
31
  });
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "Config", { enumerable: true, get: function () {
12
12
  const database_1 = require("./database");
13
13
  const webhook_1 = require("./webhook");
14
14
  const commands_1 = require("./commands");
15
+ const repository_1 = require("./repository");
15
16
  /** 插件名称 */
16
17
  exports.name = 'github-webhook-pusher';
17
18
  /** 声明服务依赖 - 需要 server 服务提供 router 和 database 服务 */
@@ -43,6 +44,7 @@ function apply(ctx, config) {
43
44
  // 需求 8.1, 8.2: 工具命令
44
45
  (0, commands_1.registerUtilCommands)(ctx, config);
45
46
  logger.debug('工具命令已注册');
47
+ scheduleDeliveryCleanup(ctx, config);
46
48
  // 插件启动日志
47
49
  logger.info(`GitHub Webhook 插件已加载`);
48
50
  logger.info(`Webhook 路径: ${config.path}`);
@@ -51,3 +53,28 @@ function apply(ctx, config) {
51
53
  logger.info('调试模式已启用');
52
54
  }
53
55
  }
56
+ function scheduleDeliveryCleanup(ctx, config) {
57
+ if (config.deliveryRetentionDays <= 0) {
58
+ logger.debug('投递记录清理已禁用');
59
+ return;
60
+ }
61
+ const intervalHours = Math.max(1, config.deliveryCleanupIntervalHours || 24);
62
+ const intervalMs = intervalHours * 60 * 60 * 1000;
63
+ const runCleanup = async () => {
64
+ const beforeDate = new Date(Date.now() - config.deliveryRetentionDays * 24 * 60 * 60 * 1000);
65
+ const removed = await (0, repository_1.cleanupDeliveries)(ctx, beforeDate);
66
+ if (config.debug) {
67
+ logger.debug(`清理投递记录: ${removed} 条 (保留 ${config.deliveryRetentionDays} 天)`);
68
+ }
69
+ };
70
+ runCleanup().catch(error => {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ logger.warn(`投递记录清理失败: ${message}`);
73
+ });
74
+ ctx.setInterval(() => {
75
+ runCleanup().catch(error => {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ logger.warn(`投递记录清理失败: ${message}`);
78
+ });
79
+ }, intervalMs);
80
+ }
package/lib/webhook.js CHANGED
@@ -43,7 +43,22 @@ function registerWebhook(ctx, config) {
43
43
  koaCtx.body = { error: 'Missing signature' };
44
44
  return;
45
45
  }
46
- if (!(0, signature_1.verifySignature)(rawBody, signature, config.secret)) {
46
+ let payloadForVerify = rawBody;
47
+ if (!payloadForVerify) {
48
+ const requestBody = koaCtx.request?.body;
49
+ if (requestBody) {
50
+ // fallback: body parser 已消费流,无法获得 raw body
51
+ payloadForVerify = JSON.stringify(requestBody);
52
+ logger.warn('未获取到原始请求体,已使用解析后的 body 进行验签(可能导致验签失败)');
53
+ }
54
+ else {
55
+ logger.warn('无法获取请求体,无法进行签名验证');
56
+ koaCtx.status = 400;
57
+ koaCtx.body = { error: 'Missing request body' };
58
+ return;
59
+ }
60
+ }
61
+ if (!(0, signature_1.verifySignature)(payloadForVerify, signature, config.secret)) {
47
62
  // 需求 1.3, 9.2: 签名验证失败
48
63
  logger.warn('签名验证失败');
49
64
  koaCtx.status = 401;
@@ -56,14 +71,28 @@ function registerWebhook(ctx, config) {
56
71
  // 需求 1.5: 签名验证成功,继续处理
57
72
  // 解析 JSON 负载
58
73
  let payload;
59
- try {
60
- payload = JSON.parse(rawBody);
74
+ if (rawBody) {
75
+ try {
76
+ payload = JSON.parse(rawBody);
77
+ }
78
+ catch (e) {
79
+ logger.warn('无法解析 JSON 负载');
80
+ koaCtx.status = 400;
81
+ koaCtx.body = { error: 'Invalid JSON payload' };
82
+ return;
83
+ }
61
84
  }
62
- catch (e) {
63
- logger.warn('无法解析 JSON 负载');
64
- koaCtx.status = 400;
65
- koaCtx.body = { error: 'Invalid JSON payload' };
66
- return;
85
+ else {
86
+ const requestBody = koaCtx.request?.body;
87
+ if (requestBody) {
88
+ payload = requestBody;
89
+ }
90
+ else {
91
+ logger.warn('请求体为空或无法读取');
92
+ koaCtx.status = 400;
93
+ koaCtx.body = { error: 'Missing request body' };
94
+ return;
95
+ }
67
96
  }
68
97
  // 获取仓库名
69
98
  const repo = payload.repository?.full_name;
@@ -135,9 +164,15 @@ function registerWebhook(ctx, config) {
135
164
  * @returns 原始请求体字符串
136
165
  */
137
166
  async function getRawBody(koaCtx) {
138
- // 如果已经有解析好的 body,尝试重新序列化
139
- if (koaCtx.request.body) {
140
- return JSON.stringify(koaCtx.request.body);
167
+ const rawBody = koaCtx.request?.rawBody;
168
+ if (typeof rawBody === 'string') {
169
+ return rawBody;
170
+ }
171
+ if (Buffer.isBuffer(rawBody)) {
172
+ return rawBody.toString('utf8');
173
+ }
174
+ if (!koaCtx.req?.readable) {
175
+ return null;
141
176
  }
142
177
  // 否则从流中读取
143
178
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-github-webhook-pusher",
3
3
  "description": "GitHub Webhook 事件推送插件",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "contributors": [
6
6
  "ClozyA <aoxuan233@gmail.com>"
7
7
  ],
package/readme.md CHANGED
@@ -31,11 +31,12 @@ npm install koishi-plugin-github-webhook-pusher
31
31
  |--------|------|--------|------|
32
32
  | `path` | string | `/github/webhook` | Webhook 接收路径 |
33
33
  | `secret` | string | (必填) | GitHub Webhook Secret,用于签名验证 |
34
- | `baseUrl` | string | - | 显示用基础 URL |
35
34
  | `defaultEvents` | string[] | `['issues', 'release', 'push']` | 新订阅的默认事件类型 |
36
35
  | `debug` | boolean | `false` | 启用调试模式,输出详细日志 |
37
36
  | `allowUntrusted` | boolean | `false` | 是否允许处理非信任仓库的事件 |
38
37
  | `concurrency` | number | `5` | 消息推送并发数 |
38
+ | `deliveryRetentionDays` | number | `30` | 投递记录保留天数(<=0 表示不清理) |
39
+ | `deliveryCleanupIntervalHours` | number | `24` | 投递记录清理间隔(小时) |
39
40
 
40
41
  ### 配置示例
41
42
 
@@ -51,6 +52,8 @@ plugins:
51
52
  debug: false
52
53
  allowUntrusted: false
53
54
  concurrency: 5
55
+ deliveryRetentionDays: 30
56
+ deliveryCleanupIntervalHours: 24
54
57
  ```
55
58
 
56
59
  ## GitHub Webhook 设置指南
@@ -178,6 +181,7 @@ https://github.com/owner/repo/releases/tag/v1.0.0
178
181
  **A:** 签名验证失败。请检查:
179
182
  1. GitHub Webhook 设置中的 Secret 与插件配置的 `secret` 是否一致
180
183
  2. Content type 是否设置为 `application/json`
184
+ 3. 如果服务器未保留 raw body,插件会使用解析后的 body 回退验签,可能导致签名不一致;建议配置保留原始请求体
181
185
 
182
186
  ### Q: 收不到 Webhook 事件?
183
187