@dsh-plugin/dsh-synapse 0.0.0-stage → 0.1.0-dev.41044818627
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/LICENSE +661 -0
- package/NOTICE.md +18 -0
- package/README.md +136 -2
- package/cordis.patch.yml +3 -0
- package/lib/bot.cjs +8314 -0
- package/lib/bot.cjs.map +7 -0
- package/lib/build.json +1 -0
- package/lib/client.js +46 -0
- package/lib/host/agent.d.ts +21 -0
- package/lib/host/agent.js +151 -0
- package/lib/host/agent.js.map +1 -0
- package/lib/host/bot.d.ts +17 -0
- package/lib/host/bot.js +158 -0
- package/lib/host/bot.js.map +1 -0
- package/lib/host/client.d.ts +2 -0
- package/lib/host/client.js +19 -0
- package/lib/host/client.js.map +1 -0
- package/lib/host/config.d.ts +24 -0
- package/lib/host/config.js +192 -0
- package/lib/host/config.js.map +1 -0
- package/lib/host/files.d.ts +29 -0
- package/lib/host/files.js +188 -0
- package/lib/host/files.js.map +1 -0
- package/lib/host/gateway.d.ts +33 -0
- package/lib/host/gateway.js +237 -0
- package/lib/host/gateway.js.map +1 -0
- package/lib/host/github.d.ts +47 -0
- package/lib/host/github.js +219 -0
- package/lib/host/github.js.map +1 -0
- package/lib/host/http.d.ts +4 -0
- package/lib/host/http.js +41 -0
- package/lib/host/http.js.map +1 -0
- package/lib/host/index.d.ts +32 -0
- package/lib/host/index.js +192 -0
- package/lib/host/index.js.map +1 -0
- package/lib/host/inference-worker.d.ts +1 -0
- package/lib/host/inference-worker.js +72 -0
- package/lib/host/inference-worker.js.map +1 -0
- package/lib/host/mcp.d.ts +138 -0
- package/lib/host/mcp.js +104 -0
- package/lib/host/mcp.js.map +1 -0
- package/lib/host/memory.d.ts +26 -0
- package/lib/host/memory.js +155 -0
- package/lib/host/memory.js.map +1 -0
- package/lib/host/panel.d.ts +1 -0
- package/lib/host/panel.html +174 -0
- package/lib/host/panel.js +388 -0
- package/lib/host/panel.js.map +1 -0
- package/lib/host/runtime.d.ts +21 -0
- package/lib/host/runtime.js +145 -0
- package/lib/host/runtime.js.map +1 -0
- package/lib/host/types.d.ts +112 -0
- package/lib/host/types.js +2 -0
- package/lib/host/types.js.map +1 -0
- package/package.json +94 -4
- package/scripts/build.mjs +90 -0
- package/scripts/check.mjs +13 -0
- package/scripts/dump-npm-latest.mjs +49 -0
- package/scripts/model-smoke.mjs +25 -0
- package/src/agent.ts +203 -0
- package/src/bot.ts +173 -0
- package/src/client.ts +20 -0
- package/src/config.ts +217 -0
- package/src/files.ts +206 -0
- package/src/gateway.ts +276 -0
- package/src/github.ts +263 -0
- package/src/http.ts +41 -0
- package/src/index.ts +227 -0
- package/src/inference-worker.ts +89 -0
- package/src/mcp.ts +112 -0
- package/src/memory.ts +178 -0
- package/src/panel.html +174 -0
- package/src/panel.ts +440 -0
- package/src/runtime.ts +164 -0
- package/src/types.ts +106 -0
- package/test/core.test.mjs +279 -0
- package/test/fixtures/mcp-server.mjs +14 -0
- package/test/integration.test.mjs +253 -0
- package/test/panel.test.mjs +73 -0
- package/tsconfig.json +18 -0
- package/vendor/gugle-chat-bot/LICENSE +661 -0
- package/vendor/gugle-chat-bot/package.json +57 -0
- package/vendor/gugle-chat-bot/src/allow-rule.yml +9345 -0
- package/vendor/gugle-chat-bot/src/api.json +4285 -0
- package/vendor/gugle-chat-bot/src/command/index.ts +141 -0
- package/vendor/gugle-chat-bot/src/config/features.ts +548 -0
- package/vendor/gugle-chat-bot/src/config/index.ts +184 -0
- package/vendor/gugle-chat-bot/src/config/manager.ts +357 -0
- package/vendor/gugle-chat-bot/src/constants/index.ts +13 -0
- package/vendor/gugle-chat-bot/src/custom.ts +548 -0
- package/vendor/gugle-chat-bot/src/event/index.ts +118 -0
- package/vendor/gugle-chat-bot/src/features/admin/index.ts +414 -0
- package/vendor/gugle-chat-bot/src/features/bili/image/impl/video.ts +39 -0
- package/vendor/gugle-chat-bot/src/features/bili/image/index.ts +37 -0
- package/vendor/gugle-chat-bot/src/features/bili/index.ts +65 -0
- package/vendor/gugle-chat-bot/src/features/bili/template/style/video.css +97 -0
- package/vendor/gugle-chat-bot/src/features/bili/template/video.html +57 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/index.ts +1128 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/markdown.ts +105 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/slash.ts +355 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/source.ts +96 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/template/message.html +14 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/template/style/message.css +144 -0
- package/vendor/gugle-chat-bot/src/features/github/api.ts +93 -0
- package/vendor/gugle-chat-bot/src/features/github/binding.ts +161 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/issue.ts +132 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/pr.ts +144 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/release.ts +160 -0
- package/vendor/gugle-chat-bot/src/features/github/image/index.ts +310 -0
- package/vendor/gugle-chat-bot/src/features/github/index.ts +302 -0
- package/vendor/gugle-chat-bot/src/features/github/template/issue.html +118 -0
- package/vendor/gugle-chat-bot/src/features/github/template/pull_request.html +125 -0
- package/vendor/gugle-chat-bot/src/features/github/template/release.html +53 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/issue.css +468 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/pull_request.css +469 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/release.css +412 -0
- package/vendor/gugle-chat-bot/src/features/hermes/client.ts +184 -0
- package/vendor/gugle-chat-bot/src/features/hermes/config.ts +210 -0
- package/vendor/gugle-chat-bot/src/features/hermes/context.ts +67 -0
- package/vendor/gugle-chat-bot/src/features/hermes/history.ts +86 -0
- package/vendor/gugle-chat-bot/src/features/hermes/index.ts +1533 -0
- package/vendor/gugle-chat-bot/src/features/hermes/message.ts +319 -0
- package/vendor/gugle-chat-bot/src/features/hermes/reaction.ts +181 -0
- package/vendor/gugle-chat-bot/src/features/hermes/render.ts +118 -0
- package/vendor/gugle-chat-bot/src/features/hermes/reply-units.ts +331 -0
- package/vendor/gugle-chat-bot/src/features/hermes/skills.ts +406 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/approval.html +30 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/progress.html +21 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/style/approval.css +81 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/style/progress.css +120 -0
- package/vendor/gugle-chat-bot/src/features/hermes/trigger.ts +86 -0
- package/vendor/gugle-chat-bot/src/features/hermes/types.ts +327 -0
- package/vendor/gugle-chat-bot/src/features/management/index.ts +156 -0
- package/vendor/gugle-chat-bot/src/features/minecraft/index.ts +127 -0
- package/vendor/gugle-chat-bot/src/features/modrinth/index.ts +71 -0
- package/vendor/gugle-chat-bot/src/features/parentheses/index.ts +117 -0
- package/vendor/gugle-chat-bot/src/features/peak-valley-timer/index.ts +452 -0
- package/vendor/gugle-chat-bot/src/features/poke/index.ts +24 -0
- package/vendor/gugle-chat-bot/src/features/version-tracker/index.ts +78 -0
- package/vendor/gugle-chat-bot/src/features/welcome/index.ts +58 -0
- package/vendor/gugle-chat-bot/src/github-ip.json +5814 -0
- package/vendor/gugle-chat-bot/src/image/index.ts +278 -0
- package/vendor/gugle-chat-bot/src/index.ts +695 -0
- package/vendor/gugle-chat-bot/src/logger/index.ts +54 -0
- package/vendor/gugle-chat-bot/src/result.json +359 -0
- package/vendor/gugle-chat-bot/src/type/bili.d.ts +825 -0
- package/vendor/gugle-chat-bot/src/type/event.d.ts +146 -0
- package/vendor/gugle-chat-bot/src/type/github.d.ts +446 -0
- package/vendor/gugle-chat-bot/src/type/index.d.ts +254 -0
- package/vendor/gugle-chat-bot/tsconfig.json +46 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
background: #313338;
|
|
9
|
+
color: #dbdee1;
|
|
10
|
+
font-family: "Segoe UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif;
|
|
11
|
+
font-size: 15px;
|
|
12
|
+
line-height: 1.45;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.container {
|
|
16
|
+
width: 640px;
|
|
17
|
+
padding: 16px 20px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.header {
|
|
21
|
+
color: #949ba4;
|
|
22
|
+
font-size: 13px;
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
margin-bottom: 8px;
|
|
25
|
+
padding-bottom: 8px;
|
|
26
|
+
border-bottom: 1px solid #3f4147;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.content {
|
|
30
|
+
word-wrap: break-word;
|
|
31
|
+
overflow-wrap: anywhere;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.content p {
|
|
35
|
+
margin: 4px 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.content h1,
|
|
39
|
+
.content h2,
|
|
40
|
+
.content h3 {
|
|
41
|
+
color: #f2f3f5;
|
|
42
|
+
margin: 10px 0 4px;
|
|
43
|
+
line-height: 1.3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.content h1 {
|
|
47
|
+
font-size: 22px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.content h2 {
|
|
51
|
+
font-size: 19px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.content h3 {
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.content code {
|
|
59
|
+
background: #1e1f22;
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
padding: 1px 4px;
|
|
62
|
+
font-family: Consolas, "Courier New", monospace;
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
color: #e3e5e8;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.content pre {
|
|
68
|
+
background: #1e1f22;
|
|
69
|
+
border: 1px solid #2b2d31;
|
|
70
|
+
border-radius: 6px;
|
|
71
|
+
padding: 10px 12px;
|
|
72
|
+
margin: 6px 0;
|
|
73
|
+
overflow-x: auto;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.content pre code {
|
|
77
|
+
background: transparent;
|
|
78
|
+
padding: 0;
|
|
79
|
+
white-space: pre;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.content blockquote {
|
|
83
|
+
border-left: 4px solid #4e5058;
|
|
84
|
+
padding-left: 12px;
|
|
85
|
+
margin: 6px 0;
|
|
86
|
+
color: #b5bac1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.content ul,
|
|
90
|
+
.content ol {
|
|
91
|
+
padding-left: 24px;
|
|
92
|
+
margin: 4px 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.content a {
|
|
96
|
+
color: #00a8fc;
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.content a:hover {
|
|
101
|
+
text-decoration: underline;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.content img {
|
|
105
|
+
max-width: 100%;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.content hr {
|
|
110
|
+
border: none;
|
|
111
|
+
border-top: 1px solid #3f4147;
|
|
112
|
+
margin: 10px 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.content table {
|
|
116
|
+
border-collapse: collapse;
|
|
117
|
+
margin: 6px 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.content th,
|
|
121
|
+
.content td {
|
|
122
|
+
border: 1px solid #4e5058;
|
|
123
|
+
padding: 4px 10px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.mention {
|
|
127
|
+
color: #c9cdfb;
|
|
128
|
+
background: rgba(88, 101, 242, 0.3);
|
|
129
|
+
border-radius: 3px;
|
|
130
|
+
padding: 0 2px;
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.spoiler {
|
|
135
|
+
background: #1e1f22;
|
|
136
|
+
color: #1e1f22;
|
|
137
|
+
border-radius: 4px;
|
|
138
|
+
padding: 0 4px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.emoji {
|
|
142
|
+
height: 1.2em;
|
|
143
|
+
vertical-align: -0.2em;
|
|
144
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { botConfig } from '@/config';
|
|
4
|
+
import { Issue, IssueStateReason, State } from '@/type/github';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GitHub API 反代地址:URL 结构为 ghapi.anvilcraft.dev + apiPath。
|
|
8
|
+
* 不再经其他第三方代理。
|
|
9
|
+
*/
|
|
10
|
+
const GITHUB_API_BASE = 'https://ghapi.anvilcraft.dev';
|
|
11
|
+
|
|
12
|
+
const axiosInstance: AxiosInstance = axios.create({
|
|
13
|
+
timeout: 10000,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
'User-Agent': botConfig.userAgent
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 请求 GitHub API(经 ghapi.anvilcraft.dev 反代)。
|
|
22
|
+
* @param apiPath GitHub API 路径(不含域名),如 /repos/owner/repo/issues/123
|
|
23
|
+
* @param logger 可选日志器
|
|
24
|
+
*/
|
|
25
|
+
export function fetchGithubApi<T>(apiPath: string, logger?: Logger): Promise<T> {
|
|
26
|
+
const url = `${GITHUB_API_BASE}${apiPath}`;
|
|
27
|
+
logger?.debug(`GitHub API: ${url}`);
|
|
28
|
+
return axiosInstance.get(url).then(response => response.data as T);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** issue/PR 状态细分(用于 pill 徽章渲染) */
|
|
32
|
+
export type LinkedIssueStatus =
|
|
33
|
+
| 'open' // issue 进行中(绿色圆点)
|
|
34
|
+
| 'completed' // issue 已完成关闭(紫色对勾)
|
|
35
|
+
| 'not_planned' // issue 未计划等关闭(灰色斜杠)
|
|
36
|
+
| 'pr_open' // PR 进行中(绿色 PR 图标)
|
|
37
|
+
| 'pr_merged' // PR 已合并(紫色合并图标)
|
|
38
|
+
| 'pr_closed' // PR 关闭未合并(红色 PR 关闭图标)
|
|
39
|
+
| 'unknown'; // 状态未知(查询失败回退,灰色问号)
|
|
40
|
+
|
|
41
|
+
export interface LinkedIssueInfo {
|
|
42
|
+
state: State;
|
|
43
|
+
state_reason?: IssueStateReason;
|
|
44
|
+
/** issue / PR 标题 */
|
|
45
|
+
title?: string | number;
|
|
46
|
+
/** issue API 对 PR 会带此字段;merged_at 非空表示已合并 */
|
|
47
|
+
pull_request?: { merged_at?: string | null };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 把 API 返回的 state/state_reason/pull_request 归一化为 pill 状态 */
|
|
51
|
+
function toStatus(info: LinkedIssueInfo): LinkedIssueStatus {
|
|
52
|
+
// PR 优先:issue API 对 PR 会带 pull_request 字段,merged_at 区分合并/关闭未合并
|
|
53
|
+
if (info.pull_request) {
|
|
54
|
+
if (info.state === 'open') return 'pr_open';
|
|
55
|
+
return info.pull_request.merged_at ? 'pr_merged' : 'pr_closed';
|
|
56
|
+
}
|
|
57
|
+
if (info.state === 'open') return 'open';
|
|
58
|
+
if (info.state === 'closed') {
|
|
59
|
+
return info.state_reason === 'completed' ? 'completed' : 'not_planned';
|
|
60
|
+
}
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 查询结果:状态 + 标题(失败时标题缺失,调用方回退为编号显示) */
|
|
65
|
+
export interface LinkedIssueDetail {
|
|
66
|
+
status: LinkedIssueStatus;
|
|
67
|
+
title?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 查询 issue/PR 状态与标题。失败回退:任何错误都返回 { status: 'unknown' },
|
|
72
|
+
* 不抛异常、不阻塞调用方。
|
|
73
|
+
* @param repository owner/repo
|
|
74
|
+
* @param number issue 编号
|
|
75
|
+
* @param logger 可选日志器
|
|
76
|
+
*/
|
|
77
|
+
export async function fetchIssueDetail(
|
|
78
|
+
repository: string,
|
|
79
|
+
number: number,
|
|
80
|
+
logger?: Logger
|
|
81
|
+
): Promise<LinkedIssueDetail> {
|
|
82
|
+
try {
|
|
83
|
+
const info = await fetchGithubApi<LinkedIssueInfo>(`/repos/${repository}/issues/${number}`, logger);
|
|
84
|
+
const status = toStatus(info);
|
|
85
|
+
if (info.title == null) {
|
|
86
|
+
return { status };
|
|
87
|
+
}
|
|
88
|
+
return { status, title: String(info.title) };
|
|
89
|
+
} catch (e) {
|
|
90
|
+
logger?.warn(`查询 issue 状态失败 ${repository}#${number}:${(e as Error)?.message ?? e}`);
|
|
91
|
+
return { status: 'unknown' };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { randomInt } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { readOrCreate, writeConfigFile } from '@/config/manager';
|
|
5
|
+
|
|
6
|
+
interface PendingBinding {
|
|
7
|
+
code: string;
|
|
8
|
+
username: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface GitHubBindings {
|
|
12
|
+
bindings: Record<string, string[]>;
|
|
13
|
+
githubToQq: Record<string, string>;
|
|
14
|
+
pending: Record<string, PendingBinding>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface GitHubUserProfile {
|
|
18
|
+
login: string;
|
|
19
|
+
bio: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class GitHubBindingManager {
|
|
23
|
+
private static readonly FILE = path.resolve(process.cwd(), 'configs', 'github-bind.json');
|
|
24
|
+
private static readonly CODE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
25
|
+
private static bindings: GitHubBindings | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 绑定 GitHub 用户名。
|
|
29
|
+
* @param qqUserId 平台内用户标识:QQ 号为 number;Discord 用户为 `dc:<用户ID>` 字符串
|
|
30
|
+
* (`dc:` 前缀保证与纯数字 QQ 号不冲突,共用同一套绑定存储)
|
|
31
|
+
*/
|
|
32
|
+
public static async bind(qqUserId: number | string, username: string): Promise<{ state: 'pending' | 'bound'; code?: string }> {
|
|
33
|
+
const normalizedUsername = GitHubBindingManager.normalizeUsername(username);
|
|
34
|
+
const bindings = GitHubBindingManager.load();
|
|
35
|
+
const qqUserIdText = String(qqUserId);
|
|
36
|
+
|
|
37
|
+
const boundQqUserId = bindings.githubToQq[normalizedUsername.toLowerCase()];
|
|
38
|
+
if (boundQqUserId && boundQqUserId !== qqUserIdText) {
|
|
39
|
+
throw new Error(`GitHub 用户 ${normalizedUsername} 已被其他用户绑定`);
|
|
40
|
+
}
|
|
41
|
+
if (GitHubBindingManager.hasBinding(qqUserIdText, normalizedUsername)) {
|
|
42
|
+
return { state: 'bound' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pendingKey = `${qqUserIdText}:${normalizedUsername.toLowerCase()}`;
|
|
46
|
+
const pending = bindings.pending[pendingKey];
|
|
47
|
+
if (!pending) {
|
|
48
|
+
const code = GitHubBindingManager.generateCode();
|
|
49
|
+
bindings.pending[pendingKey] = { code, username: normalizedUsername };
|
|
50
|
+
GitHubBindingManager.save();
|
|
51
|
+
return { state: 'pending', code };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const profile = await GitHubBindingManager.getUserProfile(pending.username);
|
|
55
|
+
if (!profile.bio?.includes(pending.code)) {
|
|
56
|
+
return { state: 'pending', code: pending.code };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const boundUsernames = bindings.bindings[qqUserIdText] || [];
|
|
60
|
+
const normalizedProfileUsername = profile.login.toLowerCase();
|
|
61
|
+
const profileBoundQqUserId = bindings.githubToQq[normalizedProfileUsername];
|
|
62
|
+
if (profileBoundQqUserId && profileBoundQqUserId !== qqUserIdText) {
|
|
63
|
+
throw new Error(`GitHub 用户 ${profile.login} 已被其他用户绑定`);
|
|
64
|
+
}
|
|
65
|
+
if (!boundUsernames.some(boundUsername => boundUsername.toLowerCase() === normalizedProfileUsername)) {
|
|
66
|
+
boundUsernames.push(profile.login);
|
|
67
|
+
bindings.bindings[qqUserIdText] = boundUsernames;
|
|
68
|
+
}
|
|
69
|
+
bindings.githubToQq[normalizedProfileUsername] = qqUserIdText;
|
|
70
|
+
delete bindings.pending[pendingKey];
|
|
71
|
+
GitHubBindingManager.save();
|
|
72
|
+
return { state: 'bound' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public static isBoundUsername(username: string): boolean {
|
|
76
|
+
const normalizedUsername = username.toLowerCase();
|
|
77
|
+
return Boolean(GitHubBindingManager.load().githubToQq[normalizedUsername]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static normalizeUsername(username: string): string {
|
|
81
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(username)) {
|
|
82
|
+
throw new Error('GitHub 用户名格式无效');
|
|
83
|
+
}
|
|
84
|
+
return username;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static generateCode(): string {
|
|
88
|
+
let code = '';
|
|
89
|
+
for (let index = 0; index < 12; index++) {
|
|
90
|
+
code += GitHubBindingManager.CODE_ALPHABET[randomInt(GitHubBindingManager.CODE_ALPHABET.length)];
|
|
91
|
+
}
|
|
92
|
+
return code;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private static async getUserProfile(username: string): Promise<GitHubUserProfile> {
|
|
96
|
+
try {
|
|
97
|
+
const response = await axios.get<GitHubUserProfile>(`https://api.github.com/users/${username}`, {
|
|
98
|
+
timeout: 10000,
|
|
99
|
+
headers: { Accept: 'application/vnd.github+json' }
|
|
100
|
+
});
|
|
101
|
+
return response.data;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
|
104
|
+
throw new Error(`未找到 GitHub 用户 ${username}`);
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`无法查询 GitHub 用户 ${username}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static hasBinding(qqUserId: string, username: string): boolean {
|
|
111
|
+
return (GitHubBindingManager.load().bindings[qqUserId] || []).some(
|
|
112
|
+
boundUsername => boundUsername.toLowerCase() === username.toLowerCase()
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 重新从磁盘加载绑定数据(/reload 用;清空内存缓存后懒加载) */
|
|
117
|
+
public static reload(): void {
|
|
118
|
+
GitHubBindingManager.bindings = undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private static load(): GitHubBindings {
|
|
122
|
+
if (GitHubBindingManager.bindings) return GitHubBindingManager.bindings;
|
|
123
|
+
|
|
124
|
+
const { data } = readOrCreate<GitHubBindings>({
|
|
125
|
+
path: GitHubBindingManager.FILE,
|
|
126
|
+
factory: () => ({ bindings: {}, githubToQq: {}, pending: {} }),
|
|
127
|
+
normalize: raw => {
|
|
128
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
129
|
+
const parsed = raw as Partial<GitHubBindings>;
|
|
130
|
+
const bindings: Record<string, string[]> =
|
|
131
|
+
parsed.bindings && typeof parsed.bindings === 'object' ? (parsed.bindings as Record<string, string[]>) : {};
|
|
132
|
+
const githubToQq: Record<string, string> =
|
|
133
|
+
parsed.githubToQq && typeof parsed.githubToQq === 'object' ? (parsed.githubToQq as Record<string, string>) : {};
|
|
134
|
+
|
|
135
|
+
for (const [qqUserId, usernames] of Object.entries(bindings)) {
|
|
136
|
+
if (!Array.isArray(usernames)) continue;
|
|
137
|
+
for (const username of usernames) {
|
|
138
|
+
if (typeof username !== 'string') continue;
|
|
139
|
+
const normalizedUsername = username.toLowerCase();
|
|
140
|
+
if (!githubToQq[normalizedUsername]) {
|
|
141
|
+
githubToQq[normalizedUsername] = qqUserId;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
bindings,
|
|
148
|
+
githubToQq,
|
|
149
|
+
pending: parsed.pending && typeof parsed.pending === 'object' ? (parsed.pending as Record<string, PendingBinding>) : {}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
GitHubBindingManager.bindings = data;
|
|
154
|
+
return GitHubBindingManager.bindings;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static save(): void {
|
|
158
|
+
if (!GitHubBindingManager.bindings) return;
|
|
159
|
+
writeConfigFile(GitHubBindingManager.FILE, GitHubBindingManager.bindings);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Issue, IssueEvent, OpenedIssueEvent, ReopenedIssueEvent, User } from '@/type/github';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { Template, tryGenerateImage } from '@/image';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
import { renderIssueBody } from '@/features/github/image';
|
|
6
|
+
|
|
7
|
+
function getRepositoryParts(repositoryUrl?: string, htmlUrl?: string): { owner: string; name: string } {
|
|
8
|
+
const url = repositoryUrl || htmlUrl;
|
|
9
|
+
if (!url) return { owner: 'GitHub', name: '' };
|
|
10
|
+
const parts = url.split('/').filter(Boolean);
|
|
11
|
+
const githubIndex = parts.indexOf('github.com');
|
|
12
|
+
const repositoryParts = githubIndex >= 0 ? parts.slice(githubIndex + 1, githubIndex + 3) : parts.slice(-2);
|
|
13
|
+
return repositoryParts.length === 2
|
|
14
|
+
? { owner: repositoryParts[0]!, name: repositoryParts[1]! }
|
|
15
|
+
: { owner: 'GitHub', name: '' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getIssueState(issue: Issue): string {
|
|
19
|
+
if (issue.state == 'open') {
|
|
20
|
+
return `
|
|
21
|
+
<div class="state_label" style="color:white; background-color: #347d39; box-shadow-color: #347d39;">
|
|
22
|
+
<div style="display: flex; align-items: center;">
|
|
23
|
+
<svg focusable="false" aria-label="Issue"
|
|
24
|
+
class="octicon octicon-issue-opened prc-StateLabel-Icon-YICrR" role="img" viewBox="0 0 16 16"
|
|
25
|
+
width="16" height="16" fill="currentColor" display="inline-block"
|
|
26
|
+
overflow="visible" style="margin-right:4px; vertical-align:text-bottom">
|
|
27
|
+
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/>
|
|
28
|
+
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"/>
|
|
29
|
+
</svg>
|
|
30
|
+
Open
|
|
31
|
+
</div>
|
|
32
|
+
</div>\n
|
|
33
|
+
`;
|
|
34
|
+
} else {
|
|
35
|
+
if (issue.state_reason == 'completed') {
|
|
36
|
+
return `
|
|
37
|
+
<div class="state_label" style="color:white; background-color: #8256d0; box-shadow-color: #8256d0;">
|
|
38
|
+
<div style="display: flex; align-items: center;">
|
|
39
|
+
<svg focusable="false" aria-label="Issue" class="octicon octicon-issue-closed prc-StateLabel-Icon-YICrR"
|
|
40
|
+
role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block"
|
|
41
|
+
overflow="visible" style="margin-right:4px; vertical-align:text-bottom">
|
|
42
|
+
<path d="M11.28 6.78a.75.75 0 0 0-1.06-1.06L7.25 8.69 5.78 7.22a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.06 0l3.5-3.5Z"/>
|
|
43
|
+
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-1.5 0a6.5 6.5 0 1 0-13 0 6.5 6.5 0 0 0 13 0Z"></path>
|
|
44
|
+
</svg>
|
|
45
|
+
Closed
|
|
46
|
+
</div>
|
|
47
|
+
</div>\n
|
|
48
|
+
`;
|
|
49
|
+
} else {
|
|
50
|
+
return `
|
|
51
|
+
<div class="state_label" style="color:white; background-color: #656c76; box-shadow-color: #656c76;">
|
|
52
|
+
<div style="display: flex; align-items: center;">
|
|
53
|
+
<svg focusable="false" aria-label="Issue, not planned" class="octicon octicon-skip prc-StateLabel-Icon-YICrR"
|
|
54
|
+
role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block"
|
|
55
|
+
overflow="visible" style="margin-right:4px; vertical-align: text-bottom;">
|
|
56
|
+
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13
|
|
57
|
+
0Zm9.78-2.22-5.5 5.5a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l5.5-5.5a.751.751 0 0 1
|
|
58
|
+
1.042.018.751.751 0 0 1 .018 1.042Z"/>
|
|
59
|
+
</svg>
|
|
60
|
+
Closed
|
|
61
|
+
</div>
|
|
62
|
+
</div>\n
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function issuesHandler(issue: Issue, logger?: Logger, operation?: string, sender?: User, extra?: string) {
|
|
69
|
+
let labelsHtml = '';
|
|
70
|
+
for (let label of issue.labels) {
|
|
71
|
+
labelsHtml += `<div class="sidebar-label" style="color:#${label.color}ff; background-color: #${label.color}55; border:2px solid #${label.color}99">${label.name}</div>\n`;
|
|
72
|
+
}
|
|
73
|
+
let headerExtra: string | undefined = undefined;
|
|
74
|
+
if (sender) {
|
|
75
|
+
headerExtra = `<div class="message">用户<div class="user">${sender.login}</div>${operation}了 </div>`;
|
|
76
|
+
}
|
|
77
|
+
return new Promise<string>((resolve, reject) => {
|
|
78
|
+
const repository = getRepositoryParts(issue.repository_url, issue.html_url);
|
|
79
|
+
const repoFullName = `${repository.owner}/${repository.name}`;
|
|
80
|
+
renderIssueBody(issue.body, repoFullName, logger)
|
|
81
|
+
.then(bodyHtml => {
|
|
82
|
+
const template = Template.load('issue', 'src/features/github/template')
|
|
83
|
+
.arg('header extra', headerExtra || '')
|
|
84
|
+
.arg('repository owner', repository.owner)
|
|
85
|
+
.arg('repository name', repository.name)
|
|
86
|
+
.arg('issue number', issue.number)
|
|
87
|
+
.arg('state label', getIssueState(issue))
|
|
88
|
+
.arg('issue title', issue.title)
|
|
89
|
+
.arg('issue author', issue.user.login)
|
|
90
|
+
.arg('issue body', bodyHtml)
|
|
91
|
+
.arg('issue created at', dayjs(issue.created_at).format('YYYY-MM-DD HH:mm:ss'))
|
|
92
|
+
.arg('issue labels', labelsHtml || 'No labels')
|
|
93
|
+
.arg('extra', extra || '');
|
|
94
|
+
const templateFile = template.file();
|
|
95
|
+
template
|
|
96
|
+
.handler()
|
|
97
|
+
.then(issue => {
|
|
98
|
+
logger?.debug(`Start process issue message...`);
|
|
99
|
+
tryGenerateImage(resolve, reject, issue, 820, templateFile);
|
|
100
|
+
})
|
|
101
|
+
.catch(reject);
|
|
102
|
+
})
|
|
103
|
+
.catch(reject);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function issuesClosed(issue: IssueEvent, logger?: Logger): Promise<string> {
|
|
108
|
+
let extra: string | undefined;
|
|
109
|
+
if (issue.issue.state_reason) {
|
|
110
|
+
extra = "<div class='h1'>关闭原因:</div>\n";
|
|
111
|
+
}
|
|
112
|
+
if (issue.issue.state_reason == 'completed') {
|
|
113
|
+
extra += "<div class='body'>已完成</div>\n";
|
|
114
|
+
} else if (issue.issue.state_reason == 'not_planned') {
|
|
115
|
+
extra += "<div class='body'>未计划</div>\n";
|
|
116
|
+
} else if (issue.issue.state_reason == 'duplicate') {
|
|
117
|
+
extra += "<div class='body'>重复</div>\n";
|
|
118
|
+
} else {
|
|
119
|
+
extra += `<div class='body'>${issue.issue.state_reason}</div>\n`;
|
|
120
|
+
}
|
|
121
|
+
return issuesHandler(issue.issue, logger, '关闭', issue.sender, extra);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function issuesOpened(issue: OpenedIssueEvent | ReopenedIssueEvent, logger?: Logger): Promise<string> {
|
|
125
|
+
let operation: string;
|
|
126
|
+
if (issue.action == 'reopened') {
|
|
127
|
+
operation = '重新打开';
|
|
128
|
+
} else {
|
|
129
|
+
operation = '提交';
|
|
130
|
+
}
|
|
131
|
+
return issuesHandler(issue.issue, logger, operation, issue.sender);
|
|
132
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { OpenedPullRequestEvent, PullRequest, PullRequestEvent, ReopenedPullRequestEvent, User } from '@/type/github';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { Template, tryGenerateImage } from '@/image';
|
|
4
|
+
import { renderIssueBody } from '@/features/github/image';
|
|
5
|
+
import dayjs from 'dayjs';
|
|
6
|
+
|
|
7
|
+
function getRepositoryParts(repositoryUrl?: string, htmlUrl?: string): { owner: string; name: string } {
|
|
8
|
+
const url = repositoryUrl || htmlUrl;
|
|
9
|
+
if (!url) return { owner: 'GitHub', name: '' };
|
|
10
|
+
const parts = url.split('/').filter(Boolean);
|
|
11
|
+
const githubIndex = parts.indexOf('github.com');
|
|
12
|
+
const repositoryParts = githubIndex >= 0 ? parts.slice(githubIndex + 1, githubIndex + 3) : parts.slice(-2);
|
|
13
|
+
return repositoryParts.length === 2
|
|
14
|
+
? { owner: repositoryParts[0]!, name: repositoryParts[1]! }
|
|
15
|
+
: { owner: 'GitHub', name: '' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getPullRequestState(pr: PullRequest): string {
|
|
19
|
+
if (pr.state == 'open') {
|
|
20
|
+
if (pr.draft) {
|
|
21
|
+
return `
|
|
22
|
+
<div class="state_label" style="color:white; background-color: #656c76; box-shadow-color: #656c76;">
|
|
23
|
+
<div style="display: flex; align-items: center;">
|
|
24
|
+
<svg class="octicon" focusable="false" height="16" fill="currentColor" viewBox="0 0 16 16" version="1.1" width="16" aria-hidden="true">
|
|
25
|
+
<path d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251
|
|
26
|
+
0 0 1 3.25 1Zm9.5 14a2.25 2.25 0 1 1 0-4.5 2.25 2.25 0 0 1 0 4.5ZM2.5 3.25a.75.75 0 1 0 1.5 0
|
|
27
|
+
.75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75
|
|
28
|
+
0 0 0 0-1.5ZM14 7.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm0-4.25a1.25 1.25 0 1 1-2.5 0
|
|
29
|
+
1.25 1.25 0 0 1 2.5 0Z"/>
|
|
30
|
+
</svg>
|
|
31
|
+
Draft
|
|
32
|
+
</div>
|
|
33
|
+
</div>\n
|
|
34
|
+
`;
|
|
35
|
+
} else {
|
|
36
|
+
return `
|
|
37
|
+
<div class="state_label" style="color:white; background-color: #347d39; box-shadow-color: #347d39;">
|
|
38
|
+
<div style="display: flex; align-items: center;">
|
|
39
|
+
<svg class="octicon" focusable="false" height="16" fill="currentColor" viewBox="0 0 16 16" version="1.1" width="16" aria-hidden="true">
|
|
40
|
+
<path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25
|
|
41
|
+
0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251
|
|
42
|
+
2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1
|
|
43
|
+
0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0
|
|
44
|
+
0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"/>
|
|
45
|
+
</svg>
|
|
46
|
+
Open
|
|
47
|
+
</div>
|
|
48
|
+
</div>\n
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
if (pr.merged) {
|
|
53
|
+
return `
|
|
54
|
+
<div class="state_label" style="color:white; background-color: #8256d0; box-shadow-color: #8256d0;">
|
|
55
|
+
<div style="display: flex; align-items: center;">
|
|
56
|
+
<svg class="octicon" focusable="false" height="16" fill="currentColor" viewBox="0 0 16 16" version="1.1" width="16" aria-hidden="true">
|
|
57
|
+
<path d="M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734
|
|
58
|
+
0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75
|
|
59
|
+
0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75
|
|
60
|
+
0 1 0 0 .005V3.25Z"/>
|
|
61
|
+
</svg>
|
|
62
|
+
Merged
|
|
63
|
+
</div>
|
|
64
|
+
</div>\n
|
|
65
|
+
`;
|
|
66
|
+
} else {
|
|
67
|
+
return `
|
|
68
|
+
<div class="state_label" style="color:white; background-color: #c93c37; box-shadow-color: #c93c37;">
|
|
69
|
+
<div style="display: flex; align-items: center;">
|
|
70
|
+
<svg class="octicon" focusable="false" height="16" fill="currentColor" viewBox="0 0 16 16" version="1.1" width="16" aria-hidden="true">
|
|
71
|
+
<path d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251
|
|
72
|
+
0 0 1 3.25 1Zm9.5 5.5a.75.75 0 0 1 .75.75v3.378a2.251 2.251 0 1 1-1.5 0V7.25a.75.75
|
|
73
|
+
0 0 1 .75-.75Zm-2.03-5.273a.75.75 0 0 1 1.06 0l.97.97.97-.97a.748.748 0 0 1 1.265.332.75.75
|
|
74
|
+
0 0 1-.205.729l-.97.97.97.97a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-.97-.97-.97.97a.749.749
|
|
75
|
+
0 0 1-1.275-.326.749.749 0 0 1 .215-.734l.97-.97-.97-.97a.75.75 0 0 1 0-1.06ZM2.5 3.25a.75.75
|
|
76
|
+
0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1
|
|
77
|
+
0 0 1.5.75.75 0 0 0 0-1.5Z"/>
|
|
78
|
+
</svg>
|
|
79
|
+
Closed
|
|
80
|
+
</div>
|
|
81
|
+
</div>\n
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function prHandler(pr: PullRequest, logger?: Logger, operation?: string, sender?: User, extra?: string) {
|
|
88
|
+
let labelsHtml = '';
|
|
89
|
+
for (let label of pr.labels) {
|
|
90
|
+
labelsHtml += `<div class="sidebar-label" style="color:#${label.color}ff; background-color: #${label.color}55; border:2px solid #${label.color}99">${label.name}</div>\n`;
|
|
91
|
+
}
|
|
92
|
+
let headerExtra: string | undefined = undefined;
|
|
93
|
+
if (sender) {
|
|
94
|
+
headerExtra = `<div class="message">用户<div class="user">${sender.login}</div>${operation}了 </div>`;
|
|
95
|
+
}
|
|
96
|
+
return new Promise<string>((resolve, reject) => {
|
|
97
|
+
const repository = getRepositoryParts(pr.repository_url, pr.html_url);
|
|
98
|
+
const repoFullName = `${repository.owner}/${repository.name}`;
|
|
99
|
+
renderIssueBody(pr.body, repoFullName, logger)
|
|
100
|
+
.then(bodyHtml => {
|
|
101
|
+
const template = Template.load('pull_request', 'src/features/github/template')
|
|
102
|
+
.arg('header extra', headerExtra || '')
|
|
103
|
+
.arg('repository owner', repository.owner)
|
|
104
|
+
.arg('repository name', repository.name)
|
|
105
|
+
.arg('pr number', pr.number)
|
|
106
|
+
.arg('pr title', pr.title)
|
|
107
|
+
.arg('pr body', bodyHtml)
|
|
108
|
+
.arg('pr labels', labelsHtml || 'No labels')
|
|
109
|
+
.arg('pr author', pr.user.login)
|
|
110
|
+
.arg('pr created at', dayjs(pr.created_at).format('YYYY-MM-DD HH:mm:ss'))
|
|
111
|
+
.arg('state label', getPullRequestState(pr))
|
|
112
|
+
.arg('extra', extra || '');
|
|
113
|
+
const templateFile = template.file();
|
|
114
|
+
template
|
|
115
|
+
.handler()
|
|
116
|
+
.then(pr => {
|
|
117
|
+
logger?.debug(`Start process pull request message...`);
|
|
118
|
+
tryGenerateImage(resolve, reject, pr, 820, templateFile);
|
|
119
|
+
})
|
|
120
|
+
.catch(reject);
|
|
121
|
+
})
|
|
122
|
+
.catch(reject);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function prClosed(pr: PullRequestEvent, logger?: Logger): Promise<string> {
|
|
127
|
+
let operation: string;
|
|
128
|
+
if (pr.pull_request.merged) {
|
|
129
|
+
operation = '合并';
|
|
130
|
+
} else {
|
|
131
|
+
operation = '关闭';
|
|
132
|
+
}
|
|
133
|
+
return prHandler(pr.pull_request, logger, operation, pr.sender);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function prOpened(pr: OpenedPullRequestEvent | ReopenedPullRequestEvent, logger?: Logger): Promise<string> {
|
|
137
|
+
let operation: string;
|
|
138
|
+
if (pr.action == 'reopened') {
|
|
139
|
+
operation = '重新打开';
|
|
140
|
+
} else {
|
|
141
|
+
operation = '提交';
|
|
142
|
+
}
|
|
143
|
+
return prHandler(pr.pull_request, logger, operation, pr.sender);
|
|
144
|
+
}
|