koishi-plugin-githubsth 1.0.5 → 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/commands/admin.js +17 -13
- package/lib/commands/render.js +2 -2
- package/lib/commands/subscribe.js +5 -5
- package/lib/config.js +42 -42
- package/lib/locales/zh-CN.js +20 -20
- 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
|
package/lib/commands/admin.js
CHANGED
|
@@ -5,12 +5,12 @@ function apply(ctx) {
|
|
|
5
5
|
const logger = ctx.logger('githubsth');
|
|
6
6
|
const repoRegex = /^[\w-]+\/[\w-.]+$/;
|
|
7
7
|
ctx.command('githubsth.trust', '管理信任仓库', { authority: 3 }).alias('gh.trust');
|
|
8
|
-
ctx.command('githubsth.trust.add <repo>', '添加信任仓库')
|
|
8
|
+
ctx.command('githubsth.trust.add <repo>', '添加信任仓库', { authority: 3 })
|
|
9
9
|
.action(async ({ session }, repo) => {
|
|
10
10
|
if (!repo)
|
|
11
|
-
return '
|
|
11
|
+
return '请指定仓库(owner/repo)。';
|
|
12
12
|
if (!repoRegex.test(repo))
|
|
13
|
-
return '
|
|
13
|
+
return '仓库格式错误,应为 owner/repo。';
|
|
14
14
|
try {
|
|
15
15
|
logger.debug(`Adding trusted repo: ${repo}`);
|
|
16
16
|
const existing = await ctx.database.get('github_trusted_repo', { repo });
|
|
@@ -22,43 +22,47 @@ function apply(ctx) {
|
|
|
22
22
|
addedBy: session?.userId || 'unknown',
|
|
23
23
|
addedAt: new Date(),
|
|
24
24
|
});
|
|
25
|
-
return
|
|
25
|
+
return `已添加信任仓库:${repo}`;
|
|
26
26
|
}
|
|
27
27
|
catch (e) {
|
|
28
28
|
logger.warn('Failed to add trusted repo:', e);
|
|
29
29
|
if (e.code === 'SQLITE_CONSTRAINT')
|
|
30
30
|
return '该仓库已在信任列表中。';
|
|
31
|
-
return
|
|
31
|
+
return `添加失败:${e.message}`;
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
ctx.command('githubsth.trust.remove <repo>', '移除信任仓库')
|
|
34
|
+
ctx.command('githubsth.trust.remove <repo>', '移除信任仓库', { authority: 3 })
|
|
35
35
|
.action(async (_, repo) => {
|
|
36
36
|
if (!repo)
|
|
37
|
-
return '
|
|
37
|
+
return '请指定仓库(owner/repo)。';
|
|
38
38
|
const result = await ctx.database.remove('github_trusted_repo', { repo });
|
|
39
39
|
if (result.matched === 0)
|
|
40
40
|
return '未找到该信任仓库。';
|
|
41
|
-
return
|
|
41
|
+
return `已移除信任仓库:${repo}`;
|
|
42
42
|
});
|
|
43
|
-
ctx.command('githubsth.trust.list', '列出所有信任仓库')
|
|
43
|
+
ctx.command('githubsth.trust.list', '列出所有信任仓库', { authority: 3 })
|
|
44
44
|
.action(async () => {
|
|
45
45
|
const repos = await ctx.database.get('github_trusted_repo', {});
|
|
46
46
|
if (repos.length === 0)
|
|
47
47
|
return '暂无信任仓库。';
|
|
48
48
|
return repos.map((r) => `${r.repo} [${r.enabled ? '启用' : '禁用'}]`).join('\n');
|
|
49
49
|
});
|
|
50
|
-
ctx.command('githubsth.trust.enable <repo>', '启用信任仓库')
|
|
50
|
+
ctx.command('githubsth.trust.enable <repo>', '启用信任仓库', { authority: 3 })
|
|
51
51
|
.action(async (_, repo) => {
|
|
52
|
+
if (!repo)
|
|
53
|
+
return '请指定仓库(owner/repo)。';
|
|
52
54
|
const result = await ctx.database.set('github_trusted_repo', { repo }, { enabled: true });
|
|
53
55
|
if (result.matched === 0)
|
|
54
56
|
return '未找到该信任仓库。';
|
|
55
|
-
return
|
|
57
|
+
return `已启用信任仓库:${repo}`;
|
|
56
58
|
});
|
|
57
|
-
ctx.command('githubsth.trust.disable <repo>', '禁用信任仓库')
|
|
59
|
+
ctx.command('githubsth.trust.disable <repo>', '禁用信任仓库', { authority: 3 })
|
|
58
60
|
.action(async (_, repo) => {
|
|
61
|
+
if (!repo)
|
|
62
|
+
return '请指定仓库(owner/repo)。';
|
|
59
63
|
const result = await ctx.database.set('github_trusted_repo', { repo }, { enabled: false });
|
|
60
64
|
if (result.matched === 0)
|
|
61
65
|
return '未找到该信任仓库。';
|
|
62
|
-
return
|
|
66
|
+
return `已禁用信任仓库:${repo}`;
|
|
63
67
|
});
|
|
64
68
|
}
|
package/lib/commands/render.js
CHANGED
|
@@ -188,8 +188,8 @@ function apply(ctx, config) {
|
|
|
188
188
|
return subs
|
|
189
189
|
.map((sub) => session?.text('commands.githubsth.render.messages.repo_style_item', [
|
|
190
190
|
sub.repo,
|
|
191
|
-
sub.renderTheme || '
|
|
192
|
-
sub.renderStyle || '
|
|
191
|
+
sub.renderTheme || '默认',
|
|
192
|
+
sub.renderStyle || '默认',
|
|
193
193
|
]) || `${sub.repo}`)
|
|
194
194
|
.join('\n');
|
|
195
195
|
});
|
|
@@ -13,9 +13,9 @@ function apply(ctx, config) {
|
|
|
13
13
|
ctx.command('githubsth.subscribe <repo> [events:text]', '订阅 GitHub 仓库事件')
|
|
14
14
|
.alias('gh.sub')
|
|
15
15
|
.usage(`
|
|
16
|
-
|
|
16
|
+
订阅 GitHub 仓库通知。若省略 events,将使用默认事件:${defaultConfigEvents.join(', ')}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
示例:
|
|
19
19
|
- gh.sub koishijs/koishi
|
|
20
20
|
- gh.sub koishijs/koishi push,issues,star
|
|
21
21
|
`)
|
|
@@ -31,7 +31,7 @@ Examples:
|
|
|
31
31
|
return session?.text('commands.githubsth.subscribe.messages.repo_not_trusted');
|
|
32
32
|
let events;
|
|
33
33
|
if (eventsStr) {
|
|
34
|
-
events = eventsStr.split(/[
|
|
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
37
|
return session?.text('commands.githubsth.subscribe.messages.invalid_events', [invalidEvents.join(', '), validEvents.join(', ')]);
|
|
@@ -94,8 +94,8 @@ Examples:
|
|
|
94
94
|
.map((sub) => session?.text('commands.githubsth.list.messages.item', [
|
|
95
95
|
sub.repo,
|
|
96
96
|
sub.events.join(', '),
|
|
97
|
-
sub.renderTheme || '
|
|
98
|
-
sub.renderStyle || '
|
|
97
|
+
sub.renderTheme || '默认',
|
|
98
|
+
sub.renderStyle || '默认',
|
|
99
99
|
]) || `${sub.repo} [${sub.events.join(', ')}]`)
|
|
100
100
|
.join('\n');
|
|
101
101
|
});
|
package/lib/config.js
CHANGED
|
@@ -3,56 +3,56 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Config = void 0;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const renderThemeSchema = koishi_1.Schema.union([
|
|
6
|
-
koishi_1.Schema.const('github-light').description('GitHub
|
|
7
|
-
koishi_1.Schema.const('github-dark').description('GitHub
|
|
8
|
-
koishi_1.Schema.const('aurora').description('
|
|
9
|
-
koishi_1.Schema.const('sunset').description('
|
|
10
|
-
koishi_1.Schema.const('matrix').description('
|
|
11
|
-
koishi_1.Schema.const('compact').description('
|
|
12
|
-
koishi_1.Schema.const('card').description('
|
|
13
|
-
koishi_1.Schema.const('terminal').description('
|
|
6
|
+
koishi_1.Schema.const('github-light').description('GitHub 亮色'),
|
|
7
|
+
koishi_1.Schema.const('github-dark').description('GitHub 暗色'),
|
|
8
|
+
koishi_1.Schema.const('aurora').description('极光主题'),
|
|
9
|
+
koishi_1.Schema.const('sunset').description('落日主题'),
|
|
10
|
+
koishi_1.Schema.const('matrix').description('矩阵主题'),
|
|
11
|
+
koishi_1.Schema.const('compact').description('紧凑主题(兼容)'),
|
|
12
|
+
koishi_1.Schema.const('card').description('卡片主题(兼容)'),
|
|
13
|
+
koishi_1.Schema.const('terminal').description('终端主题(兼容)'),
|
|
14
14
|
]);
|
|
15
15
|
const renderStyleSchema = koishi_1.Schema.union([
|
|
16
|
-
koishi_1.Schema.const('auto').description('
|
|
17
|
-
koishi_1.Schema.const('github').description('GitHub
|
|
18
|
-
koishi_1.Schema.const('glass').description('
|
|
19
|
-
koishi_1.Schema.const('neon').description('
|
|
20
|
-
koishi_1.Schema.const('compact').description('
|
|
21
|
-
koishi_1.Schema.const('card').description('
|
|
22
|
-
koishi_1.Schema.const('terminal').description('
|
|
16
|
+
koishi_1.Schema.const('auto').description('跟随主题默认排版'),
|
|
17
|
+
koishi_1.Schema.const('github').description('GitHub 排版'),
|
|
18
|
+
koishi_1.Schema.const('glass').description('玻璃排版'),
|
|
19
|
+
koishi_1.Schema.const('neon').description('霓虹排版'),
|
|
20
|
+
koishi_1.Schema.const('compact').description('紧凑排版'),
|
|
21
|
+
koishi_1.Schema.const('card').description('卡片排版'),
|
|
22
|
+
koishi_1.Schema.const('terminal').description('终端排版'),
|
|
23
23
|
]);
|
|
24
24
|
exports.Config = koishi_1.Schema.object({
|
|
25
|
-
defaultOwner: koishi_1.Schema.string().description('
|
|
26
|
-
defaultRepo: koishi_1.Schema.string().description('
|
|
27
|
-
debug: koishi_1.Schema.boolean().default(false).description('
|
|
28
|
-
logUnhandledEvents: koishi_1.Schema.boolean().default(false).description('
|
|
29
|
-
enableSessionFallback: koishi_1.Schema.boolean().default(true).description('
|
|
30
|
-
dedupRetentionHours: koishi_1.Schema.number().min(1).max(720).default(72).description('
|
|
31
|
-
sendRetryCount: koishi_1.Schema.number().min(0).max(10).default(2).description('
|
|
32
|
-
sendRetryBaseDelayMs: koishi_1.Schema.number().min(100).max(30000).default(800).description('
|
|
25
|
+
defaultOwner: koishi_1.Schema.string().description('默认仓库拥有者。'),
|
|
26
|
+
defaultRepo: koishi_1.Schema.string().description('默认仓库名称。'),
|
|
27
|
+
debug: koishi_1.Schema.boolean().default(false).description('启用调试日志。'),
|
|
28
|
+
logUnhandledEvents: koishi_1.Schema.boolean().default(false).description('记录未处理的 Webhook 事件。'),
|
|
29
|
+
enableSessionFallback: koishi_1.Schema.boolean().default(true).description('启用消息会话兜底解析。'),
|
|
30
|
+
dedupRetentionHours: koishi_1.Schema.number().min(1).max(720).default(72).description('幂等去重记录保留时长(小时)。'),
|
|
31
|
+
sendRetryCount: koishi_1.Schema.number().min(0).max(10).default(2).description('发送失败重试次数。'),
|
|
32
|
+
sendRetryBaseDelayMs: koishi_1.Schema.number().min(100).max(30000).default(800).description('重试基准退避间隔(毫秒)。'),
|
|
33
33
|
formatterLocale: koishi_1.Schema.union([
|
|
34
|
-
koishi_1.Schema.const('zh-CN').description('
|
|
35
|
-
koishi_1.Schema.const('en-US').description('
|
|
36
|
-
]).default('zh-CN').description('
|
|
34
|
+
koishi_1.Schema.const('zh-CN').description('中文'),
|
|
35
|
+
koishi_1.Schema.const('en-US').description('英文'),
|
|
36
|
+
]).default('zh-CN').description('通知文案语言。'),
|
|
37
37
|
renderMode: koishi_1.Schema.union([
|
|
38
|
-
koishi_1.Schema.const('auto').description('
|
|
39
|
-
koishi_1.Schema.const('image').description('
|
|
40
|
-
koishi_1.Schema.const('text').description('
|
|
41
|
-
]).default('auto').description('
|
|
38
|
+
koishi_1.Schema.const('auto').description('优先图片,失败按策略回退'),
|
|
39
|
+
koishi_1.Schema.const('image').description('仅图片'),
|
|
40
|
+
koishi_1.Schema.const('text').description('仅文本'),
|
|
41
|
+
]).default('auto').description('渲染模式。'),
|
|
42
42
|
renderFallback: koishi_1.Schema.union([
|
|
43
|
-
koishi_1.Schema.const('text').description('
|
|
44
|
-
koishi_1.Schema.const('drop').description('
|
|
45
|
-
]).default('text').description('
|
|
46
|
-
renderTheme: renderThemeSchema.default('github-dark').description('
|
|
47
|
-
renderStyle: renderStyleSchema.default('auto').description('
|
|
48
|
-
renderWidth: koishi_1.Schema.number().min(480).max(1600).default(860).description('
|
|
49
|
-
renderTimeoutMs: koishi_1.Schema.number().min(1000).max(60000).default(12000).description('
|
|
50
|
-
digestEnabled: koishi_1.Schema.boolean().default(false).description('
|
|
51
|
-
digestWindowSec: koishi_1.Schema.number().min(5).max(3600).default(60).description('Digest
|
|
52
|
-
digestMaxItems: koishi_1.Schema.number().min(2).max(100).default(12).description('
|
|
43
|
+
koishi_1.Schema.const('text').description('图片失败时回退文本'),
|
|
44
|
+
koishi_1.Schema.const('drop').description('图片失败时丢弃本条消息'),
|
|
45
|
+
]).default('text').description('图片渲染失败回退策略。'),
|
|
46
|
+
renderTheme: renderThemeSchema.default('github-dark').description('默认主题。'),
|
|
47
|
+
renderStyle: renderStyleSchema.default('auto').description('默认排版样式。'),
|
|
48
|
+
renderWidth: koishi_1.Schema.number().min(480).max(1600).default(860).description('图片宽度(像素)。'),
|
|
49
|
+
renderTimeoutMs: koishi_1.Schema.number().min(1000).max(60000).default(12000).description('图片渲染超时(毫秒)。'),
|
|
50
|
+
digestEnabled: koishi_1.Schema.boolean().default(false).description('启用 Digest 聚合推送。'),
|
|
51
|
+
digestWindowSec: koishi_1.Schema.number().min(5).max(3600).default(60).description('Digest 聚合窗口(秒)。'),
|
|
52
|
+
digestMaxItems: koishi_1.Schema.number().min(2).max(100).default(12).description('每条 Digest 最大事件数。'),
|
|
53
53
|
defaultEvents: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
54
54
|
.default(['push', 'issues', 'issue_comment', 'pull_request', 'pull_request_review', 'release', 'star', 'fork'])
|
|
55
|
-
.description('
|
|
55
|
+
.description('订阅命令默认事件列表。'),
|
|
56
56
|
rules: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
57
57
|
repo: koishi_1.Schema.string().required(),
|
|
58
58
|
channelId: koishi_1.Schema.string().required(),
|
|
@@ -60,5 +60,5 @@ exports.Config = koishi_1.Schema.object({
|
|
|
60
60
|
events: koishi_1.Schema.array(koishi_1.Schema.string()).default(['push', 'issues', 'pull_request', 'issue_comment', 'pull_request_review']),
|
|
61
61
|
renderTheme: renderThemeSchema,
|
|
62
62
|
renderStyle: renderStyleSchema,
|
|
63
|
-
})).hidden().description('
|
|
63
|
+
})).hidden().description('已弃用兼容字段,请使用数据库订阅。'),
|
|
64
64
|
});
|
package/lib/locales/zh-CN.js
CHANGED
|
@@ -3,35 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.default = {
|
|
4
4
|
commands: {
|
|
5
5
|
githubsth: {
|
|
6
|
-
description: 'GitHub
|
|
6
|
+
description: 'GitHub 订阅通知',
|
|
7
7
|
},
|
|
8
8
|
'githubsth.repo': {
|
|
9
9
|
description: '获取仓库信息',
|
|
10
10
|
messages: {
|
|
11
|
-
repo_info: '
|
|
12
|
-
error: '
|
|
13
|
-
specify_repo: '
|
|
14
|
-
not_found: '
|
|
11
|
+
repo_info: '仓库:{0}/{1}\n描述:{2}\nStars:{3}',
|
|
12
|
+
error: '获取仓库信息失败:{0}',
|
|
13
|
+
specify_repo: '请指定仓库,格式为 owner/repo。',
|
|
14
|
+
not_found: '仓库不存在或无权限访问。',
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
17
|
'githubsth.subscribe': {
|
|
18
18
|
description: '订阅 GitHub 仓库事件',
|
|
19
19
|
messages: {
|
|
20
|
-
specify_repo: '
|
|
20
|
+
specify_repo: '请指定仓库,格式为 owner/repo。',
|
|
21
21
|
invalid_repo: '仓库格式错误,应为 owner/repo。',
|
|
22
|
-
run_in_channel: '
|
|
22
|
+
run_in_channel: '请在群聊或频道中执行该命令。',
|
|
23
23
|
repo_not_trusted: '该仓库不在信任列表中,请先由管理员添加。',
|
|
24
24
|
invalid_events: '无效事件:{0}\n可选事件:{1}',
|
|
25
25
|
updated: '已更新订阅:{0}\n事件:{1}',
|
|
26
|
-
created: '
|
|
26
|
+
created: '已订阅:{0}\n事件:{1}',
|
|
27
27
|
failed: '订阅失败,请稍后重试。',
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
'githubsth.unsubscribe': {
|
|
31
31
|
description: '取消订阅 GitHub 仓库',
|
|
32
32
|
messages: {
|
|
33
|
-
specify_repo: '
|
|
34
|
-
run_in_channel: '
|
|
33
|
+
specify_repo: '请指定仓库,格式为 owner/repo。',
|
|
34
|
+
run_in_channel: '请在群聊或频道中执行该命令。',
|
|
35
35
|
not_found: '未找到该订阅。',
|
|
36
36
|
success: '已取消订阅:{0}',
|
|
37
37
|
},
|
|
@@ -39,19 +39,19 @@ exports.default = {
|
|
|
39
39
|
'githubsth.list': {
|
|
40
40
|
description: '查看当前频道订阅',
|
|
41
41
|
messages: {
|
|
42
|
-
run_in_channel: '
|
|
42
|
+
run_in_channel: '请在群聊或频道中执行该命令。',
|
|
43
43
|
empty: '当前频道没有订阅。',
|
|
44
|
-
item: '{0} [{1}]
|
|
44
|
+
item: '{0} [{1}] 主题={2} 样式={3}',
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
'githubsth.render': {
|
|
48
48
|
description: '渲染设置',
|
|
49
49
|
messages: {
|
|
50
50
|
invalid_mode: '无效模式。可选:{0}',
|
|
51
|
-
mode_set: '
|
|
52
|
-
invalid_theme: '
|
|
51
|
+
mode_set: '运行时渲染模式已设为 {0}(配置默认:{1})。',
|
|
52
|
+
invalid_theme: '无效主题,请先执行 githubsth.render.themes 查看列表。',
|
|
53
53
|
theme_set: '默认主题已设置为:{0}',
|
|
54
|
-
invalid_style: '
|
|
54
|
+
invalid_style: '无效样式,请先执行 githubsth.render.styles 查看列表。',
|
|
55
55
|
style_set: '默认样式已设置为:{0}',
|
|
56
56
|
invalid_width: '请提供有效的宽度数字。',
|
|
57
57
|
width_set: '图片宽度已设置为 {0}px',
|
|
@@ -61,13 +61,13 @@ exports.default = {
|
|
|
61
61
|
digest_window_set: 'Digest 窗口已设置为 {0}s',
|
|
62
62
|
invalid_count: '请提供有效的数量。',
|
|
63
63
|
digest_max_set: 'Digest 最大条目已设置为 {0}',
|
|
64
|
-
themes_list: '
|
|
65
|
-
styles_list: '
|
|
66
|
-
status_text: '
|
|
64
|
+
themes_list: '主题列表:\n- {0}',
|
|
65
|
+
styles_list: '样式列表:\n- {0}',
|
|
66
|
+
status_text: '当前模式:{0}(配置默认:{1})\n回退策略:{2}\n默认主题:{3}\n默认样式:{4}\n图片宽度:{5}\n渲染超时:{6}ms\nDigest:{7}(窗口 {8}s,最多 {9} 条)\nPuppeteer:{10}',
|
|
67
67
|
unknown_event: '未知事件 {0},已回退到 issue_comment。',
|
|
68
68
|
unknown_theme: '未知主题 {0},已回退到默认主题。',
|
|
69
69
|
unknown_style: '未知样式 {0},已回退到默认样式。',
|
|
70
|
-
preview_failed: '预览失败,请检查 puppeteer
|
|
70
|
+
preview_failed: '预览失败,请检查 puppeteer 或渲染配置。',
|
|
71
71
|
repo_required: '请提供仓库(owner/repo)。',
|
|
72
72
|
no_sub_in_channel: '当前频道没有该仓库订阅。',
|
|
73
73
|
repo_theme_set: '订阅主题已设置:{0} -> {1}',
|
|
@@ -75,7 +75,7 @@ exports.default = {
|
|
|
75
75
|
repo_style_set: '订阅样式已设置:{0} -> {1}',
|
|
76
76
|
repo_style_cleared: '订阅样式已清除:{0}',
|
|
77
77
|
no_matched_subs: '没有匹配的订阅。',
|
|
78
|
-
repo_style_item: '{0} =>
|
|
78
|
+
repo_style_item: '{0} => 主题={1} 样式={2}',
|
|
79
79
|
},
|
|
80
80
|
},
|
|
81
81
|
'githubsth.trust': {
|
|
@@ -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
|
-
|