koishi-plugin-githubsth 1.0.6 → 1.1.0
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/README.en.md +68 -0
- package/README.md +155 -44
- package/README.zh-CN.md +68 -0
- package/lib/services/notifier.d.ts +10 -2
- package/lib/services/notifier.js +211 -188
- package/package.json +4 -6
package/README.en.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# koishi-plugin-githubsth
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-githubsth)
|
|
4
|
+
|
|
5
|
+
A Koishi plugin that delivers GitHub event notifications with repository-level subscription management.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- GitHub event notifications: `push`, `issues`, `issue_comment`, `pull_request`, `pull_request_review`, `release`, `star`, `fork`, `discussion`, `workflow_run`
|
|
10
|
+
- Trusted repository allowlist (admin-only)
|
|
11
|
+
- Channel-level subscription persistence (via Koishi `database`)
|
|
12
|
+
- Text/image/auto rendering mode
|
|
13
|
+
- Theme and style controls (global and per-subscription)
|
|
14
|
+
- Digest aggregation mode
|
|
15
|
+
- Built-in i18n (`zh-CN`, `en-US`)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i koishi-plugin-githubsth
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- `koishi` `^4.18.0`
|
|
26
|
+
- `koishi-plugin-adapter-github` `^1.0.0`
|
|
27
|
+
- Koishi `database` service (required)
|
|
28
|
+
- `puppeteer` service (optional, required for image rendering)
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Main config fields:
|
|
33
|
+
|
|
34
|
+
- `defaultOwner`, `defaultRepo`
|
|
35
|
+
- `defaultEvents`
|
|
36
|
+
- `debug`, `logUnhandledEvents`
|
|
37
|
+
- `formatterLocale`
|
|
38
|
+
- `renderMode`, `renderFallback`
|
|
39
|
+
- `renderTheme`, `renderStyle`
|
|
40
|
+
- `renderWidth`, `renderTimeoutMs`
|
|
41
|
+
- `digestEnabled`, `digestWindowSec`, `digestMaxItems`
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
User commands:
|
|
46
|
+
|
|
47
|
+
- `githubsth.subscribe <owner/repo> [events]`
|
|
48
|
+
- `githubsth.unsubscribe <owner/repo>`
|
|
49
|
+
- `githubsth.list`
|
|
50
|
+
- `githubsth.repo [owner/repo]`
|
|
51
|
+
|
|
52
|
+
Admin commands (authority `3`):
|
|
53
|
+
|
|
54
|
+
- `githubsth.trust.add <owner/repo>`
|
|
55
|
+
- `githubsth.trust.remove <owner/repo>`
|
|
56
|
+
- `githubsth.trust.list`
|
|
57
|
+
- `githubsth.trust.enable <owner/repo>`
|
|
58
|
+
- `githubsth.trust.disable <owner/repo>`
|
|
59
|
+
- `githubsth.render.*` (render mode/theme/style/preview/digest controls)
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- `githubsth.subscribe` only works for repositories in trusted list.
|
|
64
|
+
- If `renderMode=auto` and image rendering is unavailable, fallback behavior follows `renderFallback`.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,69 +1,180 @@
|
|
|
1
1
|
# koishi-plugin-githubsth
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/koishi-plugin-githubsth)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 功能特性
|
|
8
|
-
|
|
9
|
-
- **事件通知**: 实时接收 GitHub 事件通知 (Push, Issue, PR, Release, Star, Fork, Discussion, Workflow)。
|
|
10
|
-
- **数据库订阅**: 订阅关系持久化存储在 Koishi 数据库中,支持动态管理。
|
|
11
|
-
- **信任仓库管理**: 管理员可配置允许订阅的仓库白名单,确保安全。
|
|
12
|
-
- **灵活的规则**: 支持按仓库、目标频道和事件类型进行订阅。
|
|
13
|
-
- **丰富格式**: 消息格式美观,包含关键信息。
|
|
14
|
-
- **调试模式**: 支持开启详细日志输出,方便排查问题。
|
|
5
|
+
> GitHub 订阅通知插件 — 监控仓库事件,推送到你的聊天频道 ✨
|
|
15
6
|
|
|
16
7
|
## 安装
|
|
17
8
|
|
|
18
9
|
```bash
|
|
19
10
|
npm install koishi-plugin-githubsth
|
|
11
|
+
# 或者
|
|
12
|
+
yarn add koishi-plugin-githubsth
|
|
13
|
+
# 或
|
|
14
|
+
pipx install ... # 开玩笑的,Koishi 用 npm 啦!
|
|
20
15
|
```
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
然后在 Koishi 配置中添加插件:
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
```yaml
|
|
20
|
+
plugins:
|
|
21
|
+
githubsth:
|
|
22
|
+
# 见下方配置
|
|
23
|
+
```
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
## 前置依赖
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
| 依赖 | 版本要求 | 说明 |
|
|
28
|
+
|:----|:-------:|:----|
|
|
29
|
+
| koishi | ^4.18.0 | 嗯,就是 Koishi 本体 |
|
|
30
|
+
| koishi-plugin-adapter-github | ^1.0.0 | GitHub 适配器,提供事件源 |
|
|
31
|
+
| database | — | 任意 Koishi 数据库(内置 memory 也行) |
|
|
32
|
+
| puppeteer | *可选* | 需要图片渲染时加这个 |
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
- **defaultRepo**: 默认仓库名称 (可选)。
|
|
32
|
-
- **debug**: 启用调试模式,输出详细日志 (默认 false)。
|
|
34
|
+
> 💡 如果不需要图片渲染(纯文本模式),puppeteer 可以不装。
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
## 快速开始
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
### 1. 添加可信仓库
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
只有添加到可信列表的仓库才能被订阅:
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- `githubsth.trust.enable <repo>`: 启用信任仓库。
|
|
44
|
-
- `githubsth.trust.disable <repo>`: 禁用信任仓库。
|
|
42
|
+
```
|
|
43
|
+
gh.trust add owner/repo
|
|
44
|
+
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
### 2. 订阅仓库事件
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- `githubsth.repo [name]`: 获取仓库信息。
|
|
48
|
+
```
|
|
49
|
+
gh.sub owner/repo
|
|
50
|
+
gh.sub owner/repo push,issues,star
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. 查看效果
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
当仓库有 push、issue、PR 等事件时,插件会自动推送到当前频道~
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `release`: 新版本发布
|
|
62
|
-
- `star`: 仓库标星
|
|
63
|
-
- `fork`: 仓库复刻
|
|
64
|
-
- `discussion`: 讨论区动态
|
|
65
|
-
- `workflow_run`: CI/CD 工作流状态
|
|
57
|
+
## 命令列表
|
|
58
|
+
|
|
59
|
+
### 订阅管理
|
|
66
60
|
|
|
67
|
-
|
|
61
|
+
| 命令 | 别名 | 说明 |
|
|
62
|
+
|:----|:----|:----|
|
|
63
|
+
| `githubsth.subscribe <repo> [events]` | `gh.sub` | 订阅仓库事件 |
|
|
64
|
+
| `githubsth.unsubscribe <repo>` | `gh.unsub` | 取消订阅 |
|
|
65
|
+
| `githubsth.list` | `gh.list` | 查看当前频道的订阅列表 |
|
|
66
|
+
|
|
67
|
+
### 可信仓库管理
|
|
68
|
+
|
|
69
|
+
| 命令 | 别名 | 说明 |
|
|
70
|
+
|:----|:----|:----|
|
|
71
|
+
| `githubsth.trust add <repo>` | `gh.trust add` | 添加可信仓库 |
|
|
72
|
+
| `githubsth.trust remove <repo>` | `gh.trust remove` | 移除可信仓库 |
|
|
73
|
+
| `githubsth.trust list` | `gh.trust list` | 查看可信仓库列表 |
|
|
74
|
+
|
|
75
|
+
### 渲染设置
|
|
76
|
+
|
|
77
|
+
| 命令 | 别名 | 说明 |
|
|
78
|
+
|:----|:----|:----|
|
|
79
|
+
| `githubsth.render.mode <mode>` | `gh.render mode` | 设置渲染模式(text/image/auto) |
|
|
80
|
+
| `githubsth.render.theme <theme>` | `gh.render theme` | 切换主题 |
|
|
81
|
+
| `githubsth.render.style <style>` | `gh.render style` | 切换排版风格 |
|
|
82
|
+
| `githubsth.render.preview [event]` | `gh.render preview` | 预览渲染效果 |
|
|
83
|
+
| `githubsth.render.status` | `gh.render status` | 查看当前渲染配置 |
|
|
84
|
+
|
|
85
|
+
### 管理命令
|
|
86
|
+
|
|
87
|
+
| 命令 | 说明 |
|
|
88
|
+
|:----|:----|
|
|
89
|
+
| `githubsth.admin.subscribe <channel> <repo>` | 强制给指定频道订阅 |
|
|
90
|
+
| `githubsth.admin.unsubscribe <channel> <repo>` | 强制取消订阅 |
|
|
91
|
+
| `githubsth.admin.cleanup` | 清理失效的频道订阅 |
|
|
92
|
+
|
|
93
|
+
## 配置项
|
|
94
|
+
|
|
95
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
96
|
+
|:------|:---:|:-----:|:----|
|
|
97
|
+
| `defaultEvents` | `string[]` | push, issues, pull_request 等 | 订阅时默认监听的事件 |
|
|
98
|
+
| `renderMode` | `'auto' \| 'image' \| 'text'` | `'auto'` | 渲染模式 |
|
|
99
|
+
| `renderTheme` | `string` | `'github-dark'` | 默认主题 |
|
|
100
|
+
| `renderStyle` | `string` | `'auto'` | 默认排版风格 |
|
|
101
|
+
| `renderWidth` | `number` | `860` | 图片宽度(px) |
|
|
102
|
+
| `renderTimeoutMs` | `number` | `12000` | 图片渲染超时 |
|
|
103
|
+
| `renderFallback` | `'text' \| 'drop'` | `'text'` | 图片渲染失败时的策略 |
|
|
104
|
+
| `digestEnabled` | `boolean` | `false` | 启用聚合推送 |
|
|
105
|
+
| `digestWindowSec` | `number` | `60` | 聚合窗口(秒) |
|
|
106
|
+
| `digestMaxItems` | `number` | `12` | 每条聚合最大事件数 |
|
|
107
|
+
| `formatterLocale` | `'zh-CN' \| 'en-US'` | `'zh-CN'` | 通知语言 |
|
|
108
|
+
| `debug` | `boolean` | `false` | 调试日志 |
|
|
109
|
+
| `logUnhandledEvents` | `boolean` | `false` | 记录未处理的事件 |
|
|
110
|
+
| `enableSessionFallback` | `boolean` | `true` | 启用消息会话兜底解析 |
|
|
111
|
+
| `dedupRetentionHours` | `number` | `72` | 去重记录保留时间 |
|
|
112
|
+
| `sendRetryCount` | `number` | `2` | 发送失败重试次数 |
|
|
113
|
+
| `sendRetryBaseDelayMs` | `number` | `800` | 重试基准延迟 |
|
|
114
|
+
|
|
115
|
+
## 主题与排版
|
|
116
|
+
|
|
117
|
+
### 内置主题
|
|
118
|
+
|
|
119
|
+
| 主题 | 说明 |
|
|
120
|
+
|:----|:----|
|
|
121
|
+
| `github-light` | GitHub 亮色 ✨ |
|
|
122
|
+
| `github-dark` | GitHub 暗色 🌙 |
|
|
123
|
+
| `aurora` | 极光主题 🌌 |
|
|
124
|
+
| `sunset` | 落日主题 🌅 |
|
|
125
|
+
| `matrix` | 矩阵主题 💚 |
|
|
126
|
+
| `compact` | 紧凑主题 |
|
|
127
|
+
| `card` | 卡片主题 |
|
|
128
|
+
| `terminal` | 终端主题 💻 |
|
|
129
|
+
|
|
130
|
+
### 排版风格
|
|
131
|
+
|
|
132
|
+
| 风格 | 说明 |
|
|
133
|
+
|:----|:----|
|
|
134
|
+
| `auto` | 跟随主题默认排版 |
|
|
135
|
+
| `github` | GitHub 风格 |
|
|
136
|
+
| `glass` | 玻璃风格 |
|
|
137
|
+
| `neon` | 霓虹风格 |
|
|
138
|
+
| `compact` | 紧凑风格 |
|
|
139
|
+
| `card` | 卡片风格 |
|
|
140
|
+
| `terminal` | 终端风格 |
|
|
141
|
+
|
|
142
|
+
## 支持的事件类型
|
|
143
|
+
|
|
144
|
+
| 事件 | 说明 |
|
|
145
|
+
|:----|:----|
|
|
146
|
+
| `push` | 代码推送 🚀 |
|
|
147
|
+
| `issues` | Issue 相关 |
|
|
148
|
+
| `issue_comment` | Issue 评论 💬 |
|
|
149
|
+
| `pull_request` | PR 相关 |
|
|
150
|
+
| `pull_request_review` | PR 审核 👀 |
|
|
151
|
+
| `star` | Star ⭐ |
|
|
152
|
+
| `fork` | Fork 🍴 |
|
|
153
|
+
| `release` | 版本发布 🏷️ |
|
|
154
|
+
| `discussion` | Discussion 💭 |
|
|
155
|
+
| `workflow_run` | Actions 工作流 ⚙️ |
|
|
156
|
+
|
|
157
|
+
## 常见问题
|
|
158
|
+
|
|
159
|
+
### Q: 事件收不到?
|
|
160
|
+
|
|
161
|
+
1. 检查是否添加了可信仓库:`gh.trust list`
|
|
162
|
+
2. 检查是否订阅了:`gh.list`
|
|
163
|
+
3. 确认 GitHub 适配器配置正确,Webhook 或 Pull 模式已启用
|
|
164
|
+
4. 看日志有没有报错:开启 `debug: true`
|
|
165
|
+
|
|
166
|
+
### Q: 图片渲染不出来?
|
|
167
|
+
|
|
168
|
+
1. 确认装了 puppeteer 插件
|
|
169
|
+
2. 检查 `renderTimeoutMs` 是否太短
|
|
170
|
+
3. 插件会自动回退到文本模式(如果 `renderFallback` 是 `'text'`)
|
|
171
|
+
|
|
172
|
+
### Q: 同一个事件收到了多次?
|
|
173
|
+
|
|
174
|
+
内置去重机制:
|
|
175
|
+
- 内存去重:5 秒内相同事件自动过滤
|
|
176
|
+
- 数据库去重:按 `dedupRetentionHours` 保留记录
|
|
177
|
+
|
|
178
|
+
## License
|
|
68
179
|
|
|
69
180
|
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# koishi-plugin-githubsth
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-githubsth)
|
|
4
|
+
|
|
5
|
+
一个用于 Koishi 的 GitHub 订阅通知插件,支持仓库级订阅管理与多样化渲染。
|
|
6
|
+
|
|
7
|
+
## 功能特性
|
|
8
|
+
|
|
9
|
+
- GitHub 事件通知:`push`、`issues`、`issue_comment`、`pull_request`、`pull_request_review`、`release`、`star`、`fork`、`discussion`、`workflow_run`
|
|
10
|
+
- 可信仓库白名单(管理员)
|
|
11
|
+
- 基于 Koishi `database` 的频道订阅持久化
|
|
12
|
+
- 文本/图片/自动渲染模式
|
|
13
|
+
- 全局与订阅级主题/样式控制
|
|
14
|
+
- Digest 聚合推送
|
|
15
|
+
- 内置中英文文案(`zh-CN`、`en-US`)
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i koishi-plugin-githubsth
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 依赖要求
|
|
24
|
+
|
|
25
|
+
- `koishi` `^4.18.0`
|
|
26
|
+
- `koishi-plugin-adapter-github` `^1.0.0`
|
|
27
|
+
- Koishi `database` 服务(必需)
|
|
28
|
+
- `puppeteer` 服务(可选,图片渲染需要)
|
|
29
|
+
|
|
30
|
+
## 配置项
|
|
31
|
+
|
|
32
|
+
主要配置包括:
|
|
33
|
+
|
|
34
|
+
- `defaultOwner`、`defaultRepo`
|
|
35
|
+
- `defaultEvents`
|
|
36
|
+
- `debug`、`logUnhandledEvents`
|
|
37
|
+
- `formatterLocale`
|
|
38
|
+
- `renderMode`、`renderFallback`
|
|
39
|
+
- `renderTheme`、`renderStyle`
|
|
40
|
+
- `renderWidth`、`renderTimeoutMs`
|
|
41
|
+
- `digestEnabled`、`digestWindowSec`、`digestMaxItems`
|
|
42
|
+
|
|
43
|
+
## 指令
|
|
44
|
+
|
|
45
|
+
用户指令:
|
|
46
|
+
|
|
47
|
+
- `githubsth.subscribe <owner/repo> [events]`
|
|
48
|
+
- `githubsth.unsubscribe <owner/repo>`
|
|
49
|
+
- `githubsth.list`
|
|
50
|
+
- `githubsth.repo [owner/repo]`
|
|
51
|
+
|
|
52
|
+
管理员指令(权限 `3`):
|
|
53
|
+
|
|
54
|
+
- `githubsth.trust.add <owner/repo>`
|
|
55
|
+
- `githubsth.trust.remove <owner/repo>`
|
|
56
|
+
- `githubsth.trust.list`
|
|
57
|
+
- `githubsth.trust.enable <owner/repo>`
|
|
58
|
+
- `githubsth.trust.disable <owner/repo>`
|
|
59
|
+
- `githubsth.render.*`(渲染模式/主题/样式/预览/digest 控制)
|
|
60
|
+
|
|
61
|
+
## 使用说明
|
|
62
|
+
|
|
63
|
+
- `githubsth.subscribe` 仅允许订阅已加入可信列表的仓库。
|
|
64
|
+
- 当 `renderMode=auto` 且图片渲染不可用时,将按 `renderFallback` 回退。
|
|
65
|
+
|
|
66
|
+
## 许可证
|
|
67
|
+
|
|
68
|
+
MIT
|
|
@@ -13,7 +13,12 @@ export declare class Notifier extends Service {
|
|
|
13
13
|
private dedupWriteCounter;
|
|
14
14
|
private runtimeRenderMode;
|
|
15
15
|
private readonly digestBuckets;
|
|
16
|
+
private healthCheckTimer;
|
|
16
17
|
constructor(ctx: Context, config: Config);
|
|
18
|
+
/** 启动健康检查:定期检查数据库连接和订阅状态 */
|
|
19
|
+
private startHealthCheck;
|
|
20
|
+
/** 执行健康检查 */
|
|
21
|
+
private performHealthCheck;
|
|
17
22
|
listThemes(): RenderTheme[];
|
|
18
23
|
normalizeTheme(theme?: string | null): RenderTheme | null;
|
|
19
24
|
listStyles(): RenderStyle[];
|
|
@@ -36,6 +41,11 @@ export declare class Notifier extends Service {
|
|
|
36
41
|
renderPreview(event?: string, theme?: RenderTheme | null): Promise<any>;
|
|
37
42
|
private registerListeners;
|
|
38
43
|
private handleEvent;
|
|
44
|
+
/**
|
|
45
|
+
* 将新适配器的结构化事件数据转换为 formatter 能用的扁平格式
|
|
46
|
+
* 兼容旧格式 (repository.full_name, sender.login 等)
|
|
47
|
+
*/
|
|
48
|
+
private buildFlatPayload;
|
|
39
49
|
private resolveRuleTheme;
|
|
40
50
|
private resolveRuleStyle;
|
|
41
51
|
private formatByEvent;
|
|
@@ -45,12 +55,10 @@ export declare class Notifier extends Service {
|
|
|
45
55
|
private renderTextAsImage;
|
|
46
56
|
private normalizeRenderedImage;
|
|
47
57
|
private extractRepoName;
|
|
48
|
-
private patchPayloadForEvent;
|
|
49
58
|
private buildEventDedupKey;
|
|
50
59
|
private shouldProcessEvent;
|
|
51
60
|
private cleanupDedupTable;
|
|
52
61
|
private sendMessage;
|
|
53
62
|
private sendWithRetry;
|
|
54
|
-
private sleep;
|
|
55
63
|
private getPreviewPayload;
|
|
56
64
|
}
|
package/lib/services/notifier.js
CHANGED
|
@@ -14,8 +14,37 @@ class Notifier extends koishi_1.Service {
|
|
|
14
14
|
this.dedupWriteCounter = 0;
|
|
15
15
|
this.runtimeRenderMode = null;
|
|
16
16
|
this.digestBuckets = new Map();
|
|
17
|
+
this.healthCheckTimer = null;
|
|
17
18
|
this.ctx.logger('githubsth').info('Notifier service initialized');
|
|
18
19
|
this.registerListeners();
|
|
20
|
+
this.startHealthCheck();
|
|
21
|
+
}
|
|
22
|
+
/** 启动健康检查:定期检查数据库连接和订阅状态 */
|
|
23
|
+
startHealthCheck() {
|
|
24
|
+
const interval = 5 * 60 * 1000; // 每 5 分钟检查一次
|
|
25
|
+
this.healthCheckTimer = setInterval(() => {
|
|
26
|
+
void this.performHealthCheck();
|
|
27
|
+
}, interval);
|
|
28
|
+
// 启动后马上检查一次
|
|
29
|
+
void this.performHealthCheck();
|
|
30
|
+
}
|
|
31
|
+
/** 执行健康检查 */
|
|
32
|
+
async performHealthCheck() {
|
|
33
|
+
try {
|
|
34
|
+
// 检查数据库连通性
|
|
35
|
+
const count = await this.ctx.database.get('github_subscription', {});
|
|
36
|
+
if (this.config.debug) {
|
|
37
|
+
this.ctx.logger('githubsth').debug(`Health check OK — ${count.length} active subscriptions`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.ctx.logger('githubsth').error('Health check failed — database may be unavailable:', error);
|
|
42
|
+
// 数据库不可用时,5 秒后重试一次,然后等下一轮
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
this.ctx.logger('githubsth').info('Retrying health check...');
|
|
45
|
+
void this.performHealthCheck();
|
|
46
|
+
}, 5000);
|
|
47
|
+
}
|
|
19
48
|
}
|
|
20
49
|
listThemes() {
|
|
21
50
|
return (0, render_card_1.listRenderThemes)();
|
|
@@ -68,17 +97,13 @@ class Notifier extends koishi_1.Service {
|
|
|
68
97
|
bind('github/issue', 'issues');
|
|
69
98
|
bind('github/issue-comment', 'issue_comment');
|
|
70
99
|
bind('github/pull-request', 'pull_request');
|
|
71
|
-
bind('github/pull-request-review', 'pull_request_review');
|
|
100
|
+
bind('github/pull-request-review-comment', 'pull_request_review');
|
|
72
101
|
bind('github/workflow-run', 'workflow_run');
|
|
73
102
|
bind('github/push', 'push');
|
|
74
103
|
bind('github/star', 'star');
|
|
75
104
|
bind('github/fork', 'fork');
|
|
76
105
|
bind('github/release', 'release');
|
|
77
106
|
bind('github/discussion', 'discussion');
|
|
78
|
-
bind('github/issues', 'issues');
|
|
79
|
-
bind('github/pull_request', 'pull_request');
|
|
80
|
-
bind('github/workflow_run', 'workflow_run');
|
|
81
|
-
bind('github/issue_comment', 'issue_comment');
|
|
82
107
|
if (this.config.enableSessionFallback !== false) {
|
|
83
108
|
this.ctx.on('message-created', (session) => {
|
|
84
109
|
if (session.platform !== 'github')
|
|
@@ -86,28 +111,30 @@ class Notifier extends koishi_1.Service {
|
|
|
86
111
|
const payload = session.payload || session.extra || session.data;
|
|
87
112
|
if (!payload)
|
|
88
113
|
return;
|
|
114
|
+
// 新适配器的 session fallback:payload 已经是结构化事件数据
|
|
115
|
+
// 包含 owner, repo, repoKey, actor, payload(原始), type, action
|
|
89
116
|
const realPayload = payload.payload || payload;
|
|
90
117
|
let eventType = 'unknown';
|
|
91
118
|
if (realPayload.issue && realPayload.comment)
|
|
92
119
|
eventType = 'issue_comment';
|
|
93
120
|
else if (realPayload.issue)
|
|
94
121
|
eventType = 'issues';
|
|
95
|
-
else if (realPayload.
|
|
122
|
+
else if (realPayload.pullRequest && realPayload.comment)
|
|
96
123
|
eventType = 'pull_request_review';
|
|
97
|
-
else if (realPayload.
|
|
124
|
+
else if (realPayload.pullRequest)
|
|
98
125
|
eventType = 'pull_request';
|
|
99
126
|
else if (realPayload.commits)
|
|
100
127
|
eventType = 'push';
|
|
101
|
-
else if (realPayload.starred_at !== undefined || realPayload.action === 'started')
|
|
102
|
-
eventType = 'star';
|
|
103
128
|
else if (realPayload.forkee)
|
|
104
129
|
eventType = 'fork';
|
|
105
130
|
else if (realPayload.release)
|
|
106
131
|
eventType = 'release';
|
|
107
132
|
else if (realPayload.discussion)
|
|
108
133
|
eventType = 'discussion';
|
|
109
|
-
else if (realPayload.
|
|
134
|
+
else if (realPayload.workflowRun)
|
|
110
135
|
eventType = 'workflow_run';
|
|
136
|
+
else if (realPayload.starred_at !== undefined || payload.action === 'started')
|
|
137
|
+
eventType = 'star';
|
|
111
138
|
else if (realPayload.repository && (realPayload.action === 'created' || realPayload.action === 'started'))
|
|
112
139
|
eventType = 'star';
|
|
113
140
|
if (eventType !== 'unknown')
|
|
@@ -116,36 +143,21 @@ class Notifier extends koishi_1.Service {
|
|
|
116
143
|
}
|
|
117
144
|
}
|
|
118
145
|
async handleEvent(event, payload) {
|
|
146
|
+
// 新适配器 v1.1.2 的 payload 是结构化事件数据
|
|
147
|
+
// 包含 owner, repo, repoKey, actor, action, timestamp
|
|
148
|
+
// 以及对应的 issue, comment, pullRequest, discussion 等
|
|
149
|
+
// realPayload 是原始 GitHub webhook payload(如果存在)
|
|
119
150
|
const realPayload = payload.payload || payload;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
else if (!realPayload.repository.full_name)
|
|
131
|
-
realPayload.repository.full_name = repoName || 'Unknown/Repo';
|
|
132
|
-
if (!realPayload.sender) {
|
|
133
|
-
if (realPayload.issue?.user)
|
|
134
|
-
realPayload.sender = realPayload.issue.user;
|
|
135
|
-
else if (realPayload.pull_request?.user)
|
|
136
|
-
realPayload.sender = realPayload.pull_request.user;
|
|
137
|
-
else if (realPayload.discussion?.user)
|
|
138
|
-
realPayload.sender = realPayload.discussion.user;
|
|
139
|
-
else if (realPayload.pusher)
|
|
140
|
-
realPayload.sender = { login: realPayload.pusher.name || 'Pusher' };
|
|
141
|
-
else
|
|
142
|
-
realPayload.sender = { login: 'GitHub' };
|
|
143
|
-
}
|
|
144
|
-
if (!(await this.shouldProcessEvent(event, payload, realPayload, repoName)))
|
|
145
|
-
return;
|
|
146
|
-
this.patchPayloadForEvent(event, realPayload, repoName || 'Unknown/Repo');
|
|
147
|
-
repoName = repoName || realPayload.repository?.full_name;
|
|
148
|
-
if (!repoName)
|
|
151
|
+
// 从结构化数据中提取 repo 信息
|
|
152
|
+
const repoName = payload.repoKey
|
|
153
|
+
|| (payload.owner && payload.repo ? `${payload.owner}/${payload.repo}` : null)
|
|
154
|
+
|| this.extractRepoName(payload, realPayload, event)
|
|
155
|
+
|| realPayload.repository?.full_name
|
|
156
|
+
|| 'Unknown/Repo';
|
|
157
|
+
// 确保 formatter 能读取到数据
|
|
158
|
+
// 构造一个兼容新旧格式的扁平 payload 给 formatter 用
|
|
159
|
+
const flatPayload = this.buildFlatPayload(payload, realPayload, event, repoName);
|
|
160
|
+
if (!(await this.shouldProcessEvent(event, payload, flatPayload, repoName)))
|
|
149
161
|
return;
|
|
150
162
|
const repoNames = [repoName];
|
|
151
163
|
if (repoName !== repoName.toLowerCase())
|
|
@@ -160,22 +172,76 @@ class Notifier extends koishi_1.Service {
|
|
|
160
172
|
if (!matchedRules.length)
|
|
161
173
|
return;
|
|
162
174
|
const uniqueRules = Array.from(new Map(matchedRules.map((rule) => [`${repoName}|${rule.channelId}`, rule])).values());
|
|
163
|
-
const textMessage = this.formatByEvent(event,
|
|
175
|
+
const textMessage = this.formatByEvent(event, flatPayload);
|
|
164
176
|
if (!textMessage)
|
|
165
177
|
return;
|
|
166
178
|
for (const rule of uniqueRules) {
|
|
167
179
|
const theme = this.resolveRuleTheme(rule);
|
|
168
180
|
const style = this.resolveRuleStyle(rule);
|
|
169
181
|
if (this.config.digestEnabled) {
|
|
170
|
-
this.enqueueDigest({ event, repo: repoName, text: textMessage, payload:
|
|
182
|
+
this.enqueueDigest({ event, repo: repoName, text: textMessage, payload: flatPayload, theme, style, rule });
|
|
171
183
|
continue;
|
|
172
184
|
}
|
|
173
|
-
const outbound = await this.prepareOutboundMessage(textMessage, event,
|
|
185
|
+
const outbound = await this.prepareOutboundMessage(textMessage, event, flatPayload, theme, style);
|
|
174
186
|
if (!outbound)
|
|
175
187
|
continue;
|
|
176
188
|
await this.sendMessage(rule, outbound);
|
|
177
189
|
}
|
|
178
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* 将新适配器的结构化事件数据转换为 formatter 能用的扁平格式
|
|
193
|
+
* 兼容旧格式 (repository.full_name, sender.login 等)
|
|
194
|
+
*/
|
|
195
|
+
buildFlatPayload(payload, realPayload, event, repoName) {
|
|
196
|
+
// 如果已经是旧格式(有 repository.full_name),直接返回
|
|
197
|
+
if (realPayload.repository?.full_name)
|
|
198
|
+
return realPayload;
|
|
199
|
+
const flat = { ...realPayload };
|
|
200
|
+
const actor = payload.actor || realPayload.actor || {};
|
|
201
|
+
const actorLogin = actor.login || actor.name || 'GitHub';
|
|
202
|
+
// 构造 repository 对象
|
|
203
|
+
if (!flat.repository) {
|
|
204
|
+
flat.repository = {
|
|
205
|
+
full_name: repoName,
|
|
206
|
+
stargazers_count: realPayload.repository?.stargazers_count
|
|
207
|
+
|| payload.repository?.stargazers_count
|
|
208
|
+
|| 0,
|
|
209
|
+
html_url: `https://github.com/${repoName}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// 构造 sender
|
|
213
|
+
if (!flat.sender) {
|
|
214
|
+
flat.sender = {
|
|
215
|
+
login: actorLogin,
|
|
216
|
+
id: actor.id || 0,
|
|
217
|
+
avatar_url: actor.avatar_url || '',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// 统一字段名:新适配器用 camelCase,旧格式用 snake_case
|
|
221
|
+
// issue -> 已有,不变
|
|
222
|
+
// pullRequest -> 需要映射到 pull_request
|
|
223
|
+
if (payload.pullRequest && !flat.pull_request) {
|
|
224
|
+
flat.pull_request = payload.pullRequest;
|
|
225
|
+
}
|
|
226
|
+
if (payload.workflowRun && !flat.workflow_run) {
|
|
227
|
+
flat.workflow_run = payload.workflowRun;
|
|
228
|
+
}
|
|
229
|
+
if (payload.workflow && !flat.workflow) {
|
|
230
|
+
flat.workflow = payload.workflow;
|
|
231
|
+
}
|
|
232
|
+
if (payload.headCommit && !flat.head_commit) {
|
|
233
|
+
flat.head_commit = payload.headCommit;
|
|
234
|
+
}
|
|
235
|
+
// action
|
|
236
|
+
if (payload.action && !flat.action) {
|
|
237
|
+
flat.action = payload.action;
|
|
238
|
+
}
|
|
239
|
+
// 补全其他字段
|
|
240
|
+
if (!flat.pusher && payload.actor) {
|
|
241
|
+
flat.pusher = { name: actorLogin };
|
|
242
|
+
}
|
|
243
|
+
return flat;
|
|
244
|
+
}
|
|
179
245
|
resolveRuleTheme(rule) {
|
|
180
246
|
return this.normalizeTheme(rule.renderTheme) || this.normalizeTheme(this.config.renderTheme) || 'github-dark';
|
|
181
247
|
}
|
|
@@ -308,6 +374,8 @@ class Notifier extends koishi_1.Service {
|
|
|
308
374
|
}
|
|
309
375
|
if (!repoName && realPayload.pull_request?.base?.repo?.full_name)
|
|
310
376
|
repoName = realPayload.pull_request.base.repo.full_name;
|
|
377
|
+
if (!repoName && realPayload.pullRequest?.base?.repo?.full_name)
|
|
378
|
+
repoName = realPayload.pullRequest.base.repo.full_name;
|
|
311
379
|
if (!repoName && typeof payload.repoKey === 'string' && payload.repoKey.includes('/'))
|
|
312
380
|
repoName = payload.repoKey;
|
|
313
381
|
if (!repoName && typeof payload.owner === 'string' && typeof payload.repo === 'string')
|
|
@@ -321,109 +389,18 @@ class Notifier extends koishi_1.Service {
|
|
|
321
389
|
}
|
|
322
390
|
return repoName;
|
|
323
391
|
}
|
|
324
|
-
patchPayloadForEvent(event, payload, repoName) {
|
|
325
|
-
const defaultUser = { login: 'GitHub', id: 0, avatar_url: '' };
|
|
326
|
-
if (!payload.sender)
|
|
327
|
-
payload.sender = defaultUser;
|
|
328
|
-
const defaultRepo = { full_name: repoName, stargazers_count: 0, html_url: `https://github.com/${repoName}` };
|
|
329
|
-
if (!payload.repository)
|
|
330
|
-
payload.repository = defaultRepo;
|
|
331
|
-
switch (event) {
|
|
332
|
-
case 'push':
|
|
333
|
-
if (!payload.pusher)
|
|
334
|
-
payload.pusher = { name: payload.sender.login };
|
|
335
|
-
if (!payload.commits)
|
|
336
|
-
payload.commits = [];
|
|
337
|
-
if (!payload.ref)
|
|
338
|
-
payload.ref = 'refs/heads/unknown';
|
|
339
|
-
if (!payload.compare)
|
|
340
|
-
payload.compare = '';
|
|
341
|
-
for (const commit of payload.commits) {
|
|
342
|
-
if (!commit.author)
|
|
343
|
-
commit.author = { name: 'Unknown' };
|
|
344
|
-
if (!commit.id)
|
|
345
|
-
commit.id = '0000000';
|
|
346
|
-
if (!commit.message)
|
|
347
|
-
commit.message = 'No message';
|
|
348
|
-
}
|
|
349
|
-
break;
|
|
350
|
-
case 'issues':
|
|
351
|
-
if (!payload.action)
|
|
352
|
-
payload.action = 'updated';
|
|
353
|
-
if (!payload.issue)
|
|
354
|
-
payload.issue = { number: 0, title: 'Unknown Issue', html_url: '', user: payload.sender };
|
|
355
|
-
if (!payload.issue.user)
|
|
356
|
-
payload.issue.user = payload.sender;
|
|
357
|
-
break;
|
|
358
|
-
case 'pull_request':
|
|
359
|
-
if (!payload.action)
|
|
360
|
-
payload.action = 'updated';
|
|
361
|
-
if (!payload.pull_request)
|
|
362
|
-
payload.pull_request = { number: 0, title: 'Unknown PR', state: 'unknown', html_url: '', user: payload.sender };
|
|
363
|
-
if (!payload.pull_request.user)
|
|
364
|
-
payload.pull_request.user = payload.sender;
|
|
365
|
-
break;
|
|
366
|
-
case 'star':
|
|
367
|
-
if (!payload.action || payload.action === 'started')
|
|
368
|
-
payload.action = 'created';
|
|
369
|
-
if (payload.repository?.stargazers_count === undefined)
|
|
370
|
-
payload.repository.stargazers_count = '?';
|
|
371
|
-
break;
|
|
372
|
-
case 'fork':
|
|
373
|
-
if (!payload.forkee)
|
|
374
|
-
payload.forkee = { full_name: 'unknown/fork' };
|
|
375
|
-
break;
|
|
376
|
-
case 'release':
|
|
377
|
-
if (!payload.action)
|
|
378
|
-
payload.action = 'published';
|
|
379
|
-
if (!payload.release)
|
|
380
|
-
payload.release = { tag_name: 'unknown', name: 'Unknown Release', html_url: '' };
|
|
381
|
-
break;
|
|
382
|
-
case 'discussion':
|
|
383
|
-
if (!payload.action)
|
|
384
|
-
payload.action = 'updated';
|
|
385
|
-
if (!payload.discussion)
|
|
386
|
-
payload.discussion = { number: 0, title: 'Unknown Discussion', html_url: '', user: payload.sender };
|
|
387
|
-
if (!payload.discussion.user)
|
|
388
|
-
payload.discussion.user = payload.sender;
|
|
389
|
-
break;
|
|
390
|
-
case 'workflow_run':
|
|
391
|
-
if (!payload.action)
|
|
392
|
-
payload.action = 'completed';
|
|
393
|
-
if (!payload.workflow_run)
|
|
394
|
-
payload.workflow_run = { conclusion: 'unknown', name: 'Unknown Workflow', head_branch: 'unknown', html_url: '' };
|
|
395
|
-
break;
|
|
396
|
-
case 'issue_comment':
|
|
397
|
-
if (!payload.action)
|
|
398
|
-
payload.action = 'created';
|
|
399
|
-
if (!payload.issue)
|
|
400
|
-
payload.issue = { number: 0, title: 'Unknown Issue', html_url: '', user: payload.sender };
|
|
401
|
-
if (!payload.comment)
|
|
402
|
-
payload.comment = { body: '', html_url: '' };
|
|
403
|
-
if (!payload.issue.user)
|
|
404
|
-
payload.issue.user = payload.sender;
|
|
405
|
-
break;
|
|
406
|
-
case 'pull_request_review':
|
|
407
|
-
if (!payload.action)
|
|
408
|
-
payload.action = 'submitted';
|
|
409
|
-
if (!payload.pull_request)
|
|
410
|
-
payload.pull_request = { number: 0, title: 'Unknown PR', html_url: '', user: payload.sender };
|
|
411
|
-
if (!payload.review)
|
|
412
|
-
payload.review = { state: 'unknown', html_url: '' };
|
|
413
|
-
if (!payload.pull_request.user)
|
|
414
|
-
payload.pull_request.user = payload.sender;
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
392
|
buildEventDedupKey(event, payload, realPayload, repoName) {
|
|
419
393
|
const keyRepo = repoName || payload.repoKey || `${payload.owner || ''}/${payload.repo || ''}` || realPayload.repository?.full_name || 'unknown/repo';
|
|
420
394
|
const action = realPayload.action || payload.action || '';
|
|
421
|
-
const commentId = realPayload.comment?.id || '';
|
|
395
|
+
const commentId = realPayload.comment?.id || payload.comment?.id || '';
|
|
422
396
|
const issueId = realPayload.issue?.id || realPayload.issue?.number || '';
|
|
423
|
-
const prId = realPayload.pull_request?.id || realPayload.pull_request?.number
|
|
397
|
+
const prId = realPayload.pull_request?.id || realPayload.pull_request?.number
|
|
398
|
+
|| realPayload.pullRequest?.id || realPayload.pullRequest?.number || '';
|
|
424
399
|
const releaseId = realPayload.release?.id || realPayload.release?.tag_name || '';
|
|
425
|
-
const workflowId = realPayload.workflow_run?.id || realPayload.workflow_run?.run_id
|
|
426
|
-
|
|
400
|
+
const workflowId = realPayload.workflow_run?.id || realPayload.workflow_run?.run_id
|
|
401
|
+
|| payload.workflowRun?.id || '';
|
|
402
|
+
const headCommit = realPayload.head_commit?.id || realPayload.after || payload.after
|
|
403
|
+
|| realPayload.commits?.[0]?.id || '';
|
|
427
404
|
const explicitId = payload.id || realPayload.id || payload.timestamp || '';
|
|
428
405
|
return [event, keyRepo, action, commentId, issueId, prId, releaseId, workflowId, headCommit, explicitId].join('|');
|
|
429
406
|
}
|
|
@@ -455,6 +432,7 @@ class Notifier extends koishi_1.Service {
|
|
|
455
432
|
catch (error) {
|
|
456
433
|
if (error?.code === 'SQLITE_CONSTRAINT')
|
|
457
434
|
return false;
|
|
435
|
+
this.ctx.logger('githubsth').warn('Dedup write error:', error?.message || error);
|
|
458
436
|
}
|
|
459
437
|
return true;
|
|
460
438
|
}
|
|
@@ -463,7 +441,9 @@ class Notifier extends koishi_1.Service {
|
|
|
463
441
|
try {
|
|
464
442
|
await this.ctx.database.remove('github_event_dedup', { createdAt: { $lt: cutoff } });
|
|
465
443
|
}
|
|
466
|
-
catch {
|
|
444
|
+
catch (error) {
|
|
445
|
+
this.ctx.logger('githubsth').warn('Dedup cleanup error:', error);
|
|
446
|
+
}
|
|
467
447
|
}
|
|
468
448
|
async sendMessage(rule, outbound) {
|
|
469
449
|
const bots = this.ctx.bots.filter((bot) => !rule.platform || bot.platform === rule.platform);
|
|
@@ -476,14 +456,19 @@ class Notifier extends koishi_1.Service {
|
|
|
476
456
|
this.ctx.logger('notifier').info(`Sent message to ${rule.channelId} via ${bot.platform}:${bot.selfId}`);
|
|
477
457
|
return;
|
|
478
458
|
}
|
|
479
|
-
catch {
|
|
459
|
+
catch (sendError) {
|
|
480
460
|
if (outbound.isImage && this.config.renderFallback === 'text') {
|
|
481
461
|
try {
|
|
482
462
|
await this.sendWithRetry(bot, rule.channelId, outbound.text);
|
|
483
463
|
this.ctx.logger('notifier').warn(`Image failed on ${bot.platform}:${bot.selfId}, fallback to text succeeded.`);
|
|
484
464
|
return;
|
|
485
465
|
}
|
|
486
|
-
catch {
|
|
466
|
+
catch (fallbackError) {
|
|
467
|
+
this.ctx.logger('notifier').error(`Image + text fallback both failed on ${bot.platform}:${bot.selfId}:`, fallbackError);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
this.ctx.logger('notifier').warn(`Send failed on ${bot.platform}:${bot.selfId}:`, sendError);
|
|
487
472
|
}
|
|
488
473
|
}
|
|
489
474
|
}
|
|
@@ -500,56 +485,94 @@ class Notifier extends koishi_1.Service {
|
|
|
500
485
|
}
|
|
501
486
|
catch (error) {
|
|
502
487
|
lastError = error;
|
|
503
|
-
if (attempt
|
|
504
|
-
|
|
505
|
-
|
|
488
|
+
if (attempt < retryCount) {
|
|
489
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
490
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
491
|
+
}
|
|
506
492
|
}
|
|
507
493
|
}
|
|
508
494
|
throw lastError;
|
|
509
495
|
}
|
|
510
|
-
sleep(ms) {
|
|
511
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
512
|
-
}
|
|
513
496
|
getPreviewPayload(event) {
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
497
|
+
const mockRepo = 'owner/repo';
|
|
498
|
+
switch (event) {
|
|
499
|
+
case 'push':
|
|
500
|
+
return {
|
|
501
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
502
|
+
sender: { login: 'octocat' },
|
|
503
|
+
ref: 'refs/heads/main',
|
|
504
|
+
commits: [
|
|
505
|
+
{ id: 'abc123', message: 'feat: add new feature', author: { name: 'octocat' } },
|
|
506
|
+
],
|
|
507
|
+
compare: `https://github.com/${mockRepo}/compare/old..new`,
|
|
508
|
+
pusher: { name: 'octocat' },
|
|
509
|
+
};
|
|
510
|
+
case 'issues':
|
|
511
|
+
return {
|
|
512
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
513
|
+
sender: { login: 'octocat' },
|
|
514
|
+
action: 'opened',
|
|
515
|
+
issue: { number: 1, title: 'Example Issue', html_url: `https://github.com/${mockRepo}/issues/1`, user: { login: 'octocat' } },
|
|
516
|
+
};
|
|
517
|
+
case 'issue_comment':
|
|
518
|
+
return {
|
|
519
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
520
|
+
sender: { login: 'octocat' },
|
|
521
|
+
action: 'created',
|
|
522
|
+
issue: { number: 1, title: 'Example Issue', html_url: `https://github.com/${mockRepo}/issues/1`, user: { login: 'octocat' } },
|
|
523
|
+
comment: { body: 'This is a sample comment', html_url: `https://github.com/${mockRepo}/issues/1#issuecomment-1` },
|
|
524
|
+
};
|
|
525
|
+
case 'pull_request':
|
|
526
|
+
return {
|
|
527
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
528
|
+
sender: { login: 'octocat' },
|
|
529
|
+
action: 'opened',
|
|
530
|
+
pull_request: { number: 1, title: 'Example PR', html_url: `https://github.com/${mockRepo}/pull/1`, user: { login: 'octocat' } },
|
|
531
|
+
};
|
|
532
|
+
case 'pull_request_review':
|
|
533
|
+
return {
|
|
534
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
535
|
+
sender: { login: 'octocat' },
|
|
536
|
+
action: 'submitted',
|
|
537
|
+
pull_request: { number: 1, title: 'Example PR', html_url: `https://github.com/${mockRepo}/pull/1`, user: { login: 'octocat' } },
|
|
538
|
+
review: { state: 'approved', html_url: `https://github.com/${mockRepo}/pull/1#pullrequestreview-1` },
|
|
539
|
+
};
|
|
540
|
+
case 'star':
|
|
541
|
+
return {
|
|
542
|
+
repository: { full_name: mockRepo, stargazers_count: 42, html_url: `https://github.com/${mockRepo}` },
|
|
543
|
+
sender: { login: 'octocat' },
|
|
544
|
+
action: 'created',
|
|
545
|
+
};
|
|
546
|
+
case 'fork':
|
|
547
|
+
return {
|
|
548
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
549
|
+
sender: { login: 'octocat' },
|
|
550
|
+
forkee: { full_name: 'octocat/repo', html_url: `https://github.com/octocat/repo` },
|
|
551
|
+
};
|
|
552
|
+
case 'release':
|
|
553
|
+
return {
|
|
554
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
555
|
+
sender: { login: 'octocat' },
|
|
556
|
+
action: 'published',
|
|
557
|
+
release: { tag_name: 'v1.0.0', name: 'Initial Release', html_url: `https://github.com/${mockRepo}/releases/v1.0.0` },
|
|
558
|
+
};
|
|
559
|
+
case 'discussion':
|
|
560
|
+
return {
|
|
561
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
562
|
+
sender: { login: 'octocat' },
|
|
563
|
+
action: 'created',
|
|
564
|
+
discussion: { number: 1, title: 'Example Discussion', html_url: `https://github.com/${mockRepo}/discussions/1`, user: { login: 'octocat' } },
|
|
565
|
+
};
|
|
566
|
+
case 'workflow_run':
|
|
567
|
+
return {
|
|
568
|
+
repository: { full_name: mockRepo, html_url: `https://github.com/${mockRepo}` },
|
|
569
|
+
sender: { login: 'octocat' },
|
|
570
|
+
action: 'completed',
|
|
571
|
+
workflow_run: { name: 'CI', conclusion: 'success', head_branch: 'main', html_url: `https://github.com/${mockRepo}/actions/runs/1` },
|
|
572
|
+
};
|
|
573
|
+
default:
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
553
576
|
}
|
|
554
577
|
}
|
|
555
578
|
exports.Notifier = Notifier;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-githubsth",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Koishi plugin for GitHub subscription notifications, trusted repositories, and rich rendering.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"koishi": {
|
|
36
36
|
"description": {
|
|
37
|
-
"en": "
|
|
38
|
-
"zh": "
|
|
37
|
+
"en": "GitHub subscription notifications with trusted repository controls and render customization.",
|
|
38
|
+
"zh": "支持可信仓库控制与渲染自定义的 GitHub 订阅通知插件。"
|
|
39
39
|
},
|
|
40
40
|
"service": {
|
|
41
41
|
"required": [
|
|
@@ -47,5 +47,3 @@
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|