koishi-plugin-githubsth 1.0.5-alpha.1 → 1.0.5-alpha.3
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 +198 -0
- package/lib/commands/render.js +119 -45
- package/lib/commands/subscribe.js +22 -20
- package/lib/config.d.ts +6 -0
- package/lib/config.js +47 -41
- package/lib/database.d.ts +2 -1
- package/lib/database.js +1 -0
- package/lib/index.js +20 -0
- package/lib/services/notifier.d.ts +11 -7
- package/lib/services/notifier.js +86 -285
- package/lib/services/render-card.d.ts +27 -0
- package/lib/services/render-card.js +322 -0
- package/package.json +5 -3
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
--status-accent: {{statusAccent}};
|
|
14
|
+
}
|
|
15
|
+
.wrap { padding: 18px; position: relative; }
|
|
16
|
+
.wrap::before {
|
|
17
|
+
content: '';
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset: 0;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
background: {{overlayPattern}};
|
|
22
|
+
opacity: 0.5;
|
|
23
|
+
}
|
|
24
|
+
.card {
|
|
25
|
+
position: relative;
|
|
26
|
+
border-radius: 14px;
|
|
27
|
+
border: 1px solid {{border}};
|
|
28
|
+
background: {{card}};
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
box-shadow: 0 12px 34px rgba(0, 0, 0, 0.28);
|
|
31
|
+
z-index: 1;
|
|
32
|
+
}
|
|
33
|
+
.card::before {
|
|
34
|
+
content: '';
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 0;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
background: {{cardTexture}};
|
|
39
|
+
opacity: 0.28;
|
|
40
|
+
}
|
|
41
|
+
.status-bar {
|
|
42
|
+
position: relative;
|
|
43
|
+
z-index: 2;
|
|
44
|
+
height: 3px;
|
|
45
|
+
background: linear-gradient(90deg, var(--status-accent), transparent);
|
|
46
|
+
}
|
|
47
|
+
.head {
|
|
48
|
+
position: relative;
|
|
49
|
+
z-index: 1;
|
|
50
|
+
padding: 12px 16px;
|
|
51
|
+
border-bottom: 1px solid {{border}};
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
gap: 12px;
|
|
56
|
+
}
|
|
57
|
+
.head-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
58
|
+
.dot { width: 10px; height: 10px; border-radius: 999px; background: var(--status-accent); box-shadow: 0 0 0 3px {{pillBg}}; }
|
|
59
|
+
.repo { font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: 0.2px; }
|
|
60
|
+
.meta { color: {{muted}}; font-size: 12px; letter-spacing: 0.3px; }
|
|
61
|
+
.type-badge {
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
padding: 2px 8px;
|
|
65
|
+
border-radius: 999px;
|
|
66
|
+
font-size: 11px;
|
|
67
|
+
border: 1px solid {{border}};
|
|
68
|
+
color: var(--status-accent);
|
|
69
|
+
margin-right: 8px;
|
|
70
|
+
}
|
|
71
|
+
.pill {
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
padding: 2px 9px;
|
|
75
|
+
border-radius: 999px;
|
|
76
|
+
font-size: 12px;
|
|
77
|
+
border: 1px solid {{border}};
|
|
78
|
+
color: {{pillText}};
|
|
79
|
+
background: {{pillBg}};
|
|
80
|
+
margin-left: 6px;
|
|
81
|
+
}
|
|
82
|
+
.body { position: relative; z-index: 1; padding: 14px 16px 16px; }
|
|
83
|
+
.title { font-size: 18px; font-weight: 700; margin-bottom: 6px; letter-spacing: 0.2px; }
|
|
84
|
+
.sub { color: {{muted}}; margin-bottom: 12px; }
|
|
85
|
+
.content {
|
|
86
|
+
border: 1px dashed {{border}};
|
|
87
|
+
border-radius: 10px;
|
|
88
|
+
padding: 10px 12px;
|
|
89
|
+
line-height: 1.62;
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
word-break: break-word;
|
|
92
|
+
margin-top: 8px;
|
|
93
|
+
background: {{contentBg}};
|
|
94
|
+
}
|
|
95
|
+
.detail-grid {
|
|
96
|
+
margin-top: 10px;
|
|
97
|
+
border: 1px solid {{border}};
|
|
98
|
+
border-radius: 10px;
|
|
99
|
+
background: {{contentBg}};
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
}
|
|
102
|
+
.detail-row {
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: 120px 1fr;
|
|
105
|
+
gap: 10px;
|
|
106
|
+
padding: 8px 10px;
|
|
107
|
+
border-bottom: 1px solid {{border}};
|
|
108
|
+
font-size: 13px;
|
|
109
|
+
}
|
|
110
|
+
.detail-row:last-child { border-bottom: 0; }
|
|
111
|
+
.detail-key { color: {{muted}}; }
|
|
112
|
+
.detail-value { word-break: break-word; }
|
|
113
|
+
.commit-list { margin-top: 10px; border-top: 1px solid {{border}}; padding-top: 10px; }
|
|
114
|
+
.commit-item { display: flex; align-items: baseline; gap: 6px; font-size: 13px; margin-bottom: 6px; }
|
|
115
|
+
.commit-icon { width: 22px; text-align: center; color: var(--status-accent); }
|
|
116
|
+
.commit-hash {
|
|
117
|
+
display: inline-block;
|
|
118
|
+
padding: 1px 6px;
|
|
119
|
+
border-radius: 8px;
|
|
120
|
+
border: 1px solid {{border}};
|
|
121
|
+
background: {{pillBg}};
|
|
122
|
+
color: {{pillText}};
|
|
123
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
124
|
+
}
|
|
125
|
+
.commit-author { color: {{muted}}; }
|
|
126
|
+
.digest-list {
|
|
127
|
+
margin-top: 10px;
|
|
128
|
+
border: 1px solid {{border}};
|
|
129
|
+
border-radius: 10px;
|
|
130
|
+
background: {{contentBg}};
|
|
131
|
+
overflow: hidden;
|
|
132
|
+
}
|
|
133
|
+
.digest-item {
|
|
134
|
+
display: grid;
|
|
135
|
+
grid-template-columns: 90px 1fr;
|
|
136
|
+
gap: 10px;
|
|
137
|
+
padding: 8px 10px;
|
|
138
|
+
border-bottom: 1px solid {{border}};
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
}
|
|
141
|
+
.digest-item:last-child { border-bottom: 0; }
|
|
142
|
+
.digest-event { color: var(--status-accent); font-weight: 600; }
|
|
143
|
+
|
|
144
|
+
body.style-glass .card { border-radius: 18px; backdrop-filter: blur(6px); box-shadow: 0 20px 50px rgba(0,0,0,0.35); }
|
|
145
|
+
body.style-glass .head { background: linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0)); }
|
|
146
|
+
|
|
147
|
+
body.style-neon .card { border-width: 2px; box-shadow: 0 0 0 1px var(--status-accent) inset, 0 0 28px rgba(0,0,0,0.38); }
|
|
148
|
+
body.style-neon .dot { box-shadow: 0 0 0 4px {{pillBg}}, 0 0 14px var(--status-accent); }
|
|
149
|
+
body.style-neon .title { text-transform: uppercase; letter-spacing: 0.8px; }
|
|
150
|
+
|
|
151
|
+
body.style-compact .wrap { padding: 10px; }
|
|
152
|
+
body.style-compact .head { padding: 10px 12px; }
|
|
153
|
+
body.style-compact .body { padding: 10px 12px 12px; }
|
|
154
|
+
body.style-compact .title { font-size: 16px; }
|
|
155
|
+
body.style-compact .content { font-size: 13px; }
|
|
156
|
+
body.style-compact .detail-row { grid-template-columns: 90px 1fr; padding: 6px 8px; }
|
|
157
|
+
|
|
158
|
+
body.style-card .card { border-radius: 24px; box-shadow: 0 26px 54px rgba(0,0,0,0.35); }
|
|
159
|
+
body.style-card .head { border-bottom-style: dashed; }
|
|
160
|
+
body.style-card .content { border-style: solid; }
|
|
161
|
+
|
|
162
|
+
body.style-terminal .card { border-radius: 0; border-width: 2px; box-shadow: none; }
|
|
163
|
+
body.style-terminal .head,
|
|
164
|
+
body.style-terminal .content,
|
|
165
|
+
body.style-terminal .pill,
|
|
166
|
+
body.style-terminal .commit-hash,
|
|
167
|
+
body.style-terminal .detail-grid,
|
|
168
|
+
body.style-terminal .digest-list { border-radius: 0; }
|
|
169
|
+
body.style-terminal .title { font-size: 17px; letter-spacing: 0.5px; }
|
|
170
|
+
|
|
171
|
+
body.style-github .repo::before { content: '#'; margin-right: 4px; color: {{muted}}; }
|
|
172
|
+
|
|
173
|
+
{{extraCss}}
|
|
174
|
+
</style>
|
|
175
|
+
</head>
|
|
176
|
+
<body class="theme-{{themeKey}} style-{{styleVariant}}">
|
|
177
|
+
<div class="wrap">
|
|
178
|
+
<div class="card">
|
|
179
|
+
<div class="status-bar"></div>
|
|
180
|
+
<div class="head">
|
|
181
|
+
<div class="head-left">
|
|
182
|
+
<span class="dot"></span>
|
|
183
|
+
<span class="repo">{{repo}}</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="meta">{{themeTitle}}</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="body">
|
|
188
|
+
<div class="title"><span class="type-badge">{{typeLabel}}</span>{{eventTitle}}</div>
|
|
189
|
+
<div class="sub">by {{actor}}{{actionPill}}{{statusPills}}</div>
|
|
190
|
+
<div class="content">{{content}}</div>
|
|
191
|
+
{{detailBlock}}
|
|
192
|
+
{{commitBlock}}
|
|
193
|
+
{{digestBlock}}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
package/lib/commands/render.js
CHANGED
|
@@ -2,104 +2,176 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.apply = apply;
|
|
4
4
|
const modes = ['text', 'image', 'auto'];
|
|
5
|
-
const events = ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'star', 'fork', 'release', 'discussion', 'workflow_run'];
|
|
5
|
+
const events = ['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'star', 'fork', 'release', 'discussion', 'workflow_run', 'digest'];
|
|
6
6
|
function apply(ctx, config) {
|
|
7
|
-
ctx.command('githubsth.render', '
|
|
8
|
-
|
|
9
|
-
ctx.command('githubsth.render.status', '查看渲染状态', { authority: 3 })
|
|
7
|
+
ctx.command('githubsth.render', 'Render settings', { authority: 3 }).alias('gh.render');
|
|
8
|
+
ctx.command('githubsth.render.status', 'Show render status', { authority: 3 })
|
|
10
9
|
.action(async () => {
|
|
11
10
|
const status = ctx.githubsthNotifier.getRenderStatus();
|
|
12
11
|
return [
|
|
13
12
|
`mode: ${status.mode} (configured: ${status.configuredMode})`,
|
|
14
13
|
`fallback: ${status.fallback}`,
|
|
15
14
|
`theme(default): ${status.theme}`,
|
|
15
|
+
`style(default): ${status.style}`,
|
|
16
16
|
`width: ${status.width}`,
|
|
17
17
|
`timeout: ${status.timeoutMs}ms`,
|
|
18
|
+
`digest: ${status.digestEnabled ? 'on' : 'off'} (${status.digestWindowSec}s, max ${status.digestMaxItems})`,
|
|
18
19
|
`puppeteer: ${status.hasPuppeteer ? 'ready' : 'missing'}`,
|
|
19
20
|
].join('\n');
|
|
20
21
|
});
|
|
21
|
-
ctx.command('githubsth.render.mode <mode:string>', '
|
|
22
|
+
ctx.command('githubsth.render.mode <mode:string>', 'Set render mode', { authority: 3 })
|
|
22
23
|
.action(async (_, mode) => {
|
|
23
|
-
if (!mode || !modes.includes(mode))
|
|
24
|
-
return
|
|
25
|
-
}
|
|
24
|
+
if (!mode || !modes.includes(mode))
|
|
25
|
+
return `Invalid mode. Allowed: ${modes.join(', ')}`;
|
|
26
26
|
ctx.githubsthNotifier.setRenderMode(mode);
|
|
27
|
-
return
|
|
27
|
+
return `Runtime render mode set to ${mode} (config default is ${config.renderMode}).`;
|
|
28
28
|
});
|
|
29
|
-
ctx.command('githubsth.render.theme <theme:string>', '
|
|
29
|
+
ctx.command('githubsth.render.theme <theme:string>', 'Set default theme', { authority: 3 })
|
|
30
30
|
.action(async (_, theme) => {
|
|
31
31
|
const normalized = ctx.githubsthNotifier.normalizeTheme(theme);
|
|
32
|
-
if (!normalized)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
32
|
+
if (!normalized)
|
|
33
|
+
return 'Invalid theme. Run githubsth.render.themes first.';
|
|
35
34
|
config.renderTheme = normalized;
|
|
36
|
-
return
|
|
35
|
+
return `Default theme set: ${normalized}`;
|
|
36
|
+
});
|
|
37
|
+
ctx.command('githubsth.render.style <style:string>', 'Set default style', { authority: 3 })
|
|
38
|
+
.action(async (_, style) => {
|
|
39
|
+
const normalized = ctx.githubsthNotifier.normalizeStyle(style);
|
|
40
|
+
if (!normalized)
|
|
41
|
+
return 'Invalid style. Run githubsth.render.styles first.';
|
|
42
|
+
config.renderStyle = normalized;
|
|
43
|
+
return `Default style set: ${normalized}`;
|
|
37
44
|
});
|
|
38
|
-
ctx.command('githubsth.render.width <width:number>', '
|
|
45
|
+
ctx.command('githubsth.render.width <width:number>', 'Set image width', { authority: 3 })
|
|
39
46
|
.action(async (_, width) => {
|
|
40
47
|
if (!width || Number.isNaN(width))
|
|
41
|
-
return '
|
|
48
|
+
return 'Please provide a valid width number.';
|
|
42
49
|
const normalized = Math.max(480, Math.min(1600, Math.floor(width)));
|
|
43
50
|
config.renderWidth = normalized;
|
|
44
|
-
return
|
|
51
|
+
return `Image width set: ${normalized}px`;
|
|
45
52
|
});
|
|
46
|
-
ctx.command('githubsth.render.
|
|
47
|
-
.action(async () => {
|
|
48
|
-
const
|
|
49
|
-
|
|
53
|
+
ctx.command('githubsth.render.digest <enabled:string>', 'Toggle digest mode (on/off)', { authority: 3 })
|
|
54
|
+
.action(async (_, enabled) => {
|
|
55
|
+
const key = String(enabled || '').toLowerCase();
|
|
56
|
+
if (!['on', 'off', 'true', 'false', '1', '0'].includes(key))
|
|
57
|
+
return 'Usage: githubsth.render.digest on|off';
|
|
58
|
+
config.digestEnabled = ['on', 'true', '1'].includes(key);
|
|
59
|
+
return `Digest mode: ${config.digestEnabled ? 'on' : 'off'}`;
|
|
50
60
|
});
|
|
51
|
-
ctx.command('githubsth.render.
|
|
52
|
-
.action(async (
|
|
61
|
+
ctx.command('githubsth.render.digest.window <seconds:number>', 'Set digest window seconds', { authority: 3 })
|
|
62
|
+
.action(async (_, seconds) => {
|
|
63
|
+
if (!seconds || Number.isNaN(seconds))
|
|
64
|
+
return 'Please provide a valid seconds value.';
|
|
65
|
+
config.digestWindowSec = Math.max(5, Math.min(3600, Math.floor(seconds)));
|
|
66
|
+
return `Digest window set: ${config.digestWindowSec}s`;
|
|
67
|
+
});
|
|
68
|
+
ctx.command('githubsth.render.digest.max <count:number>', 'Set digest max items', { authority: 3 })
|
|
69
|
+
.action(async (_, count) => {
|
|
70
|
+
if (!count || Number.isNaN(count))
|
|
71
|
+
return 'Please provide a valid count.';
|
|
72
|
+
config.digestMaxItems = Math.max(2, Math.min(100, Math.floor(count)));
|
|
73
|
+
return `Digest max items set: ${config.digestMaxItems}`;
|
|
74
|
+
});
|
|
75
|
+
ctx.command('githubsth.render.themes', 'List themes', { authority: 3 })
|
|
76
|
+
.action(async () => `Themes:\n- ${ctx.githubsthNotifier.listThemes().join('\n- ')}`);
|
|
77
|
+
ctx.command('githubsth.render.styles', 'List styles', { authority: 3 })
|
|
78
|
+
.action(async () => `Styles:\n- ${ctx.githubsthNotifier.listStyles().join('\n- ')}`);
|
|
79
|
+
ctx.command('githubsth.render.preview [event:string] [theme:string] [style:string]', 'Preview renderer', { authority: 3 })
|
|
80
|
+
.action(async ({ session }, event, theme, style) => {
|
|
53
81
|
const selectedEvent = event && events.includes(event) ? event : 'issue_comment';
|
|
54
|
-
if (event && !events.includes(event))
|
|
55
|
-
await session?.send(
|
|
56
|
-
}
|
|
82
|
+
if (event && !events.includes(event))
|
|
83
|
+
await session?.send(`Unknown event ${event}, fallback to issue_comment.`);
|
|
57
84
|
const normalizedTheme = theme ? ctx.githubsthNotifier.normalizeTheme(theme) : null;
|
|
58
|
-
if (theme && !normalizedTheme)
|
|
59
|
-
await session?.send(
|
|
60
|
-
|
|
85
|
+
if (theme && !normalizedTheme)
|
|
86
|
+
await session?.send(`Unknown theme ${theme}, fallback to default.`);
|
|
87
|
+
const normalizedStyle = style ? ctx.githubsthNotifier.normalizeStyle(style) : null;
|
|
88
|
+
if (style && !normalizedStyle)
|
|
89
|
+
await session?.send(`Unknown style ${style}, fallback to default.`);
|
|
90
|
+
const prevTheme = config.renderTheme;
|
|
91
|
+
const prevStyle = config.renderStyle;
|
|
92
|
+
if (normalizedTheme)
|
|
93
|
+
config.renderTheme = normalizedTheme;
|
|
94
|
+
if (normalizedStyle)
|
|
95
|
+
config.renderStyle = normalizedStyle;
|
|
61
96
|
const preview = await ctx.githubsthNotifier.renderPreview(selectedEvent, normalizedTheme || undefined);
|
|
62
|
-
|
|
97
|
+
config.renderTheme = prevTheme;
|
|
98
|
+
config.renderStyle = prevStyle;
|
|
99
|
+
return preview || 'Preview failed. Check puppeteer/render settings.';
|
|
63
100
|
});
|
|
64
|
-
ctx.command('githubsth.render.repo-theme <repo:string> <theme:string>', '
|
|
101
|
+
ctx.command('githubsth.render.repo-theme <repo:string> <theme:string>', 'Set per-subscription theme', { authority: 3 })
|
|
65
102
|
.action(async ({ session }, repo, theme) => {
|
|
66
103
|
if (!repo)
|
|
67
|
-
return '
|
|
104
|
+
return 'Please provide owner/repo.';
|
|
68
105
|
if (!session?.channelId)
|
|
69
|
-
return '
|
|
106
|
+
return 'Run this command in a group/channel.';
|
|
70
107
|
const normalized = ctx.githubsthNotifier.normalizeTheme(theme);
|
|
71
108
|
if (!normalized)
|
|
72
|
-
return '
|
|
109
|
+
return 'Invalid theme. Run githubsth.render.themes.';
|
|
73
110
|
const target = await ctx.database.get('github_subscription', {
|
|
74
111
|
repo,
|
|
75
112
|
channelId: session.channelId,
|
|
76
113
|
platform: session.platform || 'unknown',
|
|
77
114
|
});
|
|
78
115
|
if (!target.length)
|
|
79
|
-
return '
|
|
116
|
+
return 'No subscription for this repo in current channel.';
|
|
80
117
|
await ctx.database.set('github_subscription', { id: target[0].id }, { renderTheme: normalized });
|
|
81
|
-
return
|
|
118
|
+
return `Per-subscription theme set: ${repo} -> ${normalized}`;
|
|
82
119
|
});
|
|
83
|
-
ctx.command('githubsth.render.repo-theme.clear <repo:string>', '
|
|
120
|
+
ctx.command('githubsth.render.repo-theme.clear <repo:string>', 'Clear per-subscription theme', { authority: 3 })
|
|
84
121
|
.action(async ({ session }, repo) => {
|
|
85
122
|
if (!repo)
|
|
86
|
-
return '
|
|
123
|
+
return 'Please provide owner/repo.';
|
|
87
124
|
if (!session?.channelId)
|
|
88
|
-
return '
|
|
125
|
+
return 'Run this command in a group/channel.';
|
|
89
126
|
const target = await ctx.database.get('github_subscription', {
|
|
90
127
|
repo,
|
|
91
128
|
channelId: session.channelId,
|
|
92
129
|
platform: session.platform || 'unknown',
|
|
93
130
|
});
|
|
94
131
|
if (!target.length)
|
|
95
|
-
return '
|
|
132
|
+
return 'No subscription for this repo in current channel.';
|
|
96
133
|
await ctx.database.set('github_subscription', { id: target[0].id }, { renderTheme: null });
|
|
97
|
-
return
|
|
134
|
+
return `Per-subscription theme cleared: ${repo}`;
|
|
135
|
+
});
|
|
136
|
+
ctx.command('githubsth.render.repo-style <repo:string> <style:string>', 'Set per-subscription style', { authority: 3 })
|
|
137
|
+
.action(async ({ session }, repo, style) => {
|
|
138
|
+
if (!repo)
|
|
139
|
+
return 'Please provide owner/repo.';
|
|
140
|
+
if (!session?.channelId)
|
|
141
|
+
return 'Run this command in a group/channel.';
|
|
142
|
+
const normalized = ctx.githubsthNotifier.normalizeStyle(style);
|
|
143
|
+
if (!normalized)
|
|
144
|
+
return 'Invalid style. Run githubsth.render.styles.';
|
|
145
|
+
const target = await ctx.database.get('github_subscription', {
|
|
146
|
+
repo,
|
|
147
|
+
channelId: session.channelId,
|
|
148
|
+
platform: session.platform || 'unknown',
|
|
149
|
+
});
|
|
150
|
+
if (!target.length)
|
|
151
|
+
return 'No subscription for this repo in current channel.';
|
|
152
|
+
await ctx.database.set('github_subscription', { id: target[0].id }, { renderStyle: normalized });
|
|
153
|
+
return `Per-subscription style set: ${repo} -> ${normalized}`;
|
|
154
|
+
});
|
|
155
|
+
ctx.command('githubsth.render.repo-style.clear <repo:string>', 'Clear per-subscription style', { authority: 3 })
|
|
156
|
+
.action(async ({ session }, repo) => {
|
|
157
|
+
if (!repo)
|
|
158
|
+
return 'Please provide owner/repo.';
|
|
159
|
+
if (!session?.channelId)
|
|
160
|
+
return 'Run this command in a group/channel.';
|
|
161
|
+
const target = await ctx.database.get('github_subscription', {
|
|
162
|
+
repo,
|
|
163
|
+
channelId: session.channelId,
|
|
164
|
+
platform: session.platform || 'unknown',
|
|
165
|
+
});
|
|
166
|
+
if (!target.length)
|
|
167
|
+
return 'No subscription for this repo in current channel.';
|
|
168
|
+
await ctx.database.set('github_subscription', { id: target[0].id }, { renderStyle: null });
|
|
169
|
+
return `Per-subscription style cleared: ${repo}`;
|
|
98
170
|
});
|
|
99
|
-
ctx.command('githubsth.render.repo-theme.list [repo:string]', '
|
|
171
|
+
ctx.command('githubsth.render.repo-theme.list [repo:string]', 'List per-subscription theme/style', { authority: 3 })
|
|
100
172
|
.action(async ({ session }, repo) => {
|
|
101
173
|
if (!session?.channelId)
|
|
102
|
-
return '
|
|
174
|
+
return 'Run this command in a group/channel.';
|
|
103
175
|
const query = {
|
|
104
176
|
channelId: session.channelId,
|
|
105
177
|
platform: session.platform || 'unknown',
|
|
@@ -108,7 +180,9 @@ function apply(ctx, config) {
|
|
|
108
180
|
query.repo = repo;
|
|
109
181
|
const subs = await ctx.database.get('github_subscription', query);
|
|
110
182
|
if (!subs.length)
|
|
111
|
-
return '
|
|
112
|
-
return subs
|
|
183
|
+
return 'No matched subscriptions.';
|
|
184
|
+
return subs
|
|
185
|
+
.map((sub) => `${sub.repo} => theme=${sub.renderTheme || '(default)'} style=${sub.renderStyle || '(default)'}`)
|
|
186
|
+
.join('\n');
|
|
113
187
|
});
|
|
114
188
|
}
|
|
@@ -10,31 +10,31 @@ function apply(ctx, config) {
|
|
|
10
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]', '
|
|
13
|
+
ctx.command('githubsth.subscribe <repo> [events:text]', 'Subscribe GitHub repository events')
|
|
14
14
|
.alias('gh.sub')
|
|
15
15
|
.usage(`
|
|
16
|
-
|
|
16
|
+
Subscribe GitHub repository notifications. If events are omitted, default events are used: ${defaultConfigEvents.join(', ')}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Examples:
|
|
19
19
|
- gh.sub koishijs/koishi
|
|
20
20
|
- gh.sub koishijs/koishi push,issues,star
|
|
21
21
|
`)
|
|
22
22
|
.action(async ({ session }, repo, eventsStr) => {
|
|
23
23
|
if (!repo)
|
|
24
|
-
return '
|
|
24
|
+
return 'Please provide repository as owner/repo.';
|
|
25
25
|
if (!repoRegex.test(repo))
|
|
26
|
-
return '
|
|
26
|
+
return 'Invalid repository format. Expected owner/repo.';
|
|
27
27
|
if (!session?.channelId)
|
|
28
|
-
return '
|
|
28
|
+
return 'Please run this command in a group/channel.';
|
|
29
29
|
const trusted = await ctx.database.get('github_trusted_repo', { repo, enabled: true });
|
|
30
30
|
if (trusted.length === 0)
|
|
31
|
-
return '
|
|
31
|
+
return 'Repository is not in trusted list. Ask admin to add it first.';
|
|
32
32
|
let events;
|
|
33
33
|
if (eventsStr) {
|
|
34
34
|
events = eventsStr.split(/[,,\s]+/).map((e) => e.trim()).filter(Boolean).map((e) => e.replace(/-/g, '_'));
|
|
35
35
|
const invalidEvents = events.filter((e) => !validEvents.includes(e) && e !== '*');
|
|
36
36
|
if (invalidEvents.length) {
|
|
37
|
-
return
|
|
37
|
+
return `Invalid events: ${invalidEvents.join(', ')}\nAllowed events: ${validEvents.join(', ')}`;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
@@ -48,7 +48,7 @@ function apply(ctx, config) {
|
|
|
48
48
|
});
|
|
49
49
|
if (existing.length > 0) {
|
|
50
50
|
await ctx.database.set('github_subscription', { id: existing[0].id }, { events });
|
|
51
|
-
return
|
|
51
|
+
return `Subscription updated: ${repo}\nEvents: ${events.join(', ')}`;
|
|
52
52
|
}
|
|
53
53
|
await ctx.database.create('github_subscription', {
|
|
54
54
|
repo,
|
|
@@ -56,40 +56,42 @@ function apply(ctx, config) {
|
|
|
56
56
|
platform: session.platform || 'unknown',
|
|
57
57
|
events,
|
|
58
58
|
});
|
|
59
|
-
return
|
|
59
|
+
return `Subscribed: ${repo}\nEvents: ${events.join(', ')}`;
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
62
|
logger.warn(error);
|
|
63
|
-
return '
|
|
63
|
+
return 'Subscribe failed. Please try again later.';
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
|
-
ctx.command('githubsth.unsubscribe <repo>', '
|
|
66
|
+
ctx.command('githubsth.unsubscribe <repo>', 'Unsubscribe GitHub repository')
|
|
67
67
|
.alias('gh.unsub')
|
|
68
68
|
.action(async ({ session }, repo) => {
|
|
69
69
|
if (!repo)
|
|
70
|
-
return '
|
|
70
|
+
return 'Please provide repository as owner/repo.';
|
|
71
71
|
if (!session?.channelId)
|
|
72
|
-
return '
|
|
72
|
+
return 'Please run this command in a group/channel.';
|
|
73
73
|
const result = await ctx.database.remove('github_subscription', {
|
|
74
74
|
repo,
|
|
75
75
|
channelId: session.channelId,
|
|
76
76
|
platform: session.platform || 'unknown',
|
|
77
77
|
});
|
|
78
78
|
if (result.matched === 0)
|
|
79
|
-
return '
|
|
80
|
-
return
|
|
79
|
+
return 'Subscription not found.';
|
|
80
|
+
return `Unsubscribed: ${repo}`;
|
|
81
81
|
});
|
|
82
|
-
ctx.command('githubsth.list', '
|
|
82
|
+
ctx.command('githubsth.list', 'List current channel subscriptions')
|
|
83
83
|
.alias('gh.list')
|
|
84
84
|
.action(async ({ session }) => {
|
|
85
85
|
if (!session?.channelId)
|
|
86
|
-
return '
|
|
86
|
+
return 'Please run this command in a group/channel.';
|
|
87
87
|
const subs = await ctx.database.get('github_subscription', {
|
|
88
88
|
channelId: session.channelId,
|
|
89
89
|
platform: session.platform || 'unknown',
|
|
90
90
|
});
|
|
91
91
|
if (subs.length === 0)
|
|
92
|
-
return '
|
|
93
|
-
return subs
|
|
92
|
+
return 'No subscriptions in this channel.';
|
|
93
|
+
return subs
|
|
94
|
+
.map((sub) => `${sub.repo} [${sub.events.join(', ')}] theme=${sub.renderTheme || '(default)'} style=${sub.renderStyle || '(default)'}`)
|
|
95
|
+
.join('\n');
|
|
94
96
|
});
|
|
95
97
|
}
|
package/lib/config.d.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { Schema } from 'koishi';
|
|
|
2
2
|
export type RenderMode = 'text' | 'image' | 'auto';
|
|
3
3
|
export type RenderFallback = 'text' | 'drop';
|
|
4
4
|
export type RenderTheme = 'github-light' | 'github-dark' | 'aurora' | 'sunset' | 'matrix' | 'compact' | 'card' | 'terminal';
|
|
5
|
+
export type RenderStyle = 'auto' | 'github' | 'glass' | 'neon' | 'compact' | 'card' | 'terminal';
|
|
5
6
|
export interface Rule {
|
|
6
7
|
repo: string;
|
|
7
8
|
channelId: string;
|
|
8
9
|
platform?: string;
|
|
9
10
|
events: string[];
|
|
10
11
|
renderTheme?: RenderTheme;
|
|
12
|
+
renderStyle?: RenderStyle;
|
|
11
13
|
}
|
|
12
14
|
export interface Config {
|
|
13
15
|
defaultOwner?: string;
|
|
@@ -23,8 +25,12 @@ export interface Config {
|
|
|
23
25
|
renderMode: RenderMode;
|
|
24
26
|
renderFallback: RenderFallback;
|
|
25
27
|
renderTheme: RenderTheme;
|
|
28
|
+
renderStyle: RenderStyle;
|
|
26
29
|
renderWidth: number;
|
|
27
30
|
renderTimeoutMs: number;
|
|
31
|
+
digestEnabled: boolean;
|
|
32
|
+
digestWindowSec: number;
|
|
33
|
+
digestMaxItems: number;
|
|
28
34
|
rules?: Rule[];
|
|
29
35
|
}
|
|
30
36
|
export declare const Config: Schema<Config>;
|