koishi-plugin-githubsth 1.0.2-test2 → 1.0.3-alpha.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.
- package/lib/commands/subscribe.d.ts +1 -1
- package/lib/commands/subscribe.js +4 -3
- package/lib/config.d.ts +2 -0
- package/lib/config.js +4 -0
- package/lib/services/formatter.js +47 -44
- package/lib/services/notifier.d.ts +2 -0
- package/lib/services/notifier.js +221 -53
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Context } from 'koishi';
|
|
2
|
-
export declare function apply(ctx: Context): void;
|
|
2
|
+
export declare function apply(ctx: Context, config: any): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.apply = apply;
|
|
4
|
-
function apply(ctx) {
|
|
4
|
+
function apply(ctx, config) {
|
|
5
5
|
const logger = ctx.logger('githubsth');
|
|
6
6
|
const repoRegex = /^[\w-]+\/[\w-\.]+$/;
|
|
7
7
|
const validEvents = [
|
|
@@ -9,11 +9,12 @@ function apply(ctx) {
|
|
|
9
9
|
'pull_request_review', 'star', 'fork', 'release',
|
|
10
10
|
'discussion', 'workflow_run'
|
|
11
11
|
];
|
|
12
|
+
const defaultConfigEvents = config.defaultEvents || ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'];
|
|
12
13
|
ctx.command('githubsth.subscribe <repo> [events:text]', '订阅 GitHub 仓库')
|
|
13
14
|
.alias('gh.sub')
|
|
14
15
|
.usage(`
|
|
15
16
|
订阅 GitHub 仓库通知。
|
|
16
|
-
如果不指定事件,默认订阅:
|
|
17
|
+
如果不指定事件,默认订阅: ${defaultConfigEvents.join(', ')}
|
|
17
18
|
|
|
18
19
|
可选事件:
|
|
19
20
|
- push: 代码推送
|
|
@@ -58,7 +59,7 @@ gh.sub koishijs/koishi push,issues,star
|
|
|
58
59
|
}
|
|
59
60
|
else {
|
|
60
61
|
// Default events
|
|
61
|
-
events = [
|
|
62
|
+
events = [...defaultConfigEvents];
|
|
62
63
|
}
|
|
63
64
|
try {
|
|
64
65
|
// Check if subscription exists
|
package/lib/config.d.ts
CHANGED
package/lib/config.js
CHANGED
|
@@ -6,6 +6,10 @@ exports.Config = koishi_1.Schema.object({
|
|
|
6
6
|
defaultOwner: koishi_1.Schema.string().description('默认仓库拥有者'),
|
|
7
7
|
defaultRepo: koishi_1.Schema.string().description('默认仓库名称'),
|
|
8
8
|
debug: koishi_1.Schema.boolean().default(false).description('启用调试模式,输出详细日志'),
|
|
9
|
+
logUnhandledEvents: koishi_1.Schema.boolean().default(false).description('是否记录未处理的 Webhook 事件 (Unknown events)'),
|
|
10
|
+
defaultEvents: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
11
|
+
.default(['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'])
|
|
12
|
+
.description('默认订阅事件列表 (当不指定事件时使用)'),
|
|
9
13
|
rules: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
10
14
|
repo: koishi_1.Schema.string().required(),
|
|
11
15
|
channelId: koishi_1.Schema.string().required(),
|
|
@@ -8,39 +8,40 @@ class Formatter extends koishi_1.Service {
|
|
|
8
8
|
ctx.logger('githubsth').info('Formatter service initialized');
|
|
9
9
|
}
|
|
10
10
|
formatPush(payload) {
|
|
11
|
-
const { repository, pusher, commits, compare } = payload;
|
|
11
|
+
const { repository, pusher, commits, compare, ref } = payload;
|
|
12
12
|
if (!commits || commits.length === 0)
|
|
13
13
|
return null;
|
|
14
14
|
const commitLines = commits.map((c) => {
|
|
15
|
-
const shortHash = c.id
|
|
16
|
-
const message = c.message
|
|
17
|
-
|
|
15
|
+
const shortHash = c.id?.substring(0, 7) || '???????';
|
|
16
|
+
const message = c.message?.split('\n')[0] || 'No message';
|
|
17
|
+
const author = c.author?.name || 'Unknown';
|
|
18
|
+
return `[${shortHash}] ${message} - ${author}`;
|
|
18
19
|
}).join('\n');
|
|
19
20
|
return (0, koishi_1.h)('message', [
|
|
20
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
21
|
-
(0, koishi_1.h)('text', { content: `提交者: ${pusher
|
|
22
|
-
(0, koishi_1.h)('text', { content: `分支: ${
|
|
23
|
-
(0, koishi_1.h)('text', { content: `详情: ${compare}\n` }),
|
|
21
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} 收到新的推送\n` }),
|
|
22
|
+
(0, koishi_1.h)('text', { content: `提交者: ${pusher?.name || 'Unknown'}\n` }),
|
|
23
|
+
(0, koishi_1.h)('text', { content: `分支: ${ref ? ref.replace('refs/heads/', '') : 'unknown'}\n` }),
|
|
24
|
+
(0, koishi_1.h)('text', { content: `详情: ${compare || ''}\n` }),
|
|
24
25
|
(0, koishi_1.h)('text', { content: commitLines })
|
|
25
26
|
]);
|
|
26
27
|
}
|
|
27
28
|
formatIssue(payload) {
|
|
28
29
|
const { action, issue, repository, sender } = payload;
|
|
29
30
|
return (0, koishi_1.h)('message', [
|
|
30
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
31
|
-
(0, koishi_1.h)('text', { content: `标题: #${issue
|
|
32
|
-
(0, koishi_1.h)('text', { content: `发起人: ${sender
|
|
33
|
-
(0, koishi_1.h)('text', { content: `链接: ${issue
|
|
31
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} Issue ${action || 'updated'}\n` }),
|
|
32
|
+
(0, koishi_1.h)('text', { content: `标题: #${issue?.number || '?'} ${issue?.title || 'No Title'}\n` }),
|
|
33
|
+
(0, koishi_1.h)('text', { content: `发起人: ${sender?.login || 'Unknown'}\n` }),
|
|
34
|
+
(0, koishi_1.h)('text', { content: `链接: ${issue?.html_url || ''}` })
|
|
34
35
|
]);
|
|
35
36
|
}
|
|
36
37
|
formatPullRequest(payload) {
|
|
37
38
|
const { action, pull_request, repository, sender } = payload;
|
|
38
39
|
return (0, koishi_1.h)('message', [
|
|
39
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
40
|
-
(0, koishi_1.h)('text', { content: `标题: #${pull_request
|
|
41
|
-
(0, koishi_1.h)('text', { content: `发起人: ${sender
|
|
42
|
-
(0, koishi_1.h)('text', { content: `状态: ${pull_request
|
|
43
|
-
(0, koishi_1.h)('text', { content: `链接: ${pull_request
|
|
40
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} PR ${action || 'updated'}\n` }),
|
|
41
|
+
(0, koishi_1.h)('text', { content: `标题: #${pull_request?.number || '?'} ${pull_request?.title || 'No Title'}\n` }),
|
|
42
|
+
(0, koishi_1.h)('text', { content: `发起人: ${sender?.login || 'Unknown'}\n` }),
|
|
43
|
+
(0, koishi_1.h)('text', { content: `状态: ${pull_request?.state || 'unknown'}\n` }),
|
|
44
|
+
(0, koishi_1.h)('text', { content: `链接: ${pull_request?.html_url || ''}` })
|
|
44
45
|
]);
|
|
45
46
|
}
|
|
46
47
|
formatStar(payload) {
|
|
@@ -48,15 +49,15 @@ class Formatter extends koishi_1.Service {
|
|
|
48
49
|
if (action !== 'created')
|
|
49
50
|
return null;
|
|
50
51
|
return (0, koishi_1.h)('message', [
|
|
51
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${sender
|
|
52
|
-
(0, koishi_1.h)('text', { content: `当前 Star 数: ${repository.stargazers_count}` })
|
|
52
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${sender?.login || 'Unknown'} Star 了仓库 ${repository?.full_name || 'Unknown Repo'} 🌟\n` }),
|
|
53
|
+
(0, koishi_1.h)('text', { content: `当前 Star 数: ${repository?.stargazers_count !== undefined ? repository.stargazers_count : '?'}` })
|
|
53
54
|
]);
|
|
54
55
|
}
|
|
55
56
|
formatFork(payload) {
|
|
56
57
|
const { forkee, repository, sender } = payload;
|
|
57
58
|
return (0, koishi_1.h)('message', [
|
|
58
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${sender
|
|
59
|
-
(0, koishi_1.h)('text', { content: `新仓库: ${forkee
|
|
59
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${sender?.login || 'Unknown'} Fork 了仓库 ${repository?.full_name || 'Unknown Repo'}\n` }),
|
|
60
|
+
(0, koishi_1.h)('text', { content: `新仓库: ${forkee?.full_name || 'Unknown'}` })
|
|
60
61
|
]);
|
|
61
62
|
}
|
|
62
63
|
formatRelease(payload) {
|
|
@@ -64,43 +65,45 @@ class Formatter extends koishi_1.Service {
|
|
|
64
65
|
if (action !== 'published')
|
|
65
66
|
return null;
|
|
66
67
|
return (0, koishi_1.h)('message', [
|
|
67
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
68
|
-
(0, koishi_1.h)('text', { content: `标题: ${release
|
|
69
|
-
(0, koishi_1.h)('text', { content: `链接: ${release
|
|
68
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} 发布了新版本 ${release?.tag_name || 'unknown'} 🎉\n` }),
|
|
69
|
+
(0, koishi_1.h)('text', { content: `标题: ${release?.name || 'No Title'}\n` }),
|
|
70
|
+
(0, koishi_1.h)('text', { content: `链接: ${release?.html_url || ''}` })
|
|
70
71
|
]);
|
|
71
72
|
}
|
|
72
73
|
formatDiscussion(payload) {
|
|
73
74
|
const { action, discussion, repository, sender } = payload;
|
|
74
75
|
return (0, koishi_1.h)('message', [
|
|
75
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
76
|
-
(0, koishi_1.h)('text', { content: `标题: #${discussion
|
|
77
|
-
(0, koishi_1.h)('text', { content: `发起人: ${sender
|
|
78
|
-
(0, koishi_1.h)('text', { content: `链接: ${discussion
|
|
76
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} Discussion ${action || 'updated'}\n` }),
|
|
77
|
+
(0, koishi_1.h)('text', { content: `标题: #${discussion?.number || '?'} ${discussion?.title || 'No Title'}\n` }),
|
|
78
|
+
(0, koishi_1.h)('text', { content: `发起人: ${sender?.login || 'Unknown'}\n` }),
|
|
79
|
+
(0, koishi_1.h)('text', { content: `链接: ${discussion?.html_url || ''}` })
|
|
79
80
|
]);
|
|
80
81
|
}
|
|
81
82
|
formatWorkflowRun(payload) {
|
|
82
83
|
const { action, workflow_run, repository } = payload;
|
|
83
84
|
if (action !== 'completed')
|
|
84
85
|
return null;
|
|
85
|
-
const statusIcon = workflow_run
|
|
86
|
+
const statusIcon = workflow_run?.conclusion === 'success' ? '✅' : '❌';
|
|
86
87
|
return (0, koishi_1.h)('message', [
|
|
87
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
88
|
-
(0, koishi_1.h)('text', { content: `工作流: ${workflow_run
|
|
89
|
-
(0, koishi_1.h)('text', { content: `结果: ${workflow_run
|
|
90
|
-
(0, koishi_1.h)('text', { content: `分支: ${workflow_run
|
|
91
|
-
(0, koishi_1.h)('text', { content: `链接: ${workflow_run
|
|
88
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} 工作流运行完成 ${statusIcon}\n` }),
|
|
89
|
+
(0, koishi_1.h)('text', { content: `工作流: ${workflow_run?.name || 'Unknown'}\n` }),
|
|
90
|
+
(0, koishi_1.h)('text', { content: `结果: ${workflow_run?.conclusion || 'unknown'}\n` }),
|
|
91
|
+
(0, koishi_1.h)('text', { content: `分支: ${workflow_run?.head_branch || 'unknown'}\n` }),
|
|
92
|
+
(0, koishi_1.h)('text', { content: `链接: ${workflow_run?.html_url || ''}` })
|
|
92
93
|
]);
|
|
93
94
|
}
|
|
94
95
|
formatIssueComment(payload) {
|
|
95
96
|
const { action, issue, comment, repository, sender } = payload;
|
|
96
97
|
if (action !== 'created')
|
|
97
98
|
return null;
|
|
99
|
+
const body = comment?.body || '';
|
|
100
|
+
const shortBody = body.length > 100 ? body.substring(0, 100) + '...' : body;
|
|
98
101
|
return (0, koishi_1.h)('message', [
|
|
99
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
100
|
-
(0, koishi_1.h)('text', { content: `标题: #${issue
|
|
101
|
-
(0, koishi_1.h)('text', { content: `评论人: ${sender
|
|
102
|
-
(0, koishi_1.h)('text', { content: `内容: ${
|
|
103
|
-
(0, koishi_1.h)('text', { content: `链接: ${comment
|
|
102
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} Issue 收到新评论\n` }),
|
|
103
|
+
(0, koishi_1.h)('text', { content: `标题: #${issue?.number || '?'} ${issue?.title || 'No Title'}\n` }),
|
|
104
|
+
(0, koishi_1.h)('text', { content: `评论人: ${sender?.login || 'Unknown'}\n` }),
|
|
105
|
+
(0, koishi_1.h)('text', { content: `内容: ${shortBody}\n` }),
|
|
106
|
+
(0, koishi_1.h)('text', { content: `链接: ${comment?.html_url || ''}` })
|
|
104
107
|
]);
|
|
105
108
|
}
|
|
106
109
|
formatPullRequestReview(payload) {
|
|
@@ -108,11 +111,11 @@ class Formatter extends koishi_1.Service {
|
|
|
108
111
|
if (action !== 'submitted')
|
|
109
112
|
return null;
|
|
110
113
|
return (0, koishi_1.h)('message', [
|
|
111
|
-
(0, koishi_1.h)('text', { content: `[GitHub] ${repository
|
|
112
|
-
(0, koishi_1.h)('text', { content: `标题: #${pull_request
|
|
113
|
-
(0, koishi_1.h)('text', { content: `Reviewer: ${sender
|
|
114
|
-
(0, koishi_1.h)('text', { content: `状态: ${review
|
|
115
|
-
(0, koishi_1.h)('text', { content: `链接: ${review
|
|
114
|
+
(0, koishi_1.h)('text', { content: `[GitHub] ${repository?.full_name || 'Unknown Repo'} PR 收到 Review\n` }),
|
|
115
|
+
(0, koishi_1.h)('text', { content: `标题: #${pull_request?.number || '?'} ${pull_request?.title || 'No Title'}\n` }),
|
|
116
|
+
(0, koishi_1.h)('text', { content: `Reviewer: ${sender?.login || 'Unknown'}\n` }),
|
|
117
|
+
(0, koishi_1.h)('text', { content: `状态: ${review?.state || 'unknown'}\n` }),
|
|
118
|
+
(0, koishi_1.h)('text', { content: `链接: ${review?.html_url || ''}` })
|
|
116
119
|
]);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
@@ -7,8 +7,10 @@ declare module 'koishi' {
|
|
|
7
7
|
}
|
|
8
8
|
export declare class Notifier extends Service {
|
|
9
9
|
config: Config;
|
|
10
|
+
static inject: string[];
|
|
10
11
|
constructor(ctx: Context, config: Config);
|
|
11
12
|
private registerListeners;
|
|
12
13
|
private handleEvent;
|
|
14
|
+
private patchPayloadForEvent;
|
|
13
15
|
private sendMessage;
|
|
14
16
|
}
|
package/lib/services/notifier.js
CHANGED
|
@@ -11,16 +11,20 @@ class Notifier extends koishi_1.Service {
|
|
|
11
11
|
this.registerListeners();
|
|
12
12
|
}
|
|
13
13
|
registerListeners() {
|
|
14
|
+
// adapter-github canonical event names
|
|
15
|
+
this.ctx.on('github/issue', (payload) => this.handleEvent('issues', payload));
|
|
16
|
+
this.ctx.on('github/issue-comment', (payload) => this.handleEvent('issue_comment', payload));
|
|
17
|
+
this.ctx.on('github/pull-request', (payload) => this.handleEvent('pull_request', payload));
|
|
18
|
+
this.ctx.on('github/workflow-run', (payload) => this.handleEvent('workflow_run', payload));
|
|
19
|
+
// Backward compatibility aliases
|
|
14
20
|
this.ctx.on('github/push', (payload) => this.handleEvent('push', payload));
|
|
15
21
|
this.ctx.on('github/issues', (payload) => this.handleEvent('issues', payload));
|
|
16
22
|
this.ctx.on('github/pull_request', (payload) => this.handleEvent('pull_request', payload));
|
|
17
|
-
this.ctx.on('github/pull-request', (payload) => this.handleEvent('pull_request', payload));
|
|
18
23
|
this.ctx.on('github/star', (payload) => this.handleEvent('star', payload));
|
|
19
24
|
this.ctx.on('github/fork', (payload) => this.handleEvent('fork', payload));
|
|
20
25
|
this.ctx.on('github/release', (payload) => this.handleEvent('release', payload));
|
|
21
26
|
this.ctx.on('github/discussion', (payload) => this.handleEvent('discussion', payload));
|
|
22
27
|
this.ctx.on('github/workflow_run', (payload) => this.handleEvent('workflow_run', payload));
|
|
23
|
-
this.ctx.on('github/workflow-run', (payload) => this.handleEvent('workflow_run', payload));
|
|
24
28
|
this.ctx.on('github/issue_comment', (payload) => this.handleEvent('issue_comment', payload));
|
|
25
29
|
this.ctx.on('github/issue-comment', (payload) => this.handleEvent('issue_comment', payload));
|
|
26
30
|
this.ctx.on('github/pull-request-review', (payload) => this.handleEvent('pull_request_review', payload));
|
|
@@ -34,31 +38,39 @@ class Notifier extends koishi_1.Service {
|
|
|
34
38
|
if (this.config.debug) {
|
|
35
39
|
this.ctx.logger('githubsth').info('Found payload in session, attempting to handle');
|
|
36
40
|
}
|
|
41
|
+
// Check if payload is wrapped (adapter-github structure)
|
|
42
|
+
const realPayload = payload.payload || payload;
|
|
37
43
|
// Infer event type
|
|
38
44
|
let eventType = 'unknown';
|
|
39
|
-
|
|
45
|
+
// Check inner payload first if it exists
|
|
46
|
+
if (realPayload.issue && realPayload.comment)
|
|
40
47
|
eventType = 'issue_comment';
|
|
41
|
-
else if (
|
|
48
|
+
else if (realPayload.issue)
|
|
42
49
|
eventType = 'issues';
|
|
43
|
-
else if (
|
|
50
|
+
else if (realPayload.pull_request && realPayload.review)
|
|
44
51
|
eventType = 'pull_request_review';
|
|
45
|
-
else if (
|
|
52
|
+
else if (realPayload.pull_request)
|
|
46
53
|
eventType = 'pull_request';
|
|
47
|
-
else if (
|
|
54
|
+
else if (realPayload.commits)
|
|
48
55
|
eventType = 'push';
|
|
49
|
-
else if (
|
|
50
|
-
eventType = 'star';
|
|
51
|
-
else if (
|
|
56
|
+
else if (realPayload.starred_at !== undefined || (realPayload.action === 'started'))
|
|
57
|
+
eventType = 'star';
|
|
58
|
+
else if (realPayload.forkee)
|
|
52
59
|
eventType = 'fork';
|
|
53
|
-
else if (
|
|
60
|
+
else if (realPayload.release)
|
|
54
61
|
eventType = 'release';
|
|
55
|
-
else if (
|
|
62
|
+
else if (realPayload.discussion)
|
|
56
63
|
eventType = 'discussion';
|
|
57
|
-
else if (
|
|
64
|
+
else if (realPayload.workflow_run)
|
|
58
65
|
eventType = 'workflow_run';
|
|
66
|
+
else if (realPayload.repository && (realPayload.action === 'created' || realPayload.action === 'started'))
|
|
67
|
+
eventType = 'star';
|
|
59
68
|
if (eventType !== 'unknown') {
|
|
60
69
|
this.handleEvent(eventType, payload);
|
|
61
70
|
}
|
|
71
|
+
else if (this.config.logUnhandledEvents) {
|
|
72
|
+
this.ctx.logger('githubsth').info(`Unhandled payload structure. Keys: ${Object.keys(realPayload).join(', ')}`);
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
});
|
|
64
76
|
}
|
|
@@ -66,11 +78,15 @@ class Notifier extends koishi_1.Service {
|
|
|
66
78
|
if (this.config.debug) {
|
|
67
79
|
this.ctx.logger('githubsth').info(`Received event: ${event}`);
|
|
68
80
|
}
|
|
69
|
-
// Check if payload is nested in an 'event' object (common in some adapter versions)
|
|
70
|
-
// or if the event data is directly in payload
|
|
71
81
|
const realPayload = payload.payload || payload;
|
|
82
|
+
if (payload.actor && !realPayload.sender) {
|
|
83
|
+
const actorLogin = payload.actor.login || payload.actor.name || 'GitHub';
|
|
84
|
+
realPayload.sender = { ...payload.actor, login: actorLogin };
|
|
85
|
+
}
|
|
86
|
+
if (payload.repository && !realPayload.repository) {
|
|
87
|
+
realPayload.repository = payload.repository;
|
|
88
|
+
}
|
|
72
89
|
let repoName = realPayload.repository?.full_name;
|
|
73
|
-
// Try to fallback if repoName is missing
|
|
74
90
|
if (!repoName && realPayload.issue?.repository_url) {
|
|
75
91
|
const parts = realPayload.issue.repository_url.split('/');
|
|
76
92
|
if (parts.length >= 2) {
|
|
@@ -80,25 +96,70 @@ class Notifier extends koishi_1.Service {
|
|
|
80
96
|
if (!repoName && realPayload.pull_request?.base?.repo?.full_name) {
|
|
81
97
|
repoName = realPayload.pull_request.base.repo.full_name;
|
|
82
98
|
}
|
|
99
|
+
// adapter-github eventData fields
|
|
100
|
+
if (!repoName && typeof payload.repoKey === 'string' && payload.repoKey.includes('/')) {
|
|
101
|
+
repoName = payload.repoKey;
|
|
102
|
+
}
|
|
103
|
+
if (!repoName && typeof payload.owner === 'string' && typeof payload.repo === 'string') {
|
|
104
|
+
repoName = `${payload.owner}/${payload.repo}`;
|
|
105
|
+
}
|
|
106
|
+
if (!repoName && typeof payload.repo === 'string' && payload.repo.includes('/')) {
|
|
107
|
+
repoName = payload.repo;
|
|
108
|
+
}
|
|
109
|
+
if (!repoName && event === 'star') {
|
|
110
|
+
if (payload.repository?.full_name) {
|
|
111
|
+
repoName = payload.repository.full_name;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!repoName && payload.repository?.full_name) {
|
|
115
|
+
repoName = payload.repository.full_name;
|
|
116
|
+
}
|
|
83
117
|
if (!repoName) {
|
|
84
118
|
if (this.config.debug) {
|
|
85
|
-
this.ctx.logger('githubsth').warn(`Missing repo info for event: ${event}`);
|
|
86
|
-
this.ctx.logger('notifier').warn(`Event ${event} missing repository info. Keys: ${Object.keys(realPayload).join(', ')}`);
|
|
119
|
+
this.ctx.logger('githubsth').warn(`Missing repo info for event: ${event}. Keys: ${Object.keys(realPayload).join(', ')}`);
|
|
87
120
|
}
|
|
88
|
-
else {
|
|
89
|
-
// Log at warning level even in production if repo info is missing, as this is a critical failure
|
|
90
|
-
// But only log keys to avoid leaking sensitive data
|
|
121
|
+
else if (this.config.logUnhandledEvents) {
|
|
91
122
|
this.ctx.logger('githubsth').warn(`Missing repo info for event: ${event}. Keys: ${Object.keys(realPayload).join(', ')}`);
|
|
92
123
|
}
|
|
93
|
-
|
|
124
|
+
}
|
|
125
|
+
if (!realPayload.repository) {
|
|
126
|
+
realPayload.repository = { full_name: repoName || 'Unknown/Repo' };
|
|
127
|
+
}
|
|
128
|
+
else if (!realPayload.repository.full_name) {
|
|
129
|
+
realPayload.repository.full_name = repoName || 'Unknown/Repo';
|
|
130
|
+
}
|
|
131
|
+
if (!realPayload.sender) {
|
|
132
|
+
if (realPayload.issue?.user) {
|
|
133
|
+
realPayload.sender = realPayload.issue.user;
|
|
134
|
+
}
|
|
135
|
+
else if (realPayload.pull_request?.user) {
|
|
136
|
+
realPayload.sender = realPayload.pull_request.user;
|
|
137
|
+
}
|
|
138
|
+
else if (realPayload.discussion?.user) {
|
|
139
|
+
realPayload.sender = realPayload.discussion.user;
|
|
140
|
+
}
|
|
141
|
+
else if (realPayload.pusher) {
|
|
142
|
+
realPayload.sender = { login: realPayload.pusher.name || 'Pusher' };
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
realPayload.sender = { login: 'GitHub' };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
this.patchPayloadForEvent(event, realPayload, repoName || 'Unknown/Repo');
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
this.ctx.logger('githubsth').warn(`Failed to patch payload for ${event}:`, e);
|
|
94
153
|
}
|
|
95
154
|
if (this.config.debug) {
|
|
96
155
|
this.ctx.logger('githubsth').info(`Processing event ${event} for ${repoName}`);
|
|
97
156
|
this.ctx.logger('notifier').info(`Received event ${event} for ${repoName}`);
|
|
98
157
|
this.ctx.logger('notifier').debug(JSON.stringify(realPayload, null, 2));
|
|
99
158
|
}
|
|
100
|
-
|
|
101
|
-
|
|
159
|
+
if (!repoName) {
|
|
160
|
+
this.ctx.logger('githubsth').warn('Cannot query rules: repoName is missing');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
102
163
|
const repoNames = [repoName];
|
|
103
164
|
if (repoName !== repoName.toLowerCase()) {
|
|
104
165
|
repoNames.push(repoName.toLowerCase());
|
|
@@ -106,8 +167,6 @@ class Notifier extends koishi_1.Service {
|
|
|
106
167
|
const dbRules = await this.ctx.database.get('github_subscription', {
|
|
107
168
|
repo: repoNames
|
|
108
169
|
});
|
|
109
|
-
// Combine with config rules (if any, for backward compatibility or static rules)
|
|
110
|
-
// Also match config rules case-insensitively if needed
|
|
111
170
|
const configRules = (this.config.rules || []).filter((r) => r.repo === repoName ||
|
|
112
171
|
r.repo === repoName.toLowerCase() ||
|
|
113
172
|
r.repo === '*');
|
|
@@ -116,7 +175,6 @@ class Notifier extends koishi_1.Service {
|
|
|
116
175
|
...configRules
|
|
117
176
|
];
|
|
118
177
|
const matchedRules = allRules.filter(rule => {
|
|
119
|
-
// Event match
|
|
120
178
|
if (!rule.events.includes('*') && !rule.events.includes(event))
|
|
121
179
|
return false;
|
|
122
180
|
return true;
|
|
@@ -126,6 +184,9 @@ class Notifier extends koishi_1.Service {
|
|
|
126
184
|
this.ctx.logger('githubsth').info(`No matching rules for ${repoName} (event: ${event})`);
|
|
127
185
|
this.ctx.logger('notifier').debug(`No matching rules for ${repoName} (event: ${event})`);
|
|
128
186
|
}
|
|
187
|
+
else if (this.config.logUnhandledEvents) {
|
|
188
|
+
this.ctx.logger('githubsth').warn(`No matching rules for ${repoName} (event: ${event})`);
|
|
189
|
+
}
|
|
129
190
|
return;
|
|
130
191
|
}
|
|
131
192
|
if (this.config.debug) {
|
|
@@ -133,62 +194,168 @@ class Notifier extends koishi_1.Service {
|
|
|
133
194
|
this.ctx.logger('notifier').debug(`Found ${matchedRules.length} matching rules for ${repoName}`);
|
|
134
195
|
}
|
|
135
196
|
let message = null;
|
|
136
|
-
// Ensure formatter is loaded
|
|
137
197
|
if (!this.ctx.githubsthFormatter) {
|
|
138
198
|
this.ctx.logger('notifier').warn('Formatter service not available');
|
|
139
199
|
return;
|
|
140
200
|
}
|
|
201
|
+
try {
|
|
202
|
+
switch (event) {
|
|
203
|
+
case 'push':
|
|
204
|
+
message = this.ctx.githubsthFormatter.formatPush(realPayload);
|
|
205
|
+
break;
|
|
206
|
+
case 'issues':
|
|
207
|
+
message = this.ctx.githubsthFormatter.formatIssue(realPayload);
|
|
208
|
+
break;
|
|
209
|
+
case 'pull_request':
|
|
210
|
+
message = this.ctx.githubsthFormatter.formatPullRequest(realPayload);
|
|
211
|
+
break;
|
|
212
|
+
case 'star':
|
|
213
|
+
message = this.ctx.githubsthFormatter.formatStar(realPayload);
|
|
214
|
+
break;
|
|
215
|
+
case 'fork':
|
|
216
|
+
message = this.ctx.githubsthFormatter.formatFork(realPayload);
|
|
217
|
+
break;
|
|
218
|
+
case 'release':
|
|
219
|
+
message = this.ctx.githubsthFormatter.formatRelease(realPayload);
|
|
220
|
+
break;
|
|
221
|
+
case 'discussion':
|
|
222
|
+
message = this.ctx.githubsthFormatter.formatDiscussion(realPayload);
|
|
223
|
+
break;
|
|
224
|
+
case 'workflow_run':
|
|
225
|
+
message = this.ctx.githubsthFormatter.formatWorkflowRun(realPayload);
|
|
226
|
+
break;
|
|
227
|
+
case 'issue_comment':
|
|
228
|
+
message = this.ctx.githubsthFormatter.formatIssueComment(realPayload);
|
|
229
|
+
break;
|
|
230
|
+
case 'pull_request_review':
|
|
231
|
+
message = this.ctx.githubsthFormatter.formatPullRequestReview(realPayload);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
this.ctx.logger('githubsth').error(`Error formatting event ${event}:`, e);
|
|
237
|
+
if (this.config.debug) {
|
|
238
|
+
this.ctx.logger('notifier').error(`Error formatting event ${event}:`, e);
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (!message) {
|
|
243
|
+
if (this.config.debug) {
|
|
244
|
+
this.ctx.logger('notifier').debug(`Formatter returned null for event ${event}`);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const rule of matchedRules) {
|
|
249
|
+
if (this.config.debug) {
|
|
250
|
+
this.ctx.logger('notifier').debug(`Sending message to channel ${rule.channelId} (platform: ${rule.platform || 'any'})`);
|
|
251
|
+
}
|
|
252
|
+
await this.sendMessage(rule, message);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
patchPayloadForEvent(event, payload, repoName) {
|
|
256
|
+
const defaultUser = { login: 'GitHub', id: 0, avatar_url: '' };
|
|
257
|
+
if (!payload.sender)
|
|
258
|
+
payload.sender = defaultUser;
|
|
259
|
+
const defaultRepo = { full_name: repoName, stargazers_count: 0, html_url: `https://github.com/${repoName}` };
|
|
260
|
+
if (!payload.repository)
|
|
261
|
+
payload.repository = defaultRepo;
|
|
141
262
|
switch (event) {
|
|
142
263
|
case 'push':
|
|
143
|
-
|
|
264
|
+
if (!payload.pusher)
|
|
265
|
+
payload.pusher = { name: payload.sender.login };
|
|
266
|
+
if (!payload.commits)
|
|
267
|
+
payload.commits = [];
|
|
268
|
+
if (!payload.ref)
|
|
269
|
+
payload.ref = 'refs/heads/unknown';
|
|
270
|
+
if (!payload.compare)
|
|
271
|
+
payload.compare = '';
|
|
272
|
+
if (payload.commits.length > 0) {
|
|
273
|
+
payload.commits.forEach((c) => {
|
|
274
|
+
if (!c.author)
|
|
275
|
+
c.author = { name: 'Unknown' };
|
|
276
|
+
if (!c.id)
|
|
277
|
+
c.id = '0000000';
|
|
278
|
+
if (!c.message)
|
|
279
|
+
c.message = 'No message';
|
|
280
|
+
});
|
|
281
|
+
}
|
|
144
282
|
break;
|
|
145
283
|
case 'issues':
|
|
146
|
-
|
|
284
|
+
if (!payload.action)
|
|
285
|
+
payload.action = 'updated';
|
|
286
|
+
if (!payload.issue)
|
|
287
|
+
payload.issue = { number: 0, title: 'Unknown Issue', html_url: '', user: payload.sender };
|
|
288
|
+
if (!payload.issue.user)
|
|
289
|
+
payload.issue.user = payload.sender;
|
|
147
290
|
break;
|
|
148
291
|
case 'pull_request':
|
|
149
|
-
|
|
292
|
+
if (!payload.action)
|
|
293
|
+
payload.action = 'updated';
|
|
294
|
+
if (!payload.pull_request)
|
|
295
|
+
payload.pull_request = { number: 0, title: 'Unknown PR', state: 'unknown', html_url: '', user: payload.sender };
|
|
296
|
+
if (!payload.pull_request.user)
|
|
297
|
+
payload.pull_request.user = payload.sender;
|
|
150
298
|
break;
|
|
151
299
|
case 'star':
|
|
152
|
-
|
|
300
|
+
if (!payload.action)
|
|
301
|
+
payload.action = 'created';
|
|
302
|
+
if (payload.action === 'started')
|
|
303
|
+
payload.action = 'created';
|
|
304
|
+
if (payload.repository && payload.repository.stargazers_count === undefined) {
|
|
305
|
+
payload.repository.stargazers_count = '?';
|
|
306
|
+
}
|
|
153
307
|
break;
|
|
154
308
|
case 'fork':
|
|
155
|
-
|
|
309
|
+
if (!payload.forkee)
|
|
310
|
+
payload.forkee = { full_name: 'unknown/fork' };
|
|
156
311
|
break;
|
|
157
312
|
case 'release':
|
|
158
|
-
|
|
313
|
+
if (!payload.action)
|
|
314
|
+
payload.action = 'published';
|
|
315
|
+
if (!payload.release)
|
|
316
|
+
payload.release = { tag_name: 'unknown', name: 'Unknown Release', html_url: '' };
|
|
159
317
|
break;
|
|
160
318
|
case 'discussion':
|
|
161
|
-
|
|
319
|
+
if (!payload.action)
|
|
320
|
+
payload.action = 'updated';
|
|
321
|
+
if (!payload.discussion)
|
|
322
|
+
payload.discussion = { number: 0, title: 'Unknown Discussion', html_url: '', user: payload.sender };
|
|
323
|
+
if (!payload.discussion.user)
|
|
324
|
+
payload.discussion.user = payload.sender;
|
|
162
325
|
break;
|
|
163
326
|
case 'workflow_run':
|
|
164
|
-
|
|
327
|
+
if (!payload.action)
|
|
328
|
+
payload.action = 'completed';
|
|
329
|
+
if (!payload.workflow_run)
|
|
330
|
+
payload.workflow_run = { conclusion: 'unknown', name: 'Unknown Workflow', head_branch: 'unknown', html_url: '' };
|
|
165
331
|
break;
|
|
166
332
|
case 'issue_comment':
|
|
167
|
-
|
|
333
|
+
if (!payload.action)
|
|
334
|
+
payload.action = 'created';
|
|
335
|
+
if (!payload.issue)
|
|
336
|
+
payload.issue = { number: 0, title: 'Unknown Issue', html_url: '', user: payload.sender };
|
|
337
|
+
if (!payload.comment)
|
|
338
|
+
payload.comment = { body: '', html_url: '' };
|
|
339
|
+
if (!payload.issue.user)
|
|
340
|
+
payload.issue.user = payload.sender;
|
|
168
341
|
break;
|
|
169
342
|
case 'pull_request_review':
|
|
170
|
-
|
|
343
|
+
if (!payload.action)
|
|
344
|
+
payload.action = 'submitted';
|
|
345
|
+
if (!payload.pull_request)
|
|
346
|
+
payload.pull_request = { number: 0, title: 'Unknown PR', html_url: '', user: payload.sender };
|
|
347
|
+
if (!payload.review)
|
|
348
|
+
payload.review = { state: 'unknown', html_url: '' };
|
|
349
|
+
if (!payload.pull_request.user)
|
|
350
|
+
payload.pull_request.user = payload.sender;
|
|
171
351
|
break;
|
|
172
352
|
}
|
|
173
|
-
if (!message) {
|
|
174
|
-
if (this.config.debug) {
|
|
175
|
-
this.ctx.logger('notifier').debug(`Formatter returned null for event ${event}`);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
for (const rule of matchedRules) {
|
|
180
|
-
if (this.config.debug) {
|
|
181
|
-
this.ctx.logger('notifier').debug(`Sending message to channel ${rule.channelId} (platform: ${rule.platform || 'any'})`);
|
|
182
|
-
}
|
|
183
|
-
await this.sendMessage(rule, message);
|
|
184
|
-
}
|
|
185
353
|
}
|
|
186
354
|
async sendMessage(rule, message) {
|
|
187
|
-
// Find suitable bots
|
|
188
355
|
const bots = this.ctx.bots.filter(bot => {
|
|
189
356
|
if (rule.platform)
|
|
190
357
|
return bot.platform === rule.platform;
|
|
191
|
-
return true;
|
|
358
|
+
return true;
|
|
192
359
|
});
|
|
193
360
|
if (bots.length === 0) {
|
|
194
361
|
if (this.config.debug) {
|
|
@@ -204,7 +371,7 @@ class Notifier extends koishi_1.Service {
|
|
|
204
371
|
if (this.config.debug) {
|
|
205
372
|
this.ctx.logger('notifier').info(`Sent message to ${rule.channelId} via ${bot.platform}:${bot.selfId}`);
|
|
206
373
|
}
|
|
207
|
-
break;
|
|
374
|
+
break;
|
|
208
375
|
}
|
|
209
376
|
catch (e) {
|
|
210
377
|
if (this.config.debug) {
|
|
@@ -218,3 +385,4 @@ class Notifier extends koishi_1.Service {
|
|
|
218
385
|
}
|
|
219
386
|
}
|
|
220
387
|
exports.Notifier = Notifier;
|
|
388
|
+
Notifier.inject = ['githubsthFormatter'];
|
package/package.json
CHANGED