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.
@@ -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
- 如果不指定事件,默认订阅: push, issues, issue_comment, pull_request, pull_request_review, release, star, fork
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 = ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'];
62
+ events = [...defaultConfigEvents];
62
63
  }
63
64
  try {
64
65
  // Check if subscription exists
package/lib/config.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface Config {
9
9
  defaultOwner?: string;
10
10
  defaultRepo?: string;
11
11
  debug: boolean;
12
+ logUnhandledEvents: boolean;
13
+ defaultEvents: string[];
12
14
  rules?: Rule[];
13
15
  }
14
16
  export declare const Config: Schema<Config>;
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.substring(0, 7);
16
- const message = c.message.split('\n')[0];
17
- return `[${shortHash}] ${message} - ${c.author.name}`;
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.full_name} 收到新的推送\n` }),
21
- (0, koishi_1.h)('text', { content: `提交者: ${pusher.name}\n` }),
22
- (0, koishi_1.h)('text', { content: `分支: ${payload.ref.replace('refs/heads/', '')}\n` }),
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.full_name} Issue ${action}\n` }),
31
- (0, koishi_1.h)('text', { content: `标题: #${issue.number} ${issue.title}\n` }),
32
- (0, koishi_1.h)('text', { content: `发起人: ${sender.login}\n` }),
33
- (0, koishi_1.h)('text', { content: `链接: ${issue.html_url}` })
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.full_name} PR ${action}\n` }),
40
- (0, koishi_1.h)('text', { content: `标题: #${pull_request.number} ${pull_request.title}\n` }),
41
- (0, koishi_1.h)('text', { content: `发起人: ${sender.login}\n` }),
42
- (0, koishi_1.h)('text', { content: `状态: ${pull_request.state}\n` }),
43
- (0, koishi_1.h)('text', { content: `链接: ${pull_request.html_url}` })
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.login} Star 了仓库 ${repository.full_name} 🌟\n` }),
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.login} Fork 了仓库 ${repository.full_name}\n` }),
59
- (0, koishi_1.h)('text', { content: `新仓库: ${forkee.full_name}` })
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.full_name} 发布了新版本 ${release.tag_name} 🎉\n` }),
68
- (0, koishi_1.h)('text', { content: `标题: ${release.name}\n` }),
69
- (0, koishi_1.h)('text', { content: `链接: ${release.html_url}` })
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.full_name} Discussion ${action}\n` }),
76
- (0, koishi_1.h)('text', { content: `标题: #${discussion.number} ${discussion.title}\n` }),
77
- (0, koishi_1.h)('text', { content: `发起人: ${sender.login}\n` }),
78
- (0, koishi_1.h)('text', { content: `链接: ${discussion.html_url}` })
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.conclusion === 'success' ? '✅' : '❌';
86
+ const statusIcon = workflow_run?.conclusion === 'success' ? '✅' : '❌';
86
87
  return (0, koishi_1.h)('message', [
87
- (0, koishi_1.h)('text', { content: `[GitHub] ${repository.full_name} 工作流运行完成 ${statusIcon}\n` }),
88
- (0, koishi_1.h)('text', { content: `工作流: ${workflow_run.name}\n` }),
89
- (0, koishi_1.h)('text', { content: `结果: ${workflow_run.conclusion}\n` }),
90
- (0, koishi_1.h)('text', { content: `分支: ${workflow_run.head_branch}\n` }),
91
- (0, koishi_1.h)('text', { content: `链接: ${workflow_run.html_url}` })
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.full_name} Issue 收到新评论\n` }),
100
- (0, koishi_1.h)('text', { content: `标题: #${issue.number} ${issue.title}\n` }),
101
- (0, koishi_1.h)('text', { content: `评论人: ${sender.login}\n` }),
102
- (0, koishi_1.h)('text', { content: `内容: ${comment.body.substring(0, 100)}${comment.body.length > 100 ? '...' : ''}\n` }),
103
- (0, koishi_1.h)('text', { content: `链接: ${comment.html_url}` })
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.full_name} PR 收到 Review\n` }),
112
- (0, koishi_1.h)('text', { content: `标题: #${pull_request.number} ${pull_request.title}\n` }),
113
- (0, koishi_1.h)('text', { content: `Reviewer: ${sender.login}\n` }),
114
- (0, koishi_1.h)('text', { content: `状态: ${review.state}\n` }),
115
- (0, koishi_1.h)('text', { content: `链接: ${review.html_url}` })
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
  }
@@ -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
- if (payload.issue && payload.comment)
45
+ // Check inner payload first if it exists
46
+ if (realPayload.issue && realPayload.comment)
40
47
  eventType = 'issue_comment';
41
- else if (payload.issue)
48
+ else if (realPayload.issue)
42
49
  eventType = 'issues';
43
- else if (payload.pull_request && payload.review)
50
+ else if (realPayload.pull_request && realPayload.review)
44
51
  eventType = 'pull_request_review';
45
- else if (payload.pull_request)
52
+ else if (realPayload.pull_request)
46
53
  eventType = 'pull_request';
47
- else if (payload.commits)
54
+ else if (realPayload.commits)
48
55
  eventType = 'push';
49
- else if (payload.starred_at !== undefined || (payload.action === 'started'))
50
- eventType = 'star'; // star event usually has action 'created' but check payload structure
51
- else if (payload.forkee)
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 (payload.release)
60
+ else if (realPayload.release)
54
61
  eventType = 'release';
55
- else if (payload.discussion)
62
+ else if (realPayload.discussion)
56
63
  eventType = 'discussion';
57
- else if (payload.workflow_run)
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
- return;
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
- // Get rules from database
101
- // Try to match both exact name and lowercase name to handle case sensitivity
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
- message = this.ctx.githubsthFormatter.formatPush(realPayload);
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
- message = this.ctx.githubsthFormatter.formatIssue(realPayload);
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
- message = this.ctx.githubsthFormatter.formatPullRequest(realPayload);
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
- message = this.ctx.githubsthFormatter.formatStar(realPayload);
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
- message = this.ctx.githubsthFormatter.formatFork(realPayload);
309
+ if (!payload.forkee)
310
+ payload.forkee = { full_name: 'unknown/fork' };
156
311
  break;
157
312
  case 'release':
158
- message = this.ctx.githubsthFormatter.formatRelease(realPayload);
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
- message = this.ctx.githubsthFormatter.formatDiscussion(realPayload);
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
- message = this.ctx.githubsthFormatter.formatWorkflowRun(realPayload);
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
- message = this.ctx.githubsthFormatter.formatIssueComment(realPayload);
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
- message = this.ctx.githubsthFormatter.formatPullRequestReview(realPayload);
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; // If platform not specified, try all
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; // Break on first success
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-githubsth",
3
- "version": "1.0.2-test2",
3
+ "version": "1.0.3-alpha.1",
4
4
  "description": "Github Subscriptions Notifications, push notifications for GitHub subscriptions For koishi",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",