@karinjs/adapter-qqbot 1.2.10 → 2.0.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.md +326 -85
- package/dist/apps/login.d.ts +20 -0
- package/dist/apps/login.js +57 -0
- package/dist/config-H7LrvlNU.js +197 -0
- package/dist/index.d.ts +670 -0
- package/dist/index.js +2847 -0
- package/dist/onboard-BX8g8Wik.js +249 -0
- package/dist/utils-DO4gEkVN.js +3 -0
- package/dist/web.config.d.ts +22 -0
- package/dist/web.config.js +173 -0
- package/package.json +34 -21
- package/config/comment/config.json +0 -3
- package/config/config/config.yaml +0 -53
- package/lib/cli/pr.d.ts +0 -2
- package/lib/cli/pr.js +0 -73
- package/lib/core/adapter/adapter.d.ts +0 -91
- package/lib/core/adapter/adapter.js +0 -144
- package/lib/core/adapter/handler.d.ts +0 -49
- package/lib/core/adapter/handler.js +0 -140
- package/lib/core/adapter/markdown.d.ts +0 -38
- package/lib/core/adapter/markdown.js +0 -220
- package/lib/core/adapter/normal.d.ts +0 -30
- package/lib/core/adapter/normal.js +0 -263
- package/lib/core/api/index.d.ts +0 -167
- package/lib/core/api/index.js +0 -180
- package/lib/core/api/sign.d.ts +0 -22
- package/lib/core/api/sign.js +0 -40
- package/lib/core/api/types.d.ts +0 -236
- package/lib/core/api/types.js +0 -1
- package/lib/core/event/conver.d.ts +0 -10
- package/lib/core/event/conver.js +0 -57
- package/lib/core/event/index.d.ts +0 -9
- package/lib/core/event/index.js +0 -98
- package/lib/core/event/message.d.ts +0 -27
- package/lib/core/event/message.js +0 -95
- package/lib/core/event/notice.d.ts +0 -50
- package/lib/core/event/notice.js +0 -133
- package/lib/core/event/types.d.ts +0 -473
- package/lib/core/event/types.js +0 -1
- package/lib/core/internal/axios.d.ts +0 -25
- package/lib/core/internal/axios.js +0 -46
- package/lib/core/internal/sign.d.ts +0 -22
- package/lib/core/internal/sign.js +0 -40
- package/lib/core/webhook/c2c.d.ts +0 -2
- package/lib/core/webhook/c2c.js +0 -14
- package/lib/core/webhook/http.d.ts +0 -2
- package/lib/core/webhook/http.js +0 -35
- package/lib/core/webhook/index.d.ts +0 -17
- package/lib/core/webhook/index.js +0 -87
- package/lib/core/webhook/ws.d.ts +0 -2
- package/lib/core/webhook/ws.js +0 -79
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -5
- package/lib/root.d.ts +0 -5
- package/lib/root.js +0 -9
- package/lib/types/config.d.ts +0 -53
- package/lib/types/config.js +0 -1
- package/lib/utils/common.d.ts +0 -80
- package/lib/utils/common.js +0 -198
- package/lib/utils/config.d.ts +0 -9
- package/lib/utils/config.js +0 -54
- package/lib/utils/dir.d.ts +0 -1
- package/lib/utils/dir.js +0 -1
- package/lib/utils/index.d.ts +0 -4
- package/lib/utils/index.js +0 -4
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { d as sleep, f as log, i as getDefaultConfig, n as config, o as writeConfig } from "./config-H7LrvlNU.js";
|
|
2
|
+
import qrcode from "qrcode";
|
|
3
|
+
import axios from "node-karin/axios";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
|
|
6
|
+
//#region src/core/onboard/crypto.ts
|
|
7
|
+
/**
|
|
8
|
+
* 生成 256-bit 随机 AES 密钥(base64)
|
|
9
|
+
*/
|
|
10
|
+
const generateBindKey = () => crypto.randomBytes(32).toString("base64");
|
|
11
|
+
/**
|
|
12
|
+
* 解密 bot_encrypt_secret(AES-256-GCM)
|
|
13
|
+
*
|
|
14
|
+
* 密文布局:IV (12 bytes) || ciphertext (N bytes) || AuthTag (16 bytes)
|
|
15
|
+
*/
|
|
16
|
+
const decryptSecret = (encryptedBase64, keyBase64) => {
|
|
17
|
+
const key = Buffer.from(keyBase64, "base64");
|
|
18
|
+
if (key.length !== 32) throw new Error(`AES key length must be 32 bytes, got ${key.length}`);
|
|
19
|
+
const raw = Buffer.from(encryptedBase64, "base64");
|
|
20
|
+
const iv = raw.subarray(0, 12);
|
|
21
|
+
const ciphertext = raw.subarray(12, raw.length - 16);
|
|
22
|
+
const authTag = raw.subarray(raw.length - 16);
|
|
23
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
24
|
+
decipher.setAuthTag(authTag);
|
|
25
|
+
let plaintext = decipher.update(ciphertext, void 0, "utf8");
|
|
26
|
+
plaintext += decipher.final("utf8");
|
|
27
|
+
return plaintext;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/onboard/portal.ts
|
|
32
|
+
/** QQ 开放平台扫码绑定服务域名 */
|
|
33
|
+
const PORTAL_HOST = "q.qq.com";
|
|
34
|
+
const CREATE_PATH = "/lite/create_bind_task";
|
|
35
|
+
const POLL_PATH = "/lite/poll_bind_result";
|
|
36
|
+
/** 扫码页面模板 */
|
|
37
|
+
const CONNECT_URL = "https://q.qq.com/qqbot/openclaw/connect.html?task_id={task_id}&_wv=2&source=karin";
|
|
38
|
+
const headers = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
Accept: "application/json"
|
|
41
|
+
};
|
|
42
|
+
/** 绑定状态 */
|
|
43
|
+
const BindStatus = {
|
|
44
|
+
NONE: 0,
|
|
45
|
+
PENDING: 1,
|
|
46
|
+
COMPLETED: 2,
|
|
47
|
+
EXPIRED: 3
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* 创建绑定任务
|
|
51
|
+
* @returns task_id 与本地生成的 AES key
|
|
52
|
+
*/
|
|
53
|
+
const createBindTask = async () => {
|
|
54
|
+
const key = generateBindKey();
|
|
55
|
+
const { data } = await axios.post(`https://${PORTAL_HOST}${CREATE_PATH}`, { key }, { headers });
|
|
56
|
+
if (data.retcode !== 0) throw new Error(`create_bind_task: ${data.msg || "unknown error"}`);
|
|
57
|
+
const taskId = data.data?.task_id;
|
|
58
|
+
if (!taskId) throw new Error("create_bind_task: missing task_id");
|
|
59
|
+
return {
|
|
60
|
+
taskId,
|
|
61
|
+
aesKey: key
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* 轮询绑定结果
|
|
66
|
+
*/
|
|
67
|
+
const pollBindResult = async (taskId) => {
|
|
68
|
+
const { data } = await axios.post(`https://${PORTAL_HOST}${POLL_PATH}`, { task_id: taskId }, { headers });
|
|
69
|
+
if (data.retcode !== 0) throw new Error(`poll_bind_result: ${data.msg || "unknown error"}`);
|
|
70
|
+
const d = data.data ?? {};
|
|
71
|
+
return {
|
|
72
|
+
status: d.status ?? BindStatus.NONE,
|
|
73
|
+
appId: String(d.bot_appid ?? ""),
|
|
74
|
+
encryptedSecret: d.bot_encrypt_secret ?? "",
|
|
75
|
+
userOpenid: d.user_openid ?? ""
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* 构造扫码 URL
|
|
80
|
+
*/
|
|
81
|
+
const buildConnectUrl = (taskId) => CONNECT_URL.replace("{task_id}", encodeURIComponent(taskId));
|
|
82
|
+
/**
|
|
83
|
+
* 通过 appId + secret 直接调一次 @me,拿到 bot 昵称
|
|
84
|
+
* 用于扫码完成后的展示与写入配置
|
|
85
|
+
*/
|
|
86
|
+
const fetchBotName = async (appId, secret) => {
|
|
87
|
+
try {
|
|
88
|
+
const accessToken = (await axios.post("https://bots.qq.com/app/getAppAccessToken", {
|
|
89
|
+
appId,
|
|
90
|
+
clientSecret: secret
|
|
91
|
+
})).data?.access_token;
|
|
92
|
+
if (!accessToken) return "";
|
|
93
|
+
return (await axios.get("https://api.sgroup.qq.com/users/@me", { headers: { Authorization: `QQBot ${accessToken}` } })).data?.username || "";
|
|
94
|
+
} catch {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/core/onboard/qr.ts
|
|
101
|
+
/** 默认轮询间隔(毫秒) */
|
|
102
|
+
const POLL_INTERVAL = 2e3;
|
|
103
|
+
/** 默认总超时(秒) */
|
|
104
|
+
const DEFAULT_TIMEOUT_S = 600;
|
|
105
|
+
/** 单张二维码硬编码有效期:60s,到时主动判定过期触发刷新 */
|
|
106
|
+
const QR_TTL_MS = 6e4;
|
|
107
|
+
/** 二维码过期最大刷新次数 */
|
|
108
|
+
const MAX_REFRESHES = 3;
|
|
109
|
+
/**
|
|
110
|
+
* 生成二维码(同时拿到 ascii / base64)
|
|
111
|
+
*/
|
|
112
|
+
const renderQr = async (url) => {
|
|
113
|
+
return {
|
|
114
|
+
ascii: await qrcode.toString(url, {
|
|
115
|
+
type: "terminal",
|
|
116
|
+
small: true
|
|
117
|
+
}).catch(() => ""),
|
|
118
|
+
base64: `base64://${(await qrcode.toDataURL(url, {
|
|
119
|
+
width: 320,
|
|
120
|
+
margin: 2
|
|
121
|
+
})).split(",")[1]}`
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* 扫码登录主流程:
|
|
126
|
+
* 1. 创建绑定任务,调用 onQr 暴露二维码
|
|
127
|
+
* 2. 轮询直到 COMPLETED / 二维码过期刷新 / 超时
|
|
128
|
+
* 3. 解密返回 secret
|
|
129
|
+
*/
|
|
130
|
+
const qrRegister = async (opts = {}) => {
|
|
131
|
+
const { timeoutSeconds = DEFAULT_TIMEOUT_S, onQr } = opts;
|
|
132
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
133
|
+
for (let refresh = 0; refresh <= MAX_REFRESHES; refresh++) {
|
|
134
|
+
let task;
|
|
135
|
+
try {
|
|
136
|
+
task = await createBindTask();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
log("error", "[扫码登录] 创建绑定任务失败:", err);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const url = buildConnectUrl(task.taskId);
|
|
142
|
+
const { ascii, base64 } = await renderQr(url);
|
|
143
|
+
const qrDeadline = Date.now() + QR_TTL_MS;
|
|
144
|
+
if (ascii) {
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(ascii);
|
|
147
|
+
}
|
|
148
|
+
console.log(` 请使用 QQ 扫码(60s 内有效),或在手机 QQ 打开:\n ${url}\n`);
|
|
149
|
+
if (onQr) try {
|
|
150
|
+
await onQr({
|
|
151
|
+
url,
|
|
152
|
+
ascii,
|
|
153
|
+
base64,
|
|
154
|
+
refresh
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
log("warn", "[扫码登录] onQr 回调异常:", err);
|
|
158
|
+
}
|
|
159
|
+
let expired = false;
|
|
160
|
+
while (Date.now() < deadline) {
|
|
161
|
+
if (Date.now() >= qrDeadline) {
|
|
162
|
+
expired = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
let result;
|
|
166
|
+
try {
|
|
167
|
+
result = await pollBindResult(task.taskId);
|
|
168
|
+
} catch {
|
|
169
|
+
await sleep(POLL_INTERVAL);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (result.status === BindStatus.COMPLETED) {
|
|
173
|
+
if (!result.encryptedSecret) {
|
|
174
|
+
log("error", "[扫码登录] 绑定结果缺少加密密钥");
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
let secret;
|
|
178
|
+
try {
|
|
179
|
+
secret = decryptSecret(result.encryptedSecret, task.aesKey);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
log("error", "[扫码登录] 解密 secret 失败:", err);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
log("info", `[扫码登录] 授权成功,App ID: ${result.appId}`);
|
|
185
|
+
return {
|
|
186
|
+
appId: result.appId,
|
|
187
|
+
secret,
|
|
188
|
+
userOpenid: result.userOpenid
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (result.status === BindStatus.EXPIRED) {
|
|
192
|
+
expired = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
await sleep(POLL_INTERVAL);
|
|
196
|
+
}
|
|
197
|
+
if (expired) {
|
|
198
|
+
if (refresh >= MAX_REFRESHES) {
|
|
199
|
+
log("error", `[扫码登录] 二维码已过期 ${MAX_REFRESHES + 1} 次,终止流程`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
log("info", `[扫码登录] 二维码 60s 内未完成扫码,正在刷新 (${refresh + 1}/${MAX_REFRESHES})`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
log("error", `[扫码登录] 等待扫码总超时(${timeoutSeconds}s)`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/core/onboard/index.ts
|
|
213
|
+
/** 是否需要触发扫码引导:完全没有 bot 配置时为 true */
|
|
214
|
+
const needQrOnboard = () => config().length === 0;
|
|
215
|
+
/**
|
|
216
|
+
* 执行扫码登录并把结果写入 config.json
|
|
217
|
+
*
|
|
218
|
+
* - 同 appId 已存在 → 覆盖 secret / name
|
|
219
|
+
* - 不存在 → 追加新配置
|
|
220
|
+
*
|
|
221
|
+
* 支持 `opts.onQr` 把新二维码透出给调用方(如 #QQ登录 指令把图片回给用户)
|
|
222
|
+
*/
|
|
223
|
+
const runQrOnboard = async (opts = {}) => {
|
|
224
|
+
const result = await qrRegister(opts);
|
|
225
|
+
if (!result) return false;
|
|
226
|
+
const name = await fetchBotName(result.appId, result.secret);
|
|
227
|
+
if (name) log("info", `[扫码登录] Bot 名称: ${name}`);
|
|
228
|
+
const list = config();
|
|
229
|
+
const idx = list.findIndex((item) => item.appId === result.appId);
|
|
230
|
+
if (idx >= 0) {
|
|
231
|
+
list[idx].secret = result.secret;
|
|
232
|
+
if (name) list[idx].name = name;
|
|
233
|
+
log("info", `[扫码登录] 已更新已有配置: ${result.appId}`);
|
|
234
|
+
} else {
|
|
235
|
+
const defaults = getDefaultConfig()[0];
|
|
236
|
+
list.push({
|
|
237
|
+
...defaults,
|
|
238
|
+
name: name || "",
|
|
239
|
+
appId: result.appId,
|
|
240
|
+
secret: result.secret
|
|
241
|
+
});
|
|
242
|
+
log("info", `[扫码登录] 已添加新配置: ${result.appId}`);
|
|
243
|
+
}
|
|
244
|
+
writeConfig(list);
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
export { runQrOnboard as n, needQrOnboard as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as _$node_karin0 from "node-karin";
|
|
2
|
+
|
|
3
|
+
//#region src/web.config.d.ts
|
|
4
|
+
declare const _default: _$node_karin0.DefineConfig<{
|
|
5
|
+
qqbot: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
secret: string;
|
|
9
|
+
prodApi: string;
|
|
10
|
+
sandboxApi: string;
|
|
11
|
+
tokenApi: string;
|
|
12
|
+
sandbox: boolean;
|
|
13
|
+
qqEnable: boolean;
|
|
14
|
+
guildEnable: boolean;
|
|
15
|
+
guildMode: boolean;
|
|
16
|
+
regex: string[];
|
|
17
|
+
"keyboard:enable": boolean;
|
|
18
|
+
"event:type": string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { _default as default };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { a as pkg, n as config, o as writeConfig } from "./config-H7LrvlNU.js";
|
|
2
|
+
import "./utils-DO4gEkVN.js";
|
|
3
|
+
import { components, defineConfig } from "node-karin";
|
|
4
|
+
|
|
5
|
+
//#region src/web.config.ts
|
|
6
|
+
var web_config_default = defineConfig({
|
|
7
|
+
info: {
|
|
8
|
+
id: pkg().name,
|
|
9
|
+
name: "QQBot 适配器",
|
|
10
|
+
version: pkg().version,
|
|
11
|
+
description: "为 karin 提供 QQ 官方 Bot 连接能力(2.0)",
|
|
12
|
+
author: [{
|
|
13
|
+
name: "shijin",
|
|
14
|
+
avatar: "https://github.com/sj817.png"
|
|
15
|
+
}]
|
|
16
|
+
},
|
|
17
|
+
components: () => {
|
|
18
|
+
const data = [];
|
|
19
|
+
config().forEach((item) => {
|
|
20
|
+
data.push({
|
|
21
|
+
title: item.name || item.appId,
|
|
22
|
+
subtitle: item.appId,
|
|
23
|
+
name: item.name,
|
|
24
|
+
appId: item.appId,
|
|
25
|
+
secret: item.secret,
|
|
26
|
+
prodApi: item.prodApi,
|
|
27
|
+
sandboxApi: item.sandboxApi,
|
|
28
|
+
tokenApi: item.tokenApi,
|
|
29
|
+
sandbox: item.sandbox,
|
|
30
|
+
qqEnable: item.qqEnable,
|
|
31
|
+
guildEnable: item.guildEnable,
|
|
32
|
+
guildMode: item.guildMode === 1,
|
|
33
|
+
regex: item.regex.map((r) => `${r.reg} ${r.rep}`),
|
|
34
|
+
"keyboard:enable": item.keyboard?.enable !== false,
|
|
35
|
+
"event:type": String(item.event?.type ?? 2)
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return [components.accordionPro.create("qqbot", data, {
|
|
39
|
+
label: "QQBot",
|
|
40
|
+
description: "QQBot 配置",
|
|
41
|
+
children: {
|
|
42
|
+
key: "qqbotConfig",
|
|
43
|
+
children: [
|
|
44
|
+
components.input.create("name", {
|
|
45
|
+
label: "机器人名称",
|
|
46
|
+
description: "机器人名称,扫码登录会自动获取"
|
|
47
|
+
}),
|
|
48
|
+
components.input.create("appId", {
|
|
49
|
+
label: "AppID",
|
|
50
|
+
description: "机器人 AppID",
|
|
51
|
+
isRequired: true
|
|
52
|
+
}),
|
|
53
|
+
components.input.create("secret", {
|
|
54
|
+
label: "Secret",
|
|
55
|
+
description: "机器人 Secret",
|
|
56
|
+
isRequired: true
|
|
57
|
+
}),
|
|
58
|
+
components.input.create("prodApi", {
|
|
59
|
+
label: "正式环境 API",
|
|
60
|
+
defaultValue: "https://api.sgroup.qq.com",
|
|
61
|
+
isRequired: true
|
|
62
|
+
}),
|
|
63
|
+
components.input.create("sandboxApi", {
|
|
64
|
+
label: "沙盒环境 API",
|
|
65
|
+
defaultValue: "https://sandbox.api.sgroup.qq.com",
|
|
66
|
+
isRequired: true
|
|
67
|
+
}),
|
|
68
|
+
components.input.create("tokenApi", {
|
|
69
|
+
label: "AccessToken 接口",
|
|
70
|
+
defaultValue: "https://bots.qq.com/app/getAppAccessToken",
|
|
71
|
+
isRequired: true
|
|
72
|
+
}),
|
|
73
|
+
components.switch.create("sandbox", {
|
|
74
|
+
label: "沙盒环境",
|
|
75
|
+
description: "启用后调用沙盒 API",
|
|
76
|
+
defaultSelected: false
|
|
77
|
+
}),
|
|
78
|
+
components.switch.create("qqEnable", {
|
|
79
|
+
label: "QQ 场景",
|
|
80
|
+
description: "是否处理私聊和群聊消息",
|
|
81
|
+
defaultSelected: true
|
|
82
|
+
}),
|
|
83
|
+
components.switch.create("guildEnable", {
|
|
84
|
+
label: "频道场景",
|
|
85
|
+
description: "是否处理频道消息",
|
|
86
|
+
defaultSelected: true
|
|
87
|
+
}),
|
|
88
|
+
components.switch.create("guildMode", {
|
|
89
|
+
label: "频道私域模式",
|
|
90
|
+
description: "开启 = 私域机器人;关闭 = 公域机器人",
|
|
91
|
+
defaultSelected: false
|
|
92
|
+
}),
|
|
93
|
+
components.input.group("regex", {
|
|
94
|
+
data: [],
|
|
95
|
+
label: "消息正则替换",
|
|
96
|
+
description: "每行格式:正则表达式 替换内容。如:^/ #",
|
|
97
|
+
template: components.input.create("regex", {
|
|
98
|
+
label: "正则规则",
|
|
99
|
+
placeholder: "^/ #"
|
|
100
|
+
})
|
|
101
|
+
}),
|
|
102
|
+
components.switch.create("keyboard:enable", {
|
|
103
|
+
label: "URL 自动按钮",
|
|
104
|
+
description: "将文本中的链接自动转为 keyboard 按钮",
|
|
105
|
+
defaultSelected: true
|
|
106
|
+
}),
|
|
107
|
+
components.radio.group("event:type", {
|
|
108
|
+
label: "事件接收方式",
|
|
109
|
+
description: "选择 QQ 官方推送事件的方式",
|
|
110
|
+
defaultValue: "2",
|
|
111
|
+
radio: [
|
|
112
|
+
components.radio.create("0", {
|
|
113
|
+
label: "关闭",
|
|
114
|
+
value: "0",
|
|
115
|
+
description: "不接收任何事件"
|
|
116
|
+
}),
|
|
117
|
+
components.radio.create("1", {
|
|
118
|
+
label: "Webhook",
|
|
119
|
+
value: "1",
|
|
120
|
+
description: "QQ 官方主动推送事件到本机"
|
|
121
|
+
}),
|
|
122
|
+
components.radio.create("2", {
|
|
123
|
+
label: "WebSocket",
|
|
124
|
+
value: "2",
|
|
125
|
+
description: "主动连接 QQ 官方 WebSocket 网关接收事件(推荐)"
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
})];
|
|
132
|
+
},
|
|
133
|
+
save: (input) => {
|
|
134
|
+
if (!Array.isArray(input.qqbot)) return {
|
|
135
|
+
success: false,
|
|
136
|
+
message: "保存失败:配置格式错误"
|
|
137
|
+
};
|
|
138
|
+
const data = input.qqbot.map((item) => {
|
|
139
|
+
const guildMode = item.guildMode === true ? 1 : 0;
|
|
140
|
+
const eventType = Number(item["event:type"] ?? 0);
|
|
141
|
+
const regex = (item.regex || []).map((str) => {
|
|
142
|
+
const parts = str.split(" ");
|
|
143
|
+
return {
|
|
144
|
+
reg: parts[0]?.replace(/^<|>$/g, "") || "",
|
|
145
|
+
rep: parts[1]?.replace(/^<|>$/g, "") || ""
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
name: item.name || "",
|
|
150
|
+
appId: item.appId || "",
|
|
151
|
+
secret: item.secret || "",
|
|
152
|
+
prodApi: item.prodApi || "https://api.sgroup.qq.com",
|
|
153
|
+
sandboxApi: item.sandboxApi || "https://sandbox.api.sgroup.qq.com",
|
|
154
|
+
tokenApi: item.tokenApi || "https://bots.qq.com/app/getAppAccessToken",
|
|
155
|
+
sandbox: !!item.sandbox,
|
|
156
|
+
qqEnable: !!item.qqEnable,
|
|
157
|
+
guildEnable: !!item.guildEnable,
|
|
158
|
+
guildMode,
|
|
159
|
+
regex,
|
|
160
|
+
keyboard: { enable: item["keyboard:enable"] !== false },
|
|
161
|
+
event: { type: eventType }
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
writeConfig(data);
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
message: "保存成功"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
export { web_config_default as default };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karinjs/adapter-qqbot",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "karin
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "karin adapter for QQ Official Bot (2.0)",
|
|
5
5
|
"homepage": "https://github.com/KarinJS/karin-plugin-adapter-qqbot",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/KarinJS/karin-plugin-adapter-qqbot/issues"
|
|
@@ -12,41 +12,52 @@
|
|
|
12
12
|
},
|
|
13
13
|
"author": "shijin",
|
|
14
14
|
"type": "module",
|
|
15
|
-
"main": "
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
16
17
|
"files": [
|
|
17
|
-
"
|
|
18
|
-
"/lib/**/*.d.ts",
|
|
19
|
-
"config",
|
|
20
|
-
"resources",
|
|
21
|
-
"LICENSE",
|
|
22
|
-
"package.json",
|
|
23
|
-
"README.md"
|
|
18
|
+
"dist"
|
|
24
19
|
],
|
|
25
20
|
"scripts": {
|
|
26
|
-
"build": "tsc &&
|
|
27
|
-
"
|
|
21
|
+
"build": "tsc && tsdown",
|
|
22
|
+
"dev": "tsx watch --include \"src/**/*.ts\" src/app.ts",
|
|
23
|
+
"karin": "karin",
|
|
24
|
+
"lint": "eslint src",
|
|
25
|
+
"lint:fix": "eslint src --fix",
|
|
28
26
|
"pub": "npm publish --access public",
|
|
29
|
-
"
|
|
30
|
-
"sort": "npx sort-package-json",
|
|
31
|
-
"dev": "tsx src/index.ts",
|
|
32
|
-
"karin": "karin"
|
|
27
|
+
"sort": "npx sort-package-json"
|
|
33
28
|
},
|
|
34
29
|
"dependencies": {
|
|
35
30
|
"@karinjs/geturls": "^1.0.2",
|
|
36
|
-
"form-data": "^4.0.
|
|
37
|
-
"image-size": "^
|
|
31
|
+
"form-data": "^4.0.5",
|
|
32
|
+
"image-size": "^2.0.2",
|
|
38
33
|
"qrcode": "^1.5.4",
|
|
39
|
-
"silk-wasm": "^3.
|
|
34
|
+
"silk-wasm": "^3.7.1",
|
|
40
35
|
"tweetnacl": "^1.0.3"
|
|
41
36
|
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/express": "5.0.6",
|
|
39
|
+
"@types/node": "^24.12.4",
|
|
40
|
+
"@types/qrcode": "^1.5.6",
|
|
41
|
+
"eslint": "^9.39.4",
|
|
42
|
+
"neostandard": "^0.13.0",
|
|
43
|
+
"node-karin": "^1.15.5",
|
|
44
|
+
"tsdown": "^0.22.0",
|
|
45
|
+
"tsx": "^4.22.0",
|
|
46
|
+
"typescript": "^6.0.3"
|
|
47
|
+
},
|
|
42
48
|
"publishConfig": {
|
|
43
49
|
"access": "public",
|
|
44
50
|
"registry": "https://registry.npmjs.org"
|
|
45
51
|
},
|
|
52
|
+
"pnpm": {
|
|
53
|
+
"overrides": {
|
|
54
|
+
"yaml@>=2.0.0 <2.8.3": "^2.8.3"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
46
57
|
"karin": {
|
|
47
58
|
"main": "src/index.ts",
|
|
48
59
|
"apps": [
|
|
49
|
-
"
|
|
60
|
+
"dist/apps"
|
|
50
61
|
],
|
|
51
62
|
"ts-apps": [
|
|
52
63
|
"src/apps"
|
|
@@ -58,6 +69,8 @@
|
|
|
58
69
|
"config",
|
|
59
70
|
"data",
|
|
60
71
|
"resources"
|
|
61
|
-
]
|
|
72
|
+
],
|
|
73
|
+
"ts-web": "src/web.config.ts",
|
|
74
|
+
"web": "dist/web.config.js"
|
|
62
75
|
}
|
|
63
76
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# 全局默认配置 以下所有配置均可在账号配置中单独配置
|
|
2
|
-
default:
|
|
3
|
-
# 机器人ID
|
|
4
|
-
appId: string
|
|
5
|
-
# 机器人密钥
|
|
6
|
-
secret: string
|
|
7
|
-
# 正式环境api
|
|
8
|
-
prodApi: https://api.sgroup.qq.com
|
|
9
|
-
# 沙盒api
|
|
10
|
-
sandboxApi: https://sandbox.api.sgroup.qq.com
|
|
11
|
-
# 调用凭证的Api
|
|
12
|
-
tokenApi: https://bots.qq.com/app/getAppAccessToken
|
|
13
|
-
# 是否开启沙盒环境
|
|
14
|
-
sandbox: false
|
|
15
|
-
# 开启QQ场景能力
|
|
16
|
-
qqEnable: true
|
|
17
|
-
# 开启频道场景能力
|
|
18
|
-
guildEnable: true
|
|
19
|
-
# 频道场景模式 0-公域 1-私域
|
|
20
|
-
guildMode: 0
|
|
21
|
-
# 文本中的url转二维码白名单 配置后将不转换这些url为二维码
|
|
22
|
-
exclude: []
|
|
23
|
-
# 接受到消息后对文本进行表达式处理
|
|
24
|
-
regex: [
|
|
25
|
-
{
|
|
26
|
-
# 表达式
|
|
27
|
-
reg: "^#",
|
|
28
|
-
# 替换为
|
|
29
|
-
rep: "",
|
|
30
|
-
},
|
|
31
|
-
]
|
|
32
|
-
# markdown配置
|
|
33
|
-
markdown:
|
|
34
|
-
# 发送模式 0-关闭MD 1-原生MD 3-旧图文模板MD 4-纯文模板MD 5-自定义处理
|
|
35
|
-
mode: 0
|
|
36
|
-
# 模板ID
|
|
37
|
-
id: ""
|
|
38
|
-
kv:
|
|
39
|
-
- text_start
|
|
40
|
-
- img_dec
|
|
41
|
-
- img_url
|
|
42
|
-
- text_end
|
|
43
|
-
|
|
44
|
-
# 事件接收配置
|
|
45
|
-
event:
|
|
46
|
-
# 接收方式 0-关闭 1-webhook 2-http 3-ws
|
|
47
|
-
type: 0
|
|
48
|
-
# ws服务器地址
|
|
49
|
-
wsUrl: ws://example.com/webhook
|
|
50
|
-
# ws服务器token
|
|
51
|
-
wsToken: your_token
|
|
52
|
-
# http鉴权token
|
|
53
|
-
httpToken: your_token
|
package/lib/cli/pr.d.ts
DELETED
package/lib/cli/pr.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
/**
|
|
4
|
-
* @description 获取package.json路径
|
|
5
|
-
*/
|
|
6
|
-
const getPkgPath = () => process.cwd() + '/package.json';
|
|
7
|
-
/**
|
|
8
|
-
* @description 读取package.json
|
|
9
|
-
*/
|
|
10
|
-
const readPkg = () => JSON.parse(fs.readFileSync(getPkgPath(), 'utf-8'));
|
|
11
|
-
/**
|
|
12
|
-
* @description 写入package.json
|
|
13
|
-
* @param pkg package.json
|
|
14
|
-
*/
|
|
15
|
-
const writePkg = (pkg) => fs.writeFileSync(getPkgPath(), JSON.stringify(pkg, null, 2));
|
|
16
|
-
/**
|
|
17
|
-
* @description 构建pr版本号 <主版本号>.<次版本号>.<修订号>-<预发布标识>.<PR标识>.<PR编号>.<工作流唯一编号>-<时间戳>
|
|
18
|
-
|
|
19
|
-
* @param pkg package.json
|
|
20
|
-
*/
|
|
21
|
-
const updateVersion = (pkg) => {
|
|
22
|
-
const list = pkg.version.split('.');
|
|
23
|
-
// 1.0.0 => 1.0.1-beta.pr.pr编号.工作流唯一编号-时间戳 => 1.0.1-beta.pr.184-1631537630
|
|
24
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
25
|
-
list[2] = `${Number(list[2]) + 1}-beta.pr.${process.env.PR_NUMBER}.${process.env.GITHUB_RUN_ATTEMPT || 1}-${timestamp}`;
|
|
26
|
-
pkg.version = list.join('.');
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* @description 设置环境变量
|
|
30
|
-
* @param pkg package.json
|
|
31
|
-
*/
|
|
32
|
-
const setEnvVariables = (pkg) => {
|
|
33
|
-
fs.appendFileSync(process.env.GITHUB_ENV, `PKG_NAME=${pkg.name}\nPKG_VERSION=${pkg.version}\n`);
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* @description 更新版本号并设置环境变量
|
|
37
|
-
*/
|
|
38
|
-
const version = () => {
|
|
39
|
-
const pkg = readPkg();
|
|
40
|
-
updateVersion(pkg);
|
|
41
|
-
writePkg(pkg);
|
|
42
|
-
setEnvVariables(pkg);
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* @description 删除devDependencies和peerDependencies
|
|
46
|
-
*/
|
|
47
|
-
const clean = () => {
|
|
48
|
-
const pkg = readPkg();
|
|
49
|
-
delete pkg.devDependencies;
|
|
50
|
-
delete pkg.peerDependencies;
|
|
51
|
-
writePkg(pkg);
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* @description 执行所有操作
|
|
55
|
-
*/
|
|
56
|
-
const all = () => {
|
|
57
|
-
const pkg = readPkg();
|
|
58
|
-
updateVersion(pkg);
|
|
59
|
-
delete pkg.devDependencies;
|
|
60
|
-
delete pkg.peerDependencies;
|
|
61
|
-
writePkg(pkg);
|
|
62
|
-
setEnvVariables(pkg);
|
|
63
|
-
};
|
|
64
|
-
const cmd = process.argv[2];
|
|
65
|
-
if (cmd === 'version') {
|
|
66
|
-
version();
|
|
67
|
-
}
|
|
68
|
-
else if (cmd === 'clean') {
|
|
69
|
-
clean();
|
|
70
|
-
}
|
|
71
|
-
else if (cmd === 'all') {
|
|
72
|
-
all();
|
|
73
|
-
}
|