koishi-plugin-githubsth 1.0.5-alpha.0 โ 1.0.5-alpha.2
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/assets/render-card.html +216 -0
- package/lib/commands/render.js +70 -11
- package/lib/commands/subscribe.js +34 -62
- package/lib/config.d.ts +4 -3
- package/lib/config.js +20 -5
- package/lib/database.d.ts +2 -0
- package/lib/database.js +1 -0
- package/lib/services/notifier.d.ts +7 -6
- package/lib/services/notifier.js +61 -186
- package/lib/services/render-card.d.ts +24 -0
- package/lib/services/render-card.js +220 -0
- package/package.json +5 -3
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<style>
|
|
6
|
+
* { box-sizing: border-box; }
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
width: {{width}}px;
|
|
10
|
+
background: {{background}};
|
|
11
|
+
color: {{text}};
|
|
12
|
+
font-family: {{font}};
|
|
13
|
+
}
|
|
14
|
+
.wrap {
|
|
15
|
+
padding: 18px;
|
|
16
|
+
position: relative;
|
|
17
|
+
}
|
|
18
|
+
.wrap::before {
|
|
19
|
+
content: '';
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
background: {{overlayPattern}};
|
|
24
|
+
opacity: 0.5;
|
|
25
|
+
}
|
|
26
|
+
.card {
|
|
27
|
+
position: relative;
|
|
28
|
+
border-radius: 14px;
|
|
29
|
+
border: 1px solid {{border}};
|
|
30
|
+
background: {{card}};
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.28);
|
|
33
|
+
z-index: 1;
|
|
34
|
+
}
|
|
35
|
+
.card::before {
|
|
36
|
+
content: '';
|
|
37
|
+
position: absolute;
|
|
38
|
+
inset: 0;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
background: {{cardTexture}};
|
|
41
|
+
opacity: 0.28;
|
|
42
|
+
}
|
|
43
|
+
.head {
|
|
44
|
+
position: relative;
|
|
45
|
+
z-index: 1;
|
|
46
|
+
padding: 12px 16px;
|
|
47
|
+
border-bottom: 1px solid {{border}};
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
gap: 12px;
|
|
52
|
+
}
|
|
53
|
+
.head-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
54
|
+
.dot {
|
|
55
|
+
width: 10px;
|
|
56
|
+
height: 10px;
|
|
57
|
+
border-radius: 999px;
|
|
58
|
+
background: {{accent}};
|
|
59
|
+
box-shadow: 0 0 0 3px {{pillBg}};
|
|
60
|
+
}
|
|
61
|
+
.repo {
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
text-overflow: ellipsis;
|
|
66
|
+
letter-spacing: 0.2px;
|
|
67
|
+
}
|
|
68
|
+
.meta {
|
|
69
|
+
color: {{muted}};
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
letter-spacing: 0.3px;
|
|
72
|
+
}
|
|
73
|
+
.pill {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
padding: 2px 9px;
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
border: 1px solid {{border}};
|
|
80
|
+
color: {{pillText}};
|
|
81
|
+
background: {{pillBg}};
|
|
82
|
+
margin-left: 6px;
|
|
83
|
+
}
|
|
84
|
+
.body {
|
|
85
|
+
position: relative;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
padding: 14px 16px 16px;
|
|
88
|
+
}
|
|
89
|
+
.title {
|
|
90
|
+
font-size: 18px;
|
|
91
|
+
font-weight: 700;
|
|
92
|
+
margin-bottom: 6px;
|
|
93
|
+
letter-spacing: 0.2px;
|
|
94
|
+
}
|
|
95
|
+
.sub {
|
|
96
|
+
color: {{muted}};
|
|
97
|
+
margin-bottom: 12px;
|
|
98
|
+
}
|
|
99
|
+
.content {
|
|
100
|
+
border: 1px dashed {{border}};
|
|
101
|
+
border-radius: 10px;
|
|
102
|
+
padding: 10px 12px;
|
|
103
|
+
line-height: 1.62;
|
|
104
|
+
font-size: 14px;
|
|
105
|
+
word-break: break-word;
|
|
106
|
+
margin-top: 8px;
|
|
107
|
+
background: {{contentBg}};
|
|
108
|
+
}
|
|
109
|
+
.commit-list {
|
|
110
|
+
margin-top: 10px;
|
|
111
|
+
border-top: 1px solid {{border}};
|
|
112
|
+
padding-top: 10px;
|
|
113
|
+
}
|
|
114
|
+
.commit-item {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: baseline;
|
|
117
|
+
gap: 6px;
|
|
118
|
+
font-size: 13px;
|
|
119
|
+
margin-bottom: 6px;
|
|
120
|
+
}
|
|
121
|
+
.commit-icon { width: 18px; text-align: center; }
|
|
122
|
+
.commit-hash {
|
|
123
|
+
display: inline-block;
|
|
124
|
+
padding: 1px 6px;
|
|
125
|
+
border-radius: 8px;
|
|
126
|
+
border: 1px solid {{border}};
|
|
127
|
+
background: {{pillBg}};
|
|
128
|
+
color: {{pillText}};
|
|
129
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
130
|
+
}
|
|
131
|
+
.commit-author { color: {{muted}}; }
|
|
132
|
+
|
|
133
|
+
body.style-glass .card {
|
|
134
|
+
border-radius: 18px;
|
|
135
|
+
backdrop-filter: blur(6px);
|
|
136
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
|
|
137
|
+
}
|
|
138
|
+
body.style-glass .head {
|
|
139
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
body.style-neon .card {
|
|
143
|
+
border-width: 2px;
|
|
144
|
+
box-shadow: 0 0 0 1px {{accent}} inset, 0 0 28px rgba(0,0,0,0.38);
|
|
145
|
+
}
|
|
146
|
+
body.style-neon .dot {
|
|
147
|
+
box-shadow: 0 0 0 4px {{pillBg}}, 0 0 14px {{accent}};
|
|
148
|
+
}
|
|
149
|
+
body.style-neon .title {
|
|
150
|
+
text-transform: uppercase;
|
|
151
|
+
letter-spacing: 0.8px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
body.style-compact .wrap { padding: 10px; }
|
|
155
|
+
body.style-compact .head { padding: 10px 12px; }
|
|
156
|
+
body.style-compact .body { padding: 10px 12px 12px; }
|
|
157
|
+
body.style-compact .title { font-size: 16px; }
|
|
158
|
+
body.style-compact .content { font-size: 13px; }
|
|
159
|
+
|
|
160
|
+
body.style-card .card {
|
|
161
|
+
border-radius: 24px;
|
|
162
|
+
border-width: 1px;
|
|
163
|
+
box-shadow: 0 26px 54px rgba(0, 0, 0, 0.35);
|
|
164
|
+
}
|
|
165
|
+
body.style-card .head {
|
|
166
|
+
border-bottom-style: dashed;
|
|
167
|
+
}
|
|
168
|
+
body.style-card .content {
|
|
169
|
+
border-style: solid;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
body.style-terminal .card {
|
|
173
|
+
border-radius: 0;
|
|
174
|
+
border-width: 2px;
|
|
175
|
+
box-shadow: none;
|
|
176
|
+
}
|
|
177
|
+
body.style-terminal .head,
|
|
178
|
+
body.style-terminal .content,
|
|
179
|
+
body.style-terminal .pill,
|
|
180
|
+
body.style-terminal .commit-hash {
|
|
181
|
+
border-radius: 0;
|
|
182
|
+
}
|
|
183
|
+
body.style-terminal .title {
|
|
184
|
+
font-size: 17px;
|
|
185
|
+
letter-spacing: 0.5px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
body.style-github .repo::before {
|
|
189
|
+
content: '#';
|
|
190
|
+
margin-right: 4px;
|
|
191
|
+
color: {{muted}};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
{{extraCss}}
|
|
195
|
+
</style>
|
|
196
|
+
</head>
|
|
197
|
+
<body class="theme-{{themeKey}} style-{{styleVariant}}">
|
|
198
|
+
<div class="wrap">
|
|
199
|
+
<div class="card">
|
|
200
|
+
<div class="head">
|
|
201
|
+
<div class="head-left">
|
|
202
|
+
<span class="dot"></span>
|
|
203
|
+
<span class="repo">{{repo}}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="meta">{{themeTitle}}</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="body">
|
|
208
|
+
<div class="title">{{eventTitle}}</div>
|
|
209
|
+
<div class="sub">by {{actor}}<span class="pill">{{action}}</span>{{statusPills}}</div>
|
|
210
|
+
<div class="content">{{content}}</div>
|
|
211
|
+
{{commitBlock}}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</body>
|
|
216
|
+
</html>
|
package/lib/commands/render.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.apply = apply;
|
|
4
4
|
const modes = ['text', 'image', 'auto'];
|
|
5
|
-
const themes = ['compact', 'card', 'terminal'];
|
|
6
5
|
const events = ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'star', 'fork', 'release', 'discussion', 'workflow_run'];
|
|
7
6
|
function apply(ctx, config) {
|
|
8
7
|
ctx.command('githubsth.render', '้็ฅๆธฒๆ่ฎพ็ฝฎ๏ผๆๆฌ/ๅพ็๏ผ', { authority: 3 })
|
|
@@ -13,7 +12,7 @@ function apply(ctx, config) {
|
|
|
13
12
|
return [
|
|
14
13
|
`mode: ${status.mode} (configured: ${status.configuredMode})`,
|
|
15
14
|
`fallback: ${status.fallback}`,
|
|
16
|
-
`theme: ${status.theme}`,
|
|
15
|
+
`theme(default): ${status.theme}`,
|
|
17
16
|
`width: ${status.width}`,
|
|
18
17
|
`timeout: ${status.timeoutMs}ms`,
|
|
19
18
|
`puppeteer: ${status.hasPuppeteer ? 'ready' : 'missing'}`,
|
|
@@ -27,13 +26,14 @@ function apply(ctx, config) {
|
|
|
27
26
|
ctx.githubsthNotifier.setRenderMode(mode);
|
|
28
27
|
return `ๅทฒๅๆข่ฟ่กๆถๆธฒๆๆจกๅผไธบ ${mode}๏ผ้ๅฏๅๆขๅค้
็ฝฎๅผ ${config.renderMode}๏ผใ`;
|
|
29
28
|
});
|
|
30
|
-
ctx.command('githubsth.render.theme <theme:string>', '
|
|
29
|
+
ctx.command('githubsth.render.theme <theme:string>', '่ฎพ็ฝฎๅ
จๅฑ้ป่ฎคไธป้ข', { authority: 3 })
|
|
31
30
|
.action(async (_, theme) => {
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const normalized = ctx.githubsthNotifier.normalizeTheme(theme);
|
|
32
|
+
if (!normalized) {
|
|
33
|
+
return `ๆ ๆไธป้ขใ่ฏทไฝฟ็จ githubsth.render.themes ๆฅ็ๅฏ็จไธป้ขใ`;
|
|
34
34
|
}
|
|
35
|
-
config.renderTheme =
|
|
36
|
-
return
|
|
35
|
+
config.renderTheme = normalized;
|
|
36
|
+
return `ๅทฒ่ฎพ็ฝฎๅ
จๅฑ้ป่ฎคไธป้ขไธบ ${normalized}๏ผๅฝๅ่ฟ็จ็ๆ๏ผใ`;
|
|
37
37
|
});
|
|
38
38
|
ctx.command('githubsth.render.width <width:number>', '่ฎพ็ฝฎๅพ็ๅฎฝๅบฆ', { authority: 3 })
|
|
39
39
|
.action(async (_, width) => {
|
|
@@ -43,13 +43,72 @@ function apply(ctx, config) {
|
|
|
43
43
|
config.renderWidth = normalized;
|
|
44
44
|
return `ๅทฒ่ฎพ็ฝฎๅพ็ๅฎฝๅบฆไธบ ${normalized}px๏ผๅฝๅ่ฟ็จ็ๆ๏ผใ`;
|
|
45
45
|
});
|
|
46
|
-
ctx.command('githubsth.render.
|
|
47
|
-
.action(async (
|
|
48
|
-
const
|
|
46
|
+
ctx.command('githubsth.render.themes', 'ๆฅ็ไธป้ขๅ่กจ', { authority: 3 })
|
|
47
|
+
.action(async () => {
|
|
48
|
+
const themes = ctx.githubsthNotifier.listThemes();
|
|
49
|
+
return `ๅฏ็จไธป้ข:\n- ${themes.join('\n- ')}`;
|
|
50
|
+
});
|
|
51
|
+
ctx.command('githubsth.render.preview [event:string] [theme:string]', '้ข่ง้็ฅๆธฒๆ', { authority: 3 })
|
|
52
|
+
.action(async ({ session }, event, theme) => {
|
|
53
|
+
const selectedEvent = event && events.includes(event) ? event : 'issue_comment';
|
|
49
54
|
if (event && !events.includes(event)) {
|
|
50
55
|
await session?.send(`ๆช็ฅไบไปถ ${event}๏ผๅทฒๆน็จ้ป่ฎคไบไปถ issue_commentใ`);
|
|
51
56
|
}
|
|
52
|
-
const
|
|
57
|
+
const normalizedTheme = theme ? ctx.githubsthNotifier.normalizeTheme(theme) : null;
|
|
58
|
+
if (theme && !normalizedTheme) {
|
|
59
|
+
await session?.send(`ๆช็ฅไธป้ข ${theme}๏ผๅฐไฝฟ็จ้ป่ฎคไธป้ขใ`);
|
|
60
|
+
}
|
|
61
|
+
const preview = await ctx.githubsthNotifier.renderPreview(selectedEvent, normalizedTheme || undefined);
|
|
53
62
|
return preview || '้ข่งๅคฑ่ดฅ๏ผ่ฏทๆฃๆฅ puppeteer ๆๆธฒๆ้
็ฝฎใ';
|
|
54
63
|
});
|
|
64
|
+
ctx.command('githubsth.render.repo-theme <repo:string> <theme:string>', 'ไธบๅฝๅ้ข้ๆ่ฎข้
่ฎพ็ฝฎๅ็ฌไธป้ข', { authority: 3 })
|
|
65
|
+
.action(async ({ session }, repo, theme) => {
|
|
66
|
+
if (!repo)
|
|
67
|
+
return '่ฏทๆไพไปๅบ๏ผowner/repo๏ผใ';
|
|
68
|
+
if (!session?.channelId)
|
|
69
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
70
|
+
const normalized = ctx.githubsthNotifier.normalizeTheme(theme);
|
|
71
|
+
if (!normalized)
|
|
72
|
+
return 'ไธป้ขไธๅญๅจ๏ผ่ฏทๅ
ๆง่ก githubsth.render.themesใ';
|
|
73
|
+
const target = await ctx.database.get('github_subscription', {
|
|
74
|
+
repo,
|
|
75
|
+
channelId: session.channelId,
|
|
76
|
+
platform: session.platform || 'unknown',
|
|
77
|
+
});
|
|
78
|
+
if (!target.length)
|
|
79
|
+
return 'ๅฝๅ้ข้ๆฒกๆ่ฏฅไปๅบ่ฎข้
ใ';
|
|
80
|
+
await ctx.database.set('github_subscription', { id: target[0].id }, { renderTheme: normalized });
|
|
81
|
+
return `ๅทฒไธบ ${repo} ่ฎพ็ฝฎไธๅฑไธป้ข๏ผ${normalized}`;
|
|
82
|
+
});
|
|
83
|
+
ctx.command('githubsth.render.repo-theme.clear <repo:string>', 'ๆธ
้คๅฝๅ้ข้ๆ่ฎข้
็ไธๅฑไธป้ข', { authority: 3 })
|
|
84
|
+
.action(async ({ session }, repo) => {
|
|
85
|
+
if (!repo)
|
|
86
|
+
return '่ฏทๆไพไปๅบ๏ผowner/repo๏ผใ';
|
|
87
|
+
if (!session?.channelId)
|
|
88
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
89
|
+
const target = await ctx.database.get('github_subscription', {
|
|
90
|
+
repo,
|
|
91
|
+
channelId: session.channelId,
|
|
92
|
+
platform: session.platform || 'unknown',
|
|
93
|
+
});
|
|
94
|
+
if (!target.length)
|
|
95
|
+
return 'ๅฝๅ้ข้ๆฒกๆ่ฏฅไปๅบ่ฎข้
ใ';
|
|
96
|
+
await ctx.database.set('github_subscription', { id: target[0].id }, { renderTheme: null });
|
|
97
|
+
return `ๅทฒๆธ
้ค ${repo} ็ไธๅฑไธป้ข๏ผๅ้ๅฐๅ
จๅฑไธป้ขใ`;
|
|
98
|
+
});
|
|
99
|
+
ctx.command('githubsth.render.repo-theme.list [repo:string]', 'ๆฅ็ๅฝๅ้ข้่ฎข้
็ไธๅฑไธป้ข', { authority: 3 })
|
|
100
|
+
.action(async ({ session }, repo) => {
|
|
101
|
+
if (!session?.channelId)
|
|
102
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
103
|
+
const query = {
|
|
104
|
+
channelId: session.channelId,
|
|
105
|
+
platform: session.platform || 'unknown',
|
|
106
|
+
};
|
|
107
|
+
if (repo)
|
|
108
|
+
query.repo = repo;
|
|
109
|
+
const subs = await ctx.database.get('github_subscription', query);
|
|
110
|
+
if (!subs.length)
|
|
111
|
+
return 'ๅฝๅ้ข้ๆฒกๆๅน้
่ฎข้
ใ';
|
|
112
|
+
return subs.map((sub) => `${sub.repo} => ${sub.renderTheme || '(default)'}`).join('\n');
|
|
113
|
+
});
|
|
55
114
|
}
|
|
@@ -3,101 +3,73 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.apply = apply;
|
|
4
4
|
function apply(ctx, config) {
|
|
5
5
|
const logger = ctx.logger('githubsth');
|
|
6
|
-
const repoRegex = /^[\w-]+\/[\w
|
|
6
|
+
const repoRegex = /^[\w-]+\/[\w-.]+$/;
|
|
7
7
|
const validEvents = [
|
|
8
8
|
'push', 'issues', 'issue_comment', 'pull_request',
|
|
9
9
|
'pull_request_review', 'star', 'fork', 'release',
|
|
10
|
-
'discussion', 'workflow_run'
|
|
10
|
+
'discussion', 'workflow_run',
|
|
11
11
|
];
|
|
12
12
|
const defaultConfigEvents = config.defaultEvents || ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'];
|
|
13
|
-
ctx.command('githubsth.subscribe <repo> [events:text]', '่ฎข้
GitHub
|
|
13
|
+
ctx.command('githubsth.subscribe <repo> [events:text]', '่ฎข้
GitHub ไปๅบไบไปถ')
|
|
14
14
|
.alias('gh.sub')
|
|
15
15
|
.usage(`
|
|
16
|
-
่ฎข้
GitHub
|
|
17
|
-
ๅฆๆไธๆๅฎไบไปถ๏ผ้ป่ฎค่ฎข้
: ${defaultConfigEvents.join(', ')}
|
|
16
|
+
่ฎข้
GitHub ไปๅบ้็ฅใ่ฅไธๆๅฎไบไปถ๏ผ้ป่ฎค่ฎข้
๏ผ${defaultConfigEvents.join(', ')}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
- issue_comment: Issue ่ฏ่ฎบ
|
|
23
|
-
- pull_request: PR ๅๅปบ/ๅ
ณ้ญ/้ๅผ
|
|
24
|
-
- pull_request_review: PR ๅฎกๆฅ
|
|
25
|
-
- star: ๆ ๆ
|
|
26
|
-
- fork: ไปๅบ Fork
|
|
27
|
-
- release: ๅๅธๆฐ็ๆฌ
|
|
28
|
-
- discussion: ่ฎจ่ฎบๅบๆดๆฐ
|
|
29
|
-
- workflow_run: Workflow ่ฟ่ก
|
|
30
|
-
|
|
31
|
-
็คบไพ:
|
|
32
|
-
gh.sub koishijs/koishi
|
|
33
|
-
gh.sub koishijs/koishi push,issues,star
|
|
18
|
+
็คบไพ๏ผ
|
|
19
|
+
- gh.sub koishijs/koishi
|
|
20
|
+
- gh.sub koishijs/koishi push,issues,star
|
|
34
21
|
`)
|
|
35
22
|
.action(async ({ session }, repo, eventsStr) => {
|
|
36
23
|
if (!repo)
|
|
37
|
-
return '
|
|
24
|
+
return '่ฏทๆๅฎไปๅบ๏ผowner/repo๏ผใ';
|
|
38
25
|
if (!repoRegex.test(repo))
|
|
39
|
-
return '
|
|
26
|
+
return 'ไปๅบๆ ผๅผ้่ฏฏ๏ผๅบไธบ owner/repoใ';
|
|
40
27
|
if (!session?.channelId)
|
|
41
|
-
return '
|
|
42
|
-
// Check trusted repo
|
|
28
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
43
29
|
const trusted = await ctx.database.get('github_trusted_repo', { repo, enabled: true });
|
|
44
|
-
if (trusted.length === 0)
|
|
45
|
-
return '
|
|
46
|
-
}
|
|
47
|
-
// Parse events
|
|
30
|
+
if (trusted.length === 0)
|
|
31
|
+
return '่ฏฅไปๅบไธๅจไฟกไปปๅ่กจไธญ๏ผ่ฏทๅ
็ฑ็ฎก็ๅๆทปๅ ใ';
|
|
48
32
|
let events;
|
|
49
33
|
if (eventsStr) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Validate events
|
|
55
|
-
const invalidEvents = events.filter(e => !validEvents.includes(e) && e !== '*');
|
|
56
|
-
if (invalidEvents.length > 0) {
|
|
57
|
-
return `ๆ ๆ็ไบไปถ็ฑปๅ: ${invalidEvents.join(', ')}ใ\nๅฏ้ไบไปถ: ${validEvents.join(', ')}`;
|
|
34
|
+
events = eventsStr.split(/[,๏ผ\s]+/).map((e) => e.trim()).filter(Boolean).map((e) => e.replace(/-/g, '_'));
|
|
35
|
+
const invalidEvents = events.filter((e) => !validEvents.includes(e) && e !== '*');
|
|
36
|
+
if (invalidEvents.length) {
|
|
37
|
+
return `ๆ ๆไบไปถ๏ผ${invalidEvents.join(', ')}\nๅฏ้ไบไปถ๏ผ${validEvents.join(', ')}`;
|
|
58
38
|
}
|
|
59
39
|
}
|
|
60
40
|
else {
|
|
61
|
-
// Default events
|
|
62
41
|
events = [...defaultConfigEvents];
|
|
63
42
|
}
|
|
64
43
|
try {
|
|
65
|
-
// Check if subscription exists
|
|
66
44
|
const existing = await ctx.database.get('github_subscription', {
|
|
67
45
|
repo,
|
|
68
46
|
channelId: session.channelId,
|
|
69
47
|
platform: session.platform || 'unknown',
|
|
70
48
|
});
|
|
71
49
|
if (existing.length > 0) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
events,
|
|
75
|
-
});
|
|
76
|
-
return `ๅทฒๆดๆฐ ${repo} ็่ฎข้
๏ผๅฝๅ็ๅฌไบไปถ: ${events.join(', ')}ใ`;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
// Create new subscription
|
|
80
|
-
await ctx.database.create('github_subscription', {
|
|
81
|
-
repo,
|
|
82
|
-
channelId: session.channelId,
|
|
83
|
-
platform: session.platform || 'unknown',
|
|
84
|
-
events,
|
|
85
|
-
});
|
|
86
|
-
return `ๅทฒ่ฎข้
${repo} ็ ${events.join(', ')} ไบไปถใ`;
|
|
50
|
+
await ctx.database.set('github_subscription', { id: existing[0].id }, { events });
|
|
51
|
+
return `ๅทฒๆดๆฐ่ฎข้
๏ผ${repo}\nไบไปถ๏ผ${events.join(', ')}`;
|
|
87
52
|
}
|
|
53
|
+
await ctx.database.create('github_subscription', {
|
|
54
|
+
repo,
|
|
55
|
+
channelId: session.channelId,
|
|
56
|
+
platform: session.platform || 'unknown',
|
|
57
|
+
events,
|
|
58
|
+
});
|
|
59
|
+
return `ๅทฒ่ฎข้
${repo}\nไบไปถ๏ผ${events.join(', ')}`;
|
|
88
60
|
}
|
|
89
|
-
catch (
|
|
90
|
-
logger.warn(
|
|
91
|
-
return '
|
|
61
|
+
catch (error) {
|
|
62
|
+
logger.warn(error);
|
|
63
|
+
return '่ฎข้
ๅคฑ่ดฅ๏ผ่ฏท็จๅ้่ฏใ';
|
|
92
64
|
}
|
|
93
65
|
});
|
|
94
66
|
ctx.command('githubsth.unsubscribe <repo>', 'ๅๆถ่ฎข้
GitHub ไปๅบ')
|
|
95
67
|
.alias('gh.unsub')
|
|
96
68
|
.action(async ({ session }, repo) => {
|
|
97
69
|
if (!repo)
|
|
98
|
-
return '
|
|
70
|
+
return '่ฏทๆๅฎไปๅบ๏ผowner/repo๏ผใ';
|
|
99
71
|
if (!session?.channelId)
|
|
100
|
-
return '
|
|
72
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
101
73
|
const result = await ctx.database.remove('github_subscription', {
|
|
102
74
|
repo,
|
|
103
75
|
channelId: session.channelId,
|
|
@@ -105,19 +77,19 @@ gh.sub koishijs/koishi push,issues,star
|
|
|
105
77
|
});
|
|
106
78
|
if (result.matched === 0)
|
|
107
79
|
return 'ๆชๆพๅฐ่ฏฅ่ฎข้
ใ';
|
|
108
|
-
return `ๅทฒๅๆถ่ฎข้
${repo}
|
|
80
|
+
return `ๅทฒๅๆถ่ฎข้
${repo}`;
|
|
109
81
|
});
|
|
110
|
-
ctx.command('githubsth.list', '
|
|
82
|
+
ctx.command('githubsth.list', 'ๆฅ็ๅฝๅ้ข้่ฎข้
')
|
|
111
83
|
.alias('gh.list')
|
|
112
84
|
.action(async ({ session }) => {
|
|
113
85
|
if (!session?.channelId)
|
|
114
|
-
return '
|
|
86
|
+
return '่ฏทๅจ็พค่/้ข้ไธญๆง่ก่ฏฅๅฝไปคใ';
|
|
115
87
|
const subs = await ctx.database.get('github_subscription', {
|
|
116
88
|
channelId: session.channelId,
|
|
117
89
|
platform: session.platform || 'unknown',
|
|
118
90
|
});
|
|
119
91
|
if (subs.length === 0)
|
|
120
92
|
return 'ๅฝๅ้ข้ๆฒกๆ่ฎข้
ใ';
|
|
121
|
-
return subs.map(
|
|
93
|
+
return subs.map((sub) => `${sub.repo} [${sub.events.join(', ')}] theme=${sub.renderTheme || '(default)'}`).join('\n');
|
|
122
94
|
});
|
|
123
95
|
}
|
package/lib/config.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Schema } from 'koishi';
|
|
2
|
+
export type RenderMode = 'text' | 'image' | 'auto';
|
|
3
|
+
export type RenderFallback = 'text' | 'drop';
|
|
4
|
+
export type RenderTheme = 'github-light' | 'github-dark' | 'aurora' | 'sunset' | 'matrix' | 'compact' | 'card' | 'terminal';
|
|
2
5
|
export interface Rule {
|
|
3
6
|
repo: string;
|
|
4
7
|
channelId: string;
|
|
5
8
|
platform?: string;
|
|
6
9
|
events: string[];
|
|
10
|
+
renderTheme?: RenderTheme;
|
|
7
11
|
}
|
|
8
|
-
export type RenderMode = 'text' | 'image' | 'auto';
|
|
9
|
-
export type RenderFallback = 'text' | 'drop';
|
|
10
|
-
export type RenderTheme = 'compact' | 'card' | 'terminal';
|
|
11
12
|
export interface Config {
|
|
12
13
|
defaultOwner?: string;
|
|
13
14
|
defaultRepo?: string;
|
package/lib/config.js
CHANGED
|
@@ -25,11 +25,16 @@ exports.Config = koishi_1.Schema.object({
|
|
|
25
25
|
koishi_1.Schema.const('drop').description('ๅพ็ๅคฑ่ดฅๅไธขๅผ'),
|
|
26
26
|
]).default('text').description('ๅพ็ๆธฒๆๅคฑ่ดฅๆถ็ๅ้็ญ็ฅใ'),
|
|
27
27
|
renderTheme: koishi_1.Schema.union([
|
|
28
|
-
koishi_1.Schema.const('
|
|
29
|
-
koishi_1.Schema.const('
|
|
30
|
-
koishi_1.Schema.const('
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
koishi_1.Schema.const('github-light').description('GitHub Light'),
|
|
29
|
+
koishi_1.Schema.const('github-dark').description('GitHub Dark'),
|
|
30
|
+
koishi_1.Schema.const('aurora').description('Aurora'),
|
|
31
|
+
koishi_1.Schema.const('sunset').description('Sunset'),
|
|
32
|
+
koishi_1.Schema.const('matrix').description('Matrix'),
|
|
33
|
+
koishi_1.Schema.const('compact').description('ๅ
ผๅฎน: compact'),
|
|
34
|
+
koishi_1.Schema.const('card').description('ๅ
ผๅฎน: card'),
|
|
35
|
+
koishi_1.Schema.const('terminal').description('ๅ
ผๅฎน: terminal'),
|
|
36
|
+
]).default('github-dark').description('ๅพ็้็ฅไธป้ขใ'),
|
|
37
|
+
renderWidth: koishi_1.Schema.number().min(480).max(1600).default(860).description('ๅพ็ๅฎฝๅบฆ๏ผๅ็ด ๏ผใ'),
|
|
33
38
|
renderTimeoutMs: koishi_1.Schema.number().min(1000).max(60000).default(12000).description('ๅๆฌกๅพ็ๆธฒๆ่ถ
ๆถ๏ผๆฏซ็ง๏ผใ'),
|
|
34
39
|
defaultEvents: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
35
40
|
.default(['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'])
|
|
@@ -39,5 +44,15 @@ exports.Config = koishi_1.Schema.object({
|
|
|
39
44
|
channelId: koishi_1.Schema.string().required(),
|
|
40
45
|
platform: koishi_1.Schema.string(),
|
|
41
46
|
events: koishi_1.Schema.array(koishi_1.Schema.string()).default(['push', 'issues', 'pull_request', 'issue_comment', 'pull_request_review']),
|
|
47
|
+
renderTheme: koishi_1.Schema.union([
|
|
48
|
+
koishi_1.Schema.const('github-light'),
|
|
49
|
+
koishi_1.Schema.const('github-dark'),
|
|
50
|
+
koishi_1.Schema.const('aurora'),
|
|
51
|
+
koishi_1.Schema.const('sunset'),
|
|
52
|
+
koishi_1.Schema.const('matrix'),
|
|
53
|
+
koishi_1.Schema.const('compact'),
|
|
54
|
+
koishi_1.Schema.const('card'),
|
|
55
|
+
koishi_1.Schema.const('terminal'),
|
|
56
|
+
]),
|
|
42
57
|
})).hidden().description('ๅทฒๅบๅผ๏ผไป
ไฟ็ๅ
ผๅฎนใๅปบ่ฎฎๆน็จๆฐๆฎๅบ่ฎข้
็ฎก็ใ'),
|
|
43
58
|
});
|
package/lib/database.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Context } from 'koishi';
|
|
2
|
+
import type { RenderTheme } from './config';
|
|
2
3
|
declare module 'koishi' {
|
|
3
4
|
interface Tables {
|
|
4
5
|
github_subscription: GithubSubscription;
|
|
@@ -12,6 +13,7 @@ export interface GithubSubscription {
|
|
|
12
13
|
channelId: string;
|
|
13
14
|
platform: string;
|
|
14
15
|
events: string[];
|
|
16
|
+
renderTheme?: RenderTheme;
|
|
15
17
|
}
|
|
16
18
|
export interface GithubTrustedRepo {
|
|
17
19
|
id: number;
|
package/lib/database.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Context, Service
|
|
2
|
-
import { Config, RenderMode } from '../config';
|
|
1
|
+
import { Context, Service } from 'koishi';
|
|
2
|
+
import { Config, RenderMode, RenderTheme } from '../config';
|
|
3
3
|
declare module 'koishi' {
|
|
4
4
|
interface Context {
|
|
5
5
|
githubsthNotifier: Notifier;
|
|
@@ -13,26 +13,27 @@ export declare class Notifier extends Service {
|
|
|
13
13
|
private dedupWriteCounter;
|
|
14
14
|
private runtimeRenderMode;
|
|
15
15
|
constructor(ctx: Context, config: Config);
|
|
16
|
+
listThemes(): RenderTheme[];
|
|
17
|
+
normalizeTheme(theme?: string | null): RenderTheme | null;
|
|
16
18
|
setRenderMode(mode: RenderMode): void;
|
|
17
19
|
getRenderMode(): RenderMode;
|
|
18
20
|
getRenderStatus(): {
|
|
19
21
|
mode: RenderMode;
|
|
20
22
|
configuredMode: RenderMode;
|
|
21
23
|
fallback: import("../config").RenderFallback;
|
|
22
|
-
theme:
|
|
24
|
+
theme: RenderTheme;
|
|
23
25
|
width: number;
|
|
24
26
|
timeoutMs: number;
|
|
25
27
|
hasPuppeteer: boolean;
|
|
26
28
|
};
|
|
27
|
-
renderPreview(event?: string
|
|
29
|
+
renderPreview(event?: string, theme?: RenderTheme | null): Promise<any>;
|
|
28
30
|
private registerListeners;
|
|
29
31
|
private handleEvent;
|
|
32
|
+
private resolveRuleTheme;
|
|
30
33
|
private formatByEvent;
|
|
31
34
|
private prepareOutboundMessage;
|
|
32
35
|
private renderTextAsImage;
|
|
33
36
|
private normalizeRenderedImage;
|
|
34
|
-
private buildImageHtml;
|
|
35
|
-
private escapeHtml;
|
|
36
37
|
private extractRepoName;
|
|
37
38
|
private patchPayloadForEvent;
|
|
38
39
|
private buildEventDedupKey;
|
package/lib/services/notifier.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Notifier = void 0;
|
|
4
|
+
const node_buffer_1 = require("node:buffer");
|
|
4
5
|
const koishi_1 = require("koishi");
|
|
6
|
+
const render_card_1 = require("./render-card");
|
|
5
7
|
class Notifier extends koishi_1.Service {
|
|
6
8
|
// @ts-ignore
|
|
7
9
|
constructor(ctx, config) {
|
|
@@ -14,6 +16,12 @@ class Notifier extends koishi_1.Service {
|
|
|
14
16
|
this.ctx.logger('githubsth').info('Notifier service initialized');
|
|
15
17
|
this.registerListeners();
|
|
16
18
|
}
|
|
19
|
+
listThemes() {
|
|
20
|
+
return (0, render_card_1.listRenderThemes)();
|
|
21
|
+
}
|
|
22
|
+
normalizeTheme(theme) {
|
|
23
|
+
return (0, render_card_1.normalizeRenderTheme)(theme);
|
|
24
|
+
}
|
|
17
25
|
setRenderMode(mode) {
|
|
18
26
|
this.runtimeRenderMode = mode;
|
|
19
27
|
}
|
|
@@ -32,10 +40,12 @@ class Notifier extends koishi_1.Service {
|
|
|
32
40
|
hasPuppeteer: Boolean(puppeteer?.render),
|
|
33
41
|
};
|
|
34
42
|
}
|
|
35
|
-
async renderPreview(event = 'issue_comment') {
|
|
43
|
+
async renderPreview(event = 'issue_comment', theme) {
|
|
36
44
|
const payload = this.getPreviewPayload(event);
|
|
37
|
-
const text = this.formatByEvent(event, payload)
|
|
38
|
-
|
|
45
|
+
const text = this.formatByEvent(event, payload)
|
|
46
|
+
|| this.formatByEvent('issue_comment', this.getPreviewPayload('issue_comment'))
|
|
47
|
+
|| 'Preview unavailable.';
|
|
48
|
+
const preview = await this.prepareOutboundMessage(text, event, payload, theme || this.config.renderTheme);
|
|
39
49
|
return preview?.message || null;
|
|
40
50
|
}
|
|
41
51
|
registerListeners() {
|
|
@@ -52,7 +62,6 @@ class Notifier extends koishi_1.Service {
|
|
|
52
62
|
bind('github/fork', 'fork');
|
|
53
63
|
bind('github/release', 'release');
|
|
54
64
|
bind('github/discussion', 'discussion');
|
|
55
|
-
// legacy aliases
|
|
56
65
|
bind('github/issues', 'issues');
|
|
57
66
|
bind('github/pull_request', 'pull_request');
|
|
58
67
|
bind('github/workflow_run', 'workflow_run');
|
|
@@ -64,9 +73,6 @@ class Notifier extends koishi_1.Service {
|
|
|
64
73
|
const payload = session.payload || session.extra || session.data;
|
|
65
74
|
if (!payload)
|
|
66
75
|
return;
|
|
67
|
-
if (this.config.debug) {
|
|
68
|
-
this.ctx.logger('githubsth').info('Found payload in session, attempting to handle');
|
|
69
|
-
}
|
|
70
76
|
const realPayload = payload.payload || payload;
|
|
71
77
|
let eventType = 'unknown';
|
|
72
78
|
if (realPayload.issue && realPayload.comment)
|
|
@@ -91,12 +97,8 @@ class Notifier extends koishi_1.Service {
|
|
|
91
97
|
eventType = 'workflow_run';
|
|
92
98
|
else if (realPayload.repository && (realPayload.action === 'created' || realPayload.action === 'started'))
|
|
93
99
|
eventType = 'star';
|
|
94
|
-
if (eventType !== 'unknown')
|
|
100
|
+
if (eventType !== 'unknown')
|
|
95
101
|
void this.handleEvent(eventType, payload);
|
|
96
|
-
}
|
|
97
|
-
else if (this.config.logUnhandledEvents) {
|
|
98
|
-
this.ctx.logger('githubsth').info(`Unhandled payload structure. Keys: ${Object.keys(realPayload).join(', ')}`);
|
|
99
|
-
}
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -110,46 +112,28 @@ class Notifier extends koishi_1.Service {
|
|
|
110
112
|
realPayload.repository = payload.repository;
|
|
111
113
|
}
|
|
112
114
|
let repoName = this.extractRepoName(payload, realPayload, event);
|
|
113
|
-
if (!realPayload.repository)
|
|
115
|
+
if (!realPayload.repository)
|
|
114
116
|
realPayload.repository = { full_name: repoName || 'Unknown/Repo' };
|
|
115
|
-
|
|
116
|
-
else if (!realPayload.repository.full_name) {
|
|
117
|
+
else if (!realPayload.repository.full_name)
|
|
117
118
|
realPayload.repository.full_name = repoName || 'Unknown/Repo';
|
|
118
|
-
}
|
|
119
119
|
if (!realPayload.sender) {
|
|
120
|
-
if (realPayload.issue?.user)
|
|
120
|
+
if (realPayload.issue?.user)
|
|
121
121
|
realPayload.sender = realPayload.issue.user;
|
|
122
|
-
|
|
123
|
-
else if (realPayload.pull_request?.user) {
|
|
122
|
+
else if (realPayload.pull_request?.user)
|
|
124
123
|
realPayload.sender = realPayload.pull_request.user;
|
|
125
|
-
|
|
126
|
-
else if (realPayload.discussion?.user) {
|
|
124
|
+
else if (realPayload.discussion?.user)
|
|
127
125
|
realPayload.sender = realPayload.discussion.user;
|
|
128
|
-
|
|
129
|
-
else if (realPayload.pusher) {
|
|
126
|
+
else if (realPayload.pusher)
|
|
130
127
|
realPayload.sender = { login: realPayload.pusher.name || 'Pusher' };
|
|
131
|
-
|
|
132
|
-
else {
|
|
128
|
+
else
|
|
133
129
|
realPayload.sender = { login: 'GitHub' };
|
|
134
|
-
}
|
|
135
130
|
}
|
|
136
|
-
if (!(await this.shouldProcessEvent(event, payload, realPayload, repoName)))
|
|
137
|
-
if (this.config.debug) {
|
|
138
|
-
this.ctx.logger('githubsth').info(`Skip duplicated event: ${event} (${repoName || 'unknown'})`);
|
|
139
|
-
}
|
|
131
|
+
if (!(await this.shouldProcessEvent(event, payload, realPayload, repoName)))
|
|
140
132
|
return;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
repoName = repoName || realPayload.repository?.full_name;
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
this.ctx.logger('githubsth').warn(`Failed to patch payload for ${event}:`, error);
|
|
148
|
-
}
|
|
149
|
-
if (!repoName) {
|
|
150
|
-
this.ctx.logger('githubsth').warn('Cannot query rules: repoName is missing');
|
|
133
|
+
this.patchPayloadForEvent(event, realPayload, repoName || 'Unknown/Repo');
|
|
134
|
+
repoName = repoName || realPayload.repository?.full_name;
|
|
135
|
+
if (!repoName)
|
|
151
136
|
return;
|
|
152
|
-
}
|
|
153
137
|
const repoNames = [repoName];
|
|
154
138
|
if (repoName !== repoName.toLowerCase())
|
|
155
139
|
repoNames.push(repoName.toLowerCase());
|
|
@@ -162,66 +146,53 @@ class Notifier extends koishi_1.Service {
|
|
|
162
146
|
const matchedRules = allRules.filter((rule) => rule.events.includes('*') || rule.events.includes(event));
|
|
163
147
|
if (!matchedRules.length)
|
|
164
148
|
return;
|
|
165
|
-
// De-duplicate same delivery target to avoid double push caused by duplicated rules.
|
|
166
149
|
const uniqueRules = Array.from(new Map(matchedRules.map((rule) => [`${repoName}|${rule.channelId}`, rule])).values());
|
|
167
150
|
const textMessage = this.formatByEvent(event, realPayload);
|
|
168
151
|
if (!textMessage)
|
|
169
152
|
return;
|
|
170
|
-
const outbound = await this.prepareOutboundMessage(textMessage);
|
|
171
|
-
if (!outbound) {
|
|
172
|
-
if (this.config.debug)
|
|
173
|
-
this.ctx.logger('githubsth').warn(`Drop message because render failed and fallback=drop (${event}, ${repoName})`);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
153
|
for (const rule of uniqueRules) {
|
|
154
|
+
const theme = this.resolveRuleTheme(rule);
|
|
155
|
+
const outbound = await this.prepareOutboundMessage(textMessage, event, realPayload, theme);
|
|
156
|
+
if (!outbound)
|
|
157
|
+
continue;
|
|
177
158
|
await this.sendMessage(rule, outbound);
|
|
178
159
|
}
|
|
179
160
|
}
|
|
161
|
+
resolveRuleTheme(rule) {
|
|
162
|
+
return this.normalizeTheme(rule.renderTheme) || this.normalizeTheme(this.config.renderTheme) || 'github-dark';
|
|
163
|
+
}
|
|
180
164
|
formatByEvent(event, payload) {
|
|
181
165
|
switch (event) {
|
|
182
|
-
case 'push':
|
|
183
|
-
|
|
184
|
-
case '
|
|
185
|
-
|
|
186
|
-
case '
|
|
187
|
-
|
|
188
|
-
case '
|
|
189
|
-
|
|
190
|
-
case '
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return this.ctx.githubsthFormatter.formatRelease(payload);
|
|
194
|
-
case 'discussion':
|
|
195
|
-
return this.ctx.githubsthFormatter.formatDiscussion(payload);
|
|
196
|
-
case 'workflow_run':
|
|
197
|
-
return this.ctx.githubsthFormatter.formatWorkflowRun(payload);
|
|
198
|
-
case 'issue_comment':
|
|
199
|
-
return this.ctx.githubsthFormatter.formatIssueComment(payload);
|
|
200
|
-
case 'pull_request_review':
|
|
201
|
-
return this.ctx.githubsthFormatter.formatPullRequestReview(payload);
|
|
202
|
-
default:
|
|
203
|
-
return null;
|
|
166
|
+
case 'push': return this.ctx.githubsthFormatter.formatPush(payload);
|
|
167
|
+
case 'issues': return this.ctx.githubsthFormatter.formatIssue(payload);
|
|
168
|
+
case 'pull_request': return this.ctx.githubsthFormatter.formatPullRequest(payload);
|
|
169
|
+
case 'star': return this.ctx.githubsthFormatter.formatStar(payload);
|
|
170
|
+
case 'fork': return this.ctx.githubsthFormatter.formatFork(payload);
|
|
171
|
+
case 'release': return this.ctx.githubsthFormatter.formatRelease(payload);
|
|
172
|
+
case 'discussion': return this.ctx.githubsthFormatter.formatDiscussion(payload);
|
|
173
|
+
case 'workflow_run': return this.ctx.githubsthFormatter.formatWorkflowRun(payload);
|
|
174
|
+
case 'issue_comment': return this.ctx.githubsthFormatter.formatIssueComment(payload);
|
|
175
|
+
case 'pull_request_review': return this.ctx.githubsthFormatter.formatPullRequestReview(payload);
|
|
176
|
+
default: return null;
|
|
204
177
|
}
|
|
205
178
|
}
|
|
206
|
-
async prepareOutboundMessage(textMessage) {
|
|
179
|
+
async prepareOutboundMessage(textMessage, event, payload, theme) {
|
|
207
180
|
const mode = this.getRenderMode();
|
|
208
|
-
if (mode === 'text')
|
|
181
|
+
if (mode === 'text')
|
|
209
182
|
return { message: textMessage, text: textMessage, isImage: false };
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (imageMessage) {
|
|
183
|
+
const imageMessage = await this.renderTextAsImage(textMessage, event, payload, theme);
|
|
184
|
+
if (imageMessage)
|
|
213
185
|
return { message: imageMessage, text: textMessage, isImage: true };
|
|
214
|
-
}
|
|
215
186
|
if (mode === 'image' && this.config.renderFallback === 'drop')
|
|
216
187
|
return null;
|
|
217
188
|
return { message: textMessage, text: textMessage, isImage: false };
|
|
218
189
|
}
|
|
219
|
-
async renderTextAsImage(textMessage) {
|
|
190
|
+
async renderTextAsImage(textMessage, event, payload, theme) {
|
|
220
191
|
const puppeteer = this.ctx.puppeteer;
|
|
221
192
|
if (!puppeteer || typeof puppeteer.render !== 'function')
|
|
222
193
|
return null;
|
|
223
194
|
try {
|
|
224
|
-
const html =
|
|
195
|
+
const html = (0, render_card_1.buildRenderHtml)(textMessage, event, payload, theme, this.config.renderWidth || 860);
|
|
225
196
|
const task = puppeteer.render(html);
|
|
226
197
|
const timeout = this.config.renderTimeoutMs || 12000;
|
|
227
198
|
const rendered = await Promise.race([
|
|
@@ -248,85 +219,12 @@ class Notifier extends koishi_1.Service {
|
|
|
248
219
|
return koishi_1.h.image(trimmed);
|
|
249
220
|
return null;
|
|
250
221
|
}
|
|
251
|
-
if (Buffer.isBuffer(rendered))
|
|
222
|
+
if (node_buffer_1.Buffer.isBuffer(rendered))
|
|
252
223
|
return koishi_1.h.image(rendered, 'image/png');
|
|
253
224
|
if (rendered instanceof Uint8Array)
|
|
254
|
-
return koishi_1.h.image(Buffer.from(rendered), 'image/png');
|
|
225
|
+
return koishi_1.h.image(node_buffer_1.Buffer.from(rendered), 'image/png');
|
|
255
226
|
return null;
|
|
256
227
|
}
|
|
257
|
-
buildImageHtml(textMessage) {
|
|
258
|
-
const escaped = this.escapeHtml(textMessage).replace(/\n/g, '<br/>');
|
|
259
|
-
const width = this.config.renderWidth || 840;
|
|
260
|
-
let bg = 'linear-gradient(135deg, #1f2937, #111827)';
|
|
261
|
-
let card = 'rgba(17, 24, 39, 0.92)';
|
|
262
|
-
let accent = '#22d3ee';
|
|
263
|
-
let font = "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif";
|
|
264
|
-
if (this.config.renderTheme === 'card') {
|
|
265
|
-
bg = 'linear-gradient(140deg, #0f172a, #1e293b)';
|
|
266
|
-
card = 'rgba(15, 23, 42, 0.9)';
|
|
267
|
-
accent = '#60a5fa';
|
|
268
|
-
}
|
|
269
|
-
else if (this.config.renderTheme === 'terminal') {
|
|
270
|
-
bg = '#0b1020';
|
|
271
|
-
card = '#0b1220';
|
|
272
|
-
accent = '#34d399';
|
|
273
|
-
font = "'Consolas', 'Courier New', monospace";
|
|
274
|
-
}
|
|
275
|
-
return `<!doctype html>
|
|
276
|
-
<html>
|
|
277
|
-
<head>
|
|
278
|
-
<meta charset="utf-8" />
|
|
279
|
-
<style>
|
|
280
|
-
* { box-sizing: border-box; }
|
|
281
|
-
body {
|
|
282
|
-
margin: 0;
|
|
283
|
-
width: ${width}px;
|
|
284
|
-
background: ${bg};
|
|
285
|
-
color: #e5e7eb;
|
|
286
|
-
font-family: ${font};
|
|
287
|
-
}
|
|
288
|
-
.wrap {
|
|
289
|
-
padding: 20px;
|
|
290
|
-
}
|
|
291
|
-
.card {
|
|
292
|
-
border-radius: 14px;
|
|
293
|
-
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
294
|
-
background: ${card};
|
|
295
|
-
overflow: hidden;
|
|
296
|
-
}
|
|
297
|
-
.head {
|
|
298
|
-
padding: 12px 16px;
|
|
299
|
-
border-bottom: 1px solid rgba(148, 163, 184, 0.24);
|
|
300
|
-
font-weight: 700;
|
|
301
|
-
color: ${accent};
|
|
302
|
-
letter-spacing: .3px;
|
|
303
|
-
}
|
|
304
|
-
.content {
|
|
305
|
-
padding: 14px 16px;
|
|
306
|
-
line-height: 1.55;
|
|
307
|
-
word-break: break-word;
|
|
308
|
-
font-size: 14px;
|
|
309
|
-
}
|
|
310
|
-
</style>
|
|
311
|
-
</head>
|
|
312
|
-
<body>
|
|
313
|
-
<div class="wrap">
|
|
314
|
-
<div class="card">
|
|
315
|
-
<div class="head">GitHub Notification</div>
|
|
316
|
-
<div class="content">${escaped}</div>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
319
|
-
</body>
|
|
320
|
-
</html>`;
|
|
321
|
-
}
|
|
322
|
-
escapeHtml(input) {
|
|
323
|
-
return String(input)
|
|
324
|
-
.replace(/&/g, '&')
|
|
325
|
-
.replace(/</g, '<')
|
|
326
|
-
.replace(/>/g, '>')
|
|
327
|
-
.replace(/"/g, '"')
|
|
328
|
-
.replace(/'/g, ''');
|
|
329
|
-
}
|
|
330
228
|
extractRepoName(payload, realPayload, event) {
|
|
331
229
|
let repoName = realPayload.repository?.full_name;
|
|
332
230
|
if (!repoName && realPayload.issue?.repository_url) {
|
|
@@ -334,9 +232,8 @@ class Notifier extends koishi_1.Service {
|
|
|
334
232
|
if (parts.length >= 2)
|
|
335
233
|
repoName = `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
|
|
336
234
|
}
|
|
337
|
-
if (!repoName && realPayload.pull_request?.base?.repo?.full_name)
|
|
235
|
+
if (!repoName && realPayload.pull_request?.base?.repo?.full_name)
|
|
338
236
|
repoName = realPayload.pull_request.base.repo.full_name;
|
|
339
|
-
}
|
|
340
237
|
if (!repoName && typeof payload.repoKey === 'string' && payload.repoKey.includes('/'))
|
|
341
238
|
repoName = payload.repoKey;
|
|
342
239
|
if (!repoName && typeof payload.owner === 'string' && typeof payload.repo === 'string')
|
|
@@ -478,29 +375,21 @@ class Notifier extends koishi_1.Service {
|
|
|
478
375
|
createdAt: new Date(),
|
|
479
376
|
});
|
|
480
377
|
this.dedupWriteCounter += 1;
|
|
481
|
-
if (this.dedupWriteCounter % 200 === 0)
|
|
378
|
+
if (this.dedupWriteCounter % 200 === 0)
|
|
482
379
|
void this.cleanupDedupTable();
|
|
483
|
-
}
|
|
484
380
|
}
|
|
485
381
|
catch (error) {
|
|
486
382
|
if (error?.code === 'SQLITE_CONSTRAINT')
|
|
487
383
|
return false;
|
|
488
|
-
this.ctx.logger('githubsth').warn('Failed to write dedup record, fallback to in-memory dedup only:', error);
|
|
489
384
|
}
|
|
490
385
|
return true;
|
|
491
386
|
}
|
|
492
387
|
async cleanupDedupTable() {
|
|
493
388
|
const cutoff = new Date(Date.now() - this.config.dedupRetentionHours * 60 * 60 * 1000);
|
|
494
389
|
try {
|
|
495
|
-
await this.ctx.database.remove('github_event_dedup', {
|
|
496
|
-
createdAt: { $lt: cutoff },
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
if (this.config.debug) {
|
|
501
|
-
this.ctx.logger('githubsth').warn('Failed to cleanup dedup table:', error);
|
|
502
|
-
}
|
|
390
|
+
await this.ctx.database.remove('github_event_dedup', { createdAt: { $lt: cutoff } });
|
|
503
391
|
}
|
|
392
|
+
catch { }
|
|
504
393
|
}
|
|
505
394
|
async sendMessage(rule, outbound) {
|
|
506
395
|
const bots = this.ctx.bots.filter((bot) => !rule.platform || bot.platform === rule.platform);
|
|
@@ -520,13 +409,8 @@ class Notifier extends koishi_1.Service {
|
|
|
520
409
|
this.ctx.logger('notifier').warn(`Image failed on ${bot.platform}:${bot.selfId}, fallback to text succeeded.`);
|
|
521
410
|
return;
|
|
522
411
|
}
|
|
523
|
-
catch
|
|
524
|
-
if (this.config.debug)
|
|
525
|
-
this.ctx.logger('notifier').warn(`Fallback text send failed: ${fallbackError}`);
|
|
526
|
-
}
|
|
412
|
+
catch { }
|
|
527
413
|
}
|
|
528
|
-
if (this.config.debug)
|
|
529
|
-
this.ctx.logger('notifier').warn(`Bot ${bot.sid} failed to send message with retries: ${error}`);
|
|
530
414
|
}
|
|
531
415
|
}
|
|
532
416
|
this.ctx.logger('notifier').warn(`Failed to send message to ${rule.channelId}`);
|
|
@@ -544,8 +428,7 @@ class Notifier extends koishi_1.Service {
|
|
|
544
428
|
lastError = error;
|
|
545
429
|
if (attempt >= retryCount)
|
|
546
430
|
break;
|
|
547
|
-
|
|
548
|
-
await this.sleep(delay);
|
|
431
|
+
await this.sleep(baseDelay * Math.pow(2, attempt));
|
|
549
432
|
}
|
|
550
433
|
}
|
|
551
434
|
throw lastError;
|
|
@@ -554,21 +437,17 @@ class Notifier extends koishi_1.Service {
|
|
|
554
437
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
555
438
|
}
|
|
556
439
|
getPreviewPayload(event) {
|
|
557
|
-
const baseRepo = { full_name: 'acmuhan/JackalClientDocs', stargazers_count: 128 };
|
|
558
|
-
const baseUser = { login: 'acmuhan' };
|
|
559
440
|
const payload = {
|
|
560
441
|
action: 'created',
|
|
561
|
-
repository:
|
|
562
|
-
sender:
|
|
442
|
+
repository: { full_name: 'acmuhan/JackalClientDocs', stargazers_count: 128 },
|
|
443
|
+
sender: { login: 'vercel[bot]' },
|
|
563
444
|
issue: {
|
|
564
445
|
number: 29,
|
|
565
446
|
title: 'Delete demobot',
|
|
566
447
|
html_url: 'https://github.com/acmuhan/JackalClientDocs/issues/29',
|
|
567
448
|
pull_request: { html_url: 'https://github.com/acmuhan/JackalClientDocs/pull/29' },
|
|
568
449
|
},
|
|
569
|
-
comment: {
|
|
570
|
-
body: '[vc]: #gOsUN...=eyJpc01vbm9yZXBvIjp0cnVl...'
|
|
571
|
-
},
|
|
450
|
+
comment: { body: '[vc]: #gOsUN...=eyJpc01vbm9yZXBvIjp0cnVl...' },
|
|
572
451
|
pull_request: {
|
|
573
452
|
number: 29,
|
|
574
453
|
title: 'Delete demobot',
|
|
@@ -581,9 +460,7 @@ class Notifier extends koishi_1.Service {
|
|
|
581
460
|
head_branch: 'main',
|
|
582
461
|
html_url: 'https://github.com/acmuhan/JackalClientDocs/actions/runs/1',
|
|
583
462
|
},
|
|
584
|
-
commits: [
|
|
585
|
-
{ id: 'ea5eaddca38f25ce013ee50d70addb49c8d28844', message: 'Delete demobot', author: { name: 'MuHan' } },
|
|
586
|
-
],
|
|
463
|
+
commits: [{ id: 'ea5eaddca38f25ce013ee50d70addb49c8d28844', message: 'feat: delete demobot', author: { name: 'MuHan' } }],
|
|
587
464
|
ref: 'refs/heads/main',
|
|
588
465
|
compare: 'https://github.com/acmuhan/JackalClientDocs/compare/old...new',
|
|
589
466
|
pusher: { name: 'acmuhan' },
|
|
@@ -592,8 +469,6 @@ class Notifier extends koishi_1.Service {
|
|
|
592
469
|
discussion: { number: 7, title: 'Roadmap', html_url: 'https://github.com/acmuhan/JackalClientDocs/discussions/7' },
|
|
593
470
|
review: { state: 'approved', html_url: 'https://github.com/acmuhan/JackalClientDocs/pull/29#pullrequestreview-1' },
|
|
594
471
|
};
|
|
595
|
-
if (event === 'star')
|
|
596
|
-
payload.action = 'created';
|
|
597
472
|
if (event === 'workflow_run')
|
|
598
473
|
payload.action = 'completed';
|
|
599
474
|
if (event === 'pull_request_review')
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RenderTheme } from '../config';
|
|
2
|
+
type StyleVariant = 'github' | 'glass' | 'neon' | 'compact' | 'card' | 'terminal';
|
|
3
|
+
export type ThemePreset = {
|
|
4
|
+
key: string;
|
|
5
|
+
title: string;
|
|
6
|
+
style: StyleVariant;
|
|
7
|
+
background: string;
|
|
8
|
+
card: string;
|
|
9
|
+
border: string;
|
|
10
|
+
text: string;
|
|
11
|
+
muted: string;
|
|
12
|
+
accent: string;
|
|
13
|
+
pillText: string;
|
|
14
|
+
pillBg: string;
|
|
15
|
+
contentBg: string;
|
|
16
|
+
overlayPattern: string;
|
|
17
|
+
cardTexture: string;
|
|
18
|
+
extraCss: string;
|
|
19
|
+
font: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function listRenderThemes(): RenderTheme[];
|
|
22
|
+
export declare function normalizeRenderTheme(theme?: string | null): RenderTheme | null;
|
|
23
|
+
export declare function buildRenderHtml(textMessage: string, event: string, payload: any, theme: RenderTheme, width: number): string;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listRenderThemes = listRenderThemes;
|
|
4
|
+
exports.normalizeRenderTheme = normalizeRenderTheme;
|
|
5
|
+
exports.buildRenderHtml = buildRenderHtml;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const NO_PATTERN = 'none';
|
|
9
|
+
const NO_TEXTURE = 'none';
|
|
10
|
+
const THEME_PRESETS = {
|
|
11
|
+
'github-light': {
|
|
12
|
+
key: 'github-light', title: 'GitHub Light', style: 'github',
|
|
13
|
+
background: 'linear-gradient(145deg, #f6f8fa, #eef2f7)', card: '#ffffff', border: '#d0d7de',
|
|
14
|
+
text: '#24292f', muted: '#57606a', accent: '#0969da', pillText: '#0969da', pillBg: '#ddf4ff',
|
|
15
|
+
contentBg: 'rgba(246,248,250,0.72)',
|
|
16
|
+
overlayPattern: NO_PATTERN,
|
|
17
|
+
cardTexture: NO_TEXTURE,
|
|
18
|
+
extraCss: '.theme-github-light .head { background: linear-gradient(180deg, #ffffff, #f9fbfd); }',
|
|
19
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
20
|
+
},
|
|
21
|
+
'github-dark': {
|
|
22
|
+
key: 'github-dark', title: 'GitHub Dark', style: 'github',
|
|
23
|
+
background: 'linear-gradient(145deg, #0d1117, #161b22)', card: '#161b22', border: '#30363d',
|
|
24
|
+
text: '#c9d1d9', muted: '#8b949e', accent: '#58a6ff', pillText: '#58a6ff', pillBg: 'rgba(56,139,253,0.15)',
|
|
25
|
+
contentBg: 'rgba(13,17,23,0.45)',
|
|
26
|
+
overlayPattern: NO_PATTERN,
|
|
27
|
+
cardTexture: NO_TEXTURE,
|
|
28
|
+
extraCss: '.theme-github-dark .head { background: linear-gradient(180deg, rgba(88,166,255,0.07), rgba(22,27,34,0)); }',
|
|
29
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
30
|
+
},
|
|
31
|
+
aurora: {
|
|
32
|
+
key: 'aurora', title: 'Aurora', style: 'glass',
|
|
33
|
+
background: 'linear-gradient(155deg, #07203f, #2b5876)', card: 'rgba(12,24,42,0.86)', border: 'rgba(143,211,244,0.35)',
|
|
34
|
+
text: '#e8f5ff', muted: '#b7d7ef', accent: '#7dd3fc', pillText: '#7dd3fc', pillBg: 'rgba(125,211,252,0.16)',
|
|
35
|
+
contentBg: 'rgba(10,21,37,0.5)',
|
|
36
|
+
overlayPattern: 'radial-gradient(circle at 18% 15%, rgba(125,211,252,0.24), transparent 30%), radial-gradient(circle at 82% 80%, rgba(56,189,248,0.24), transparent 26%)',
|
|
37
|
+
cardTexture: 'linear-gradient(135deg, rgba(255,255,255,0.14) 0%, transparent 32%, rgba(255,255,255,0.06) 70%, transparent 100%)',
|
|
38
|
+
extraCss: '.theme-aurora .title { text-shadow: 0 2px 16px rgba(125,211,252,0.25); }',
|
|
39
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
40
|
+
},
|
|
41
|
+
sunset: {
|
|
42
|
+
key: 'sunset', title: 'Sunset', style: 'card',
|
|
43
|
+
background: 'linear-gradient(155deg, #3f2b96, #a83279)', card: 'rgba(46,16,57,0.9)', border: 'rgba(255,183,197,0.34)',
|
|
44
|
+
text: '#fff1f6', muted: '#ffd2e4', accent: '#fda4af', pillText: '#fecdd3', pillBg: 'rgba(253,164,175,0.18)',
|
|
45
|
+
contentBg: 'rgba(86,28,84,0.32)',
|
|
46
|
+
overlayPattern: 'radial-gradient(circle at 85% 16%, rgba(255,219,234,0.28), transparent 28%), radial-gradient(circle at 10% 78%, rgba(249,115,22,0.2), transparent 34%)',
|
|
47
|
+
cardTexture: 'linear-gradient(160deg, rgba(255,255,255,0.12), transparent 46%)',
|
|
48
|
+
extraCss: '.theme-sunset .head { background: linear-gradient(120deg, rgba(253,164,175,0.2), rgba(168,85,247,0.12)); }',
|
|
49
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
50
|
+
},
|
|
51
|
+
matrix: {
|
|
52
|
+
key: 'matrix', title: 'Matrix', style: 'neon',
|
|
53
|
+
background: '#07140d', card: '#0a1e13', border: '#1f5136',
|
|
54
|
+
text: '#b8f7cc', muted: '#7fd79f', accent: '#34d399', pillText: '#34d399', pillBg: 'rgba(52,211,153,0.16)',
|
|
55
|
+
contentBg: 'rgba(9,30,18,0.62)',
|
|
56
|
+
overlayPattern: 'repeating-linear-gradient(180deg, rgba(52,211,153,0.06), rgba(52,211,153,0.06) 1px, transparent 1px, transparent 6px)',
|
|
57
|
+
cardTexture: 'linear-gradient(0deg, rgba(52,211,153,0.07), transparent 40%)',
|
|
58
|
+
extraCss: '.theme-matrix .commit-item { letter-spacing: 0.2px; } .theme-matrix .title { color: #8af5b7; }',
|
|
59
|
+
font: "'Consolas', 'Courier New', monospace",
|
|
60
|
+
},
|
|
61
|
+
compact: {
|
|
62
|
+
key: 'compact', title: 'Compact', style: 'compact',
|
|
63
|
+
background: 'linear-gradient(145deg, #1f2937, #111827)', card: 'rgba(17,24,39,0.92)', border: 'rgba(148,163,184,0.30)',
|
|
64
|
+
text: '#e5e7eb', muted: '#94a3b8', accent: '#22d3ee', pillText: '#22d3ee', pillBg: 'rgba(34,211,238,0.15)',
|
|
65
|
+
contentBg: 'rgba(15,23,42,0.5)',
|
|
66
|
+
overlayPattern: NO_PATTERN,
|
|
67
|
+
cardTexture: NO_TEXTURE,
|
|
68
|
+
extraCss: '.theme-compact .meta { display: none; } .theme-compact .commit-item { margin-bottom: 4px; }',
|
|
69
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
70
|
+
},
|
|
71
|
+
card: {
|
|
72
|
+
key: 'card', title: 'Card', style: 'card',
|
|
73
|
+
background: 'linear-gradient(145deg, #0f172a, #1e293b)', card: 'rgba(15,23,42,0.92)', border: 'rgba(148,163,184,0.30)',
|
|
74
|
+
text: '#e2e8f0', muted: '#94a3b8', accent: '#60a5fa', pillText: '#60a5fa', pillBg: 'rgba(96,165,250,0.16)',
|
|
75
|
+
contentBg: 'rgba(30,41,59,0.42)',
|
|
76
|
+
overlayPattern: 'radial-gradient(circle at 80% 20%, rgba(96,165,250,0.2), transparent 35%)',
|
|
77
|
+
cardTexture: 'linear-gradient(180deg, rgba(255,255,255,0.07), transparent 18%)',
|
|
78
|
+
extraCss: '.theme-card .title { font-size: 20px; } .theme-card .pill { border-width: 1px; }',
|
|
79
|
+
font: "'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
80
|
+
},
|
|
81
|
+
terminal: {
|
|
82
|
+
key: 'terminal', title: 'Terminal', style: 'terminal',
|
|
83
|
+
background: '#0b1020', card: '#0b1220', border: '#1f2a44',
|
|
84
|
+
text: '#d1fae5', muted: '#6ee7b7', accent: '#34d399', pillText: '#34d399', pillBg: 'rgba(52,211,153,0.18)',
|
|
85
|
+
contentBg: 'rgba(8,15,28,0.62)',
|
|
86
|
+
overlayPattern: 'repeating-linear-gradient(0deg, transparent, transparent 23px, rgba(52,211,153,0.08) 24px)',
|
|
87
|
+
cardTexture: NO_TEXTURE,
|
|
88
|
+
extraCss: '.theme-terminal .repo, .theme-terminal .title { text-transform: uppercase; }',
|
|
89
|
+
font: "'Consolas', 'Courier New', monospace",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const THEME_ALIASES = {
|
|
93
|
+
'gh-light': 'github-light',
|
|
94
|
+
'gh-dark': 'github-dark',
|
|
95
|
+
};
|
|
96
|
+
let templateCache = '';
|
|
97
|
+
function getTemplate() {
|
|
98
|
+
if (!templateCache) {
|
|
99
|
+
const templatePath = (0, node_path_1.join)(__dirname, '..', 'assets', 'render-card.html');
|
|
100
|
+
templateCache = (0, node_fs_1.readFileSync)(templatePath, 'utf8');
|
|
101
|
+
}
|
|
102
|
+
return templateCache;
|
|
103
|
+
}
|
|
104
|
+
function escapeHtml(input) {
|
|
105
|
+
return String(input)
|
|
106
|
+
.replace(/&/g, '&')
|
|
107
|
+
.replace(/</g, '<')
|
|
108
|
+
.replace(/>/g, '>')
|
|
109
|
+
.replace(/"/g, '"')
|
|
110
|
+
.replace(/'/g, ''');
|
|
111
|
+
}
|
|
112
|
+
function pickCommitIcon(message) {
|
|
113
|
+
const lower = (message || '').toLowerCase();
|
|
114
|
+
if (lower.startsWith('feat'))
|
|
115
|
+
return 'โจ';
|
|
116
|
+
if (lower.startsWith('fix'))
|
|
117
|
+
return '๐';
|
|
118
|
+
if (lower.startsWith('docs'))
|
|
119
|
+
return '๐';
|
|
120
|
+
if (lower.startsWith('refactor'))
|
|
121
|
+
return 'โป๏ธ';
|
|
122
|
+
if (lower.startsWith('test'))
|
|
123
|
+
return 'โ
';
|
|
124
|
+
if (lower.startsWith('chore'))
|
|
125
|
+
return '๐งน';
|
|
126
|
+
return '๐';
|
|
127
|
+
}
|
|
128
|
+
function getEventTitle(event, payload) {
|
|
129
|
+
switch (event) {
|
|
130
|
+
case 'push': return '๐ Push Event';
|
|
131
|
+
case 'issues': return '๐ Issue Update';
|
|
132
|
+
case 'issue_comment': return payload?.issue?.pull_request ? '๐ฌ Pull Request Comment' : '๐ฌ Issue Comment';
|
|
133
|
+
case 'pull_request': return '๐ Pull Request Update';
|
|
134
|
+
case 'pull_request_review': return 'โ
Pull Request Review';
|
|
135
|
+
case 'release': return '๐ฆ Release Published';
|
|
136
|
+
case 'workflow_run': return 'โ๏ธ Workflow Completed';
|
|
137
|
+
case 'discussion': return '๐งต Discussion Update';
|
|
138
|
+
case 'star': return 'โญ New Star';
|
|
139
|
+
case 'fork': return '๐ด Repository Forked';
|
|
140
|
+
default: return '๐ GitHub Notification';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function buildStatusPills(event, payload) {
|
|
144
|
+
const pills = [];
|
|
145
|
+
if (event === 'pull_request' && payload?.pull_request?.state)
|
|
146
|
+
pills.push(payload.pull_request.state);
|
|
147
|
+
if (event === 'workflow_run' && payload?.workflow_run?.conclusion)
|
|
148
|
+
pills.push(payload.workflow_run.conclusion);
|
|
149
|
+
if (event === 'release' && payload?.release?.tag_name)
|
|
150
|
+
pills.push(payload.release.tag_name);
|
|
151
|
+
return pills
|
|
152
|
+
.slice(0, 3)
|
|
153
|
+
.map((item) => `<span class="pill">${escapeHtml(String(item))}</span>`)
|
|
154
|
+
.join('');
|
|
155
|
+
}
|
|
156
|
+
function buildCommitBlock(event, payload) {
|
|
157
|
+
if (event !== 'push' || !Array.isArray(payload?.commits) || payload.commits.length === 0)
|
|
158
|
+
return '';
|
|
159
|
+
const rows = payload.commits.slice(0, 6).map((commit) => {
|
|
160
|
+
const hash = escapeHtml((commit.id || '0000000').slice(0, 7));
|
|
161
|
+
const message = String(commit.message || '').split('\n')[0];
|
|
162
|
+
const icon = pickCommitIcon(message);
|
|
163
|
+
const author = escapeHtml(commit.author?.name || 'unknown');
|
|
164
|
+
return `<div class="commit-item"><span class="commit-icon">${icon}</span><span class="commit-hash">${hash}</span><span class="commit-msg">${escapeHtml(message)}</span><span class="commit-author">- ${author}</span></div>`;
|
|
165
|
+
}).join('');
|
|
166
|
+
return `<div class="commit-list">${rows}</div>`;
|
|
167
|
+
}
|
|
168
|
+
function listRenderThemes() {
|
|
169
|
+
return Object.keys(THEME_PRESETS);
|
|
170
|
+
}
|
|
171
|
+
function normalizeRenderTheme(theme) {
|
|
172
|
+
if (!theme)
|
|
173
|
+
return null;
|
|
174
|
+
const key = theme.trim().toLowerCase();
|
|
175
|
+
const direct = listRenderThemes().find((item) => item === key);
|
|
176
|
+
if (direct)
|
|
177
|
+
return direct;
|
|
178
|
+
return THEME_ALIASES[key] || null;
|
|
179
|
+
}
|
|
180
|
+
function buildRenderHtml(textMessage, event, payload, theme, width) {
|
|
181
|
+
const preset = THEME_PRESETS[theme] || THEME_PRESETS['github-dark'];
|
|
182
|
+
const content = escapeHtml(textMessage).replace(/\n/g, '<br/>');
|
|
183
|
+
const repo = escapeHtml(payload?.repository?.full_name || 'unknown/repo');
|
|
184
|
+
const actor = escapeHtml(payload?.sender?.login || payload?.pusher?.name || 'github');
|
|
185
|
+
const action = escapeHtml(payload?.action || 'updated');
|
|
186
|
+
const eventTitle = escapeHtml(getEventTitle(event, payload));
|
|
187
|
+
const statusPills = buildStatusPills(event, payload);
|
|
188
|
+
const commitBlock = buildCommitBlock(event, payload);
|
|
189
|
+
const variables = {
|
|
190
|
+
width: String(width || 860),
|
|
191
|
+
themeKey: preset.key,
|
|
192
|
+
styleVariant: preset.style,
|
|
193
|
+
background: preset.background,
|
|
194
|
+
card: preset.card,
|
|
195
|
+
border: preset.border,
|
|
196
|
+
text: preset.text,
|
|
197
|
+
muted: preset.muted,
|
|
198
|
+
accent: preset.accent,
|
|
199
|
+
pillText: preset.pillText,
|
|
200
|
+
pillBg: preset.pillBg,
|
|
201
|
+
contentBg: preset.contentBg,
|
|
202
|
+
overlayPattern: preset.overlayPattern,
|
|
203
|
+
cardTexture: preset.cardTexture,
|
|
204
|
+
extraCss: preset.extraCss,
|
|
205
|
+
font: preset.font,
|
|
206
|
+
repo,
|
|
207
|
+
themeTitle: escapeHtml(preset.title),
|
|
208
|
+
eventTitle,
|
|
209
|
+
actor,
|
|
210
|
+
action,
|
|
211
|
+
statusPills,
|
|
212
|
+
content,
|
|
213
|
+
commitBlock,
|
|
214
|
+
};
|
|
215
|
+
let html = getTemplate();
|
|
216
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
217
|
+
html = html.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
218
|
+
}
|
|
219
|
+
return html;
|
|
220
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-githubsth",
|
|
3
|
-
"version": "1.0.5-alpha.
|
|
3
|
+
"version": "1.0.5-alpha.2",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
13
|
+
"build": "tsc && node scripts/copy-assets.js",
|
|
14
14
|
"dev": "tsc -w",
|
|
15
15
|
"test": "jest"
|
|
16
16
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"koishi": {
|
|
36
36
|
"description": {
|
|
37
37
|
"en": "Github Subscriptions Notifications, push notifications for GitHub subscriptions",
|
|
38
|
-
"zh": "GitHub
|
|
38
|
+
"zh": "GitHub ่ฎข้
ๆจ้้็ฅ๏ผไพ่ต GitHub ้้
ๅจ"
|
|
39
39
|
},
|
|
40
40
|
"service": {
|
|
41
41
|
"required": [
|
|
@@ -47,3 +47,5 @@
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
|