@karinjs/adapter-qqbot 2.0.0 → 2.2.1
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 +112 -255
- package/dist/apps/cleanup.d.ts +5 -0
- package/dist/apps/cleanup.js +22 -0
- package/dist/apps/login.d.ts +1 -3
- package/dist/apps/login.js +65 -16
- package/dist/common-by1GpV0b.js +49 -0
- package/dist/config-B_Gc9JKZ.js +253 -0
- package/dist/index.d.ts +511 -25
- package/dist/index.js +2327 -242
- package/dist/message-Dt4QkDYn.js +1739 -0
- package/dist/{onboard-BX8g8Wik.js → onboard-BXQ1cG8h.js} +10 -7
- package/dist/utils-DIq0lGMt.js +4 -0
- package/dist/web.config.d.ts +21 -19
- package/dist/web.config.js +232 -88
- package/package.json +6 -11
- package/dist/config-H7LrvlNU.js +0 -197
- package/dist/utils-DO4gEkVN.js +0 -3
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { common, karinPathBase, logger, requireFileSync, watch } from "node-karin";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/root.ts
|
|
7
|
+
/** 当前文件的绝对路径 */
|
|
8
|
+
const filePath = fileURLToPath(import.meta.url);
|
|
9
|
+
/** 插件包绝对路径 */
|
|
10
|
+
const dirPath = path.resolve(filePath, "../..");
|
|
11
|
+
/** 插件包的名称 */
|
|
12
|
+
const basename = path.basename(dirPath);
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/utils/logger.ts
|
|
16
|
+
const PREFIX = "[QQ Official Bot]";
|
|
17
|
+
const m = {
|
|
18
|
+
trace: (...a) => logger.trace(PREFIX, ...a),
|
|
19
|
+
debug: (...a) => logger.debug(PREFIX, ...a),
|
|
20
|
+
info: (...a) => logger.info(PREFIX, ...a),
|
|
21
|
+
warn: (...a) => logger.warn(PREFIX, ...a),
|
|
22
|
+
error: (...a) => logger.error(PREFIX, ...a),
|
|
23
|
+
fatal: (...a) => logger.fatal(PREFIX, ...a),
|
|
24
|
+
mark: (...a) => logger.mark(PREFIX, ...a)
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* QQBot 日志函数
|
|
28
|
+
* - log('info', '消息')
|
|
29
|
+
* - log.info('消息')
|
|
30
|
+
*
|
|
31
|
+
* 日志级别由 karin 框架的 log.level 控制
|
|
32
|
+
*/
|
|
33
|
+
function log(level, ...args) {
|
|
34
|
+
m[level](...args);
|
|
35
|
+
}
|
|
36
|
+
Object.assign(log, m);
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils/plugin.ts
|
|
40
|
+
/**
|
|
41
|
+
* 读取当前插件的 package.json。
|
|
42
|
+
*
|
|
43
|
+
* @returns package.json 内容。
|
|
44
|
+
*/
|
|
45
|
+
const pkg = () => requireFileSync(`${dirPath}/package.json`);
|
|
46
|
+
/** 当前插件 npm 包名,如 @karinjs/adapter-qqbot。 */
|
|
47
|
+
const pluginName = pkg().name;
|
|
48
|
+
/** 文件系统目录名:把 npm 包名里的 `/` 规范化为 `-`,与 Karin 框架对齐。 */
|
|
49
|
+
const pluginDirName = pluginName.replace(/\//g, "-");
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/utils/proxy-url.ts
|
|
53
|
+
/**
|
|
54
|
+
* HTTP / token 地址只清理首尾空白。完整接口地址是否带尾斜杠可能有路由含义,不能替用户改。
|
|
55
|
+
*/
|
|
56
|
+
const normalizeHttpUrl = (raw) => raw.trim();
|
|
57
|
+
/**
|
|
58
|
+
* 拼接 OpenAPI 路径时再处理斜杠,避免配置值本身被改写。
|
|
59
|
+
*/
|
|
60
|
+
const joinHttpPath = (base, path) => {
|
|
61
|
+
return `${normalizeHttpUrl(base).replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* WebSocket 网关地址:
|
|
65
|
+
* - ws:// / wss:// 原样保留协议
|
|
66
|
+
* - http:// / https:// 映射为 ws:// / wss://
|
|
67
|
+
* - 未写协议时按 wss:// 处理
|
|
68
|
+
*
|
|
69
|
+
* 这里只做最小处理,不改 path / query / hash。自定义网关的尾斜杠和查询参数都可能有路由含义。
|
|
70
|
+
*/
|
|
71
|
+
const normalizeWsGatewayUrl = (raw) => {
|
|
72
|
+
const value = raw.trim();
|
|
73
|
+
if (!value) return "";
|
|
74
|
+
let normalized = value;
|
|
75
|
+
const scheme = value.match(/^([a-z][a-z\d+.-]*):\/\//i)?.[1]?.toLowerCase();
|
|
76
|
+
if (scheme === "http" || scheme === "https") normalized = value.replace(/^http/i, "ws");
|
|
77
|
+
else if (scheme && scheme !== "ws" && scheme !== "wss") throw new Error(`unsupported websocket protocol: ${scheme}:`);
|
|
78
|
+
else if (!scheme) normalized = `wss://${value}`;
|
|
79
|
+
const parsed = new URL(normalized);
|
|
80
|
+
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new Error(`unsupported websocket protocol: ${parsed.protocol}`);
|
|
81
|
+
return normalized;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* 只有从 OpenAPI 根地址推导官方 fallback 网关时,才主动拼 `/websocket/`。
|
|
85
|
+
*/
|
|
86
|
+
const buildFallbackWsUrlFromApi = (apiUrl) => {
|
|
87
|
+
const parsed = new URL(normalizeWsGatewayUrl(apiUrl));
|
|
88
|
+
parsed.pathname = `${parsed.pathname.replace(/\/+$/, "")}/websocket/`;
|
|
89
|
+
parsed.search = "";
|
|
90
|
+
parsed.hash = "";
|
|
91
|
+
return parsed.toString();
|
|
92
|
+
};
|
|
93
|
+
const normalizeProxyConfig = (proxy) => ({
|
|
94
|
+
prodApi: normalizeHttpUrl(proxy.prodApi),
|
|
95
|
+
sandboxApi: normalizeHttpUrl(proxy.sandboxApi),
|
|
96
|
+
tokenApi: normalizeHttpUrl(proxy.tokenApi),
|
|
97
|
+
prodWs: normalizeWsGatewayUrl(proxy.prodWs),
|
|
98
|
+
sandboxWs: normalizeWsGatewayUrl(proxy.sandboxWs)
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/utils/config.ts
|
|
103
|
+
let cache;
|
|
104
|
+
const cacheMap = {};
|
|
105
|
+
/** 延迟注入,避免与 core/index.ts 循环依赖 */
|
|
106
|
+
let _createBot;
|
|
107
|
+
let _destroyBot;
|
|
108
|
+
const bindHandlers = (createBot, destroyBot) => {
|
|
109
|
+
_createBot = createBot;
|
|
110
|
+
_destroyBot = destroyBot;
|
|
111
|
+
};
|
|
112
|
+
/** 配置目录:${karinPathBase}/@karinjs-adapter-qqbot/config */
|
|
113
|
+
const dirConfig = `${karinPathBase}/${pluginDirName}/config`;
|
|
114
|
+
const configPath = `${dirConfig}/config.json`;
|
|
115
|
+
const syncCache = (data) => {
|
|
116
|
+
cache = data;
|
|
117
|
+
Object.keys(cacheMap).forEach((key) => delete cacheMap[key]);
|
|
118
|
+
data.forEach((v) => {
|
|
119
|
+
cacheMap[v.appId] = v;
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* 读取配置
|
|
124
|
+
*/
|
|
125
|
+
const config = () => {
|
|
126
|
+
if (!fs.existsSync(configPath)) {
|
|
127
|
+
fs.mkdirSync(dirConfig, { recursive: true });
|
|
128
|
+
fs.writeFileSync(configPath, JSON.stringify([], null, 2));
|
|
129
|
+
}
|
|
130
|
+
if (cache) return cache;
|
|
131
|
+
const result = formatConfig(requireFileSync(configPath));
|
|
132
|
+
syncCache(result);
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* 直接从磁盘读取配置,不使用内存缓存。
|
|
137
|
+
*/
|
|
138
|
+
const readConfigFile = () => {
|
|
139
|
+
if (!fs.existsSync(configPath)) return config();
|
|
140
|
+
return formatConfig(requireFileSync(configPath));
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* 获取单 bot 配置
|
|
144
|
+
*/
|
|
145
|
+
const getConfig = (appid) => cacheMap[appid];
|
|
146
|
+
/**
|
|
147
|
+
* 写入配置
|
|
148
|
+
*/
|
|
149
|
+
const writeConfig = (data) => {
|
|
150
|
+
fs.mkdirSync(dirConfig, { recursive: true });
|
|
151
|
+
const normalized = formatConfig(data);
|
|
152
|
+
fs.writeFileSync(configPath, JSON.stringify(normalized, null, 2));
|
|
153
|
+
syncCache(normalized);
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* 用默认值补齐用户配置
|
|
157
|
+
*/
|
|
158
|
+
const formatConfig = (user) => {
|
|
159
|
+
const def = getDefaultConfig()[0];
|
|
160
|
+
return user.map((item) => {
|
|
161
|
+
const { prodApi, sandboxApi, tokenApi, wsUrl, proxy, keyboard, markdown, messageCache, event, ...rest } = item;
|
|
162
|
+
return {
|
|
163
|
+
...def,
|
|
164
|
+
...rest,
|
|
165
|
+
proxy: normalizeProxyConfig({
|
|
166
|
+
...def.proxy,
|
|
167
|
+
...proxy,
|
|
168
|
+
prodApi: proxy?.prodApi || prodApi || def.proxy.prodApi,
|
|
169
|
+
sandboxApi: proxy?.sandboxApi || sandboxApi || def.proxy.sandboxApi,
|
|
170
|
+
tokenApi: proxy?.tokenApi || tokenApi || def.proxy.tokenApi,
|
|
171
|
+
prodWs: proxy?.prodWs || proxy?.wsUrl || wsUrl || def.proxy.prodWs,
|
|
172
|
+
sandboxWs: proxy?.sandboxWs || proxy?.wsUrl || wsUrl || def.proxy.sandboxWs
|
|
173
|
+
}),
|
|
174
|
+
keyboard: {
|
|
175
|
+
...def.keyboard,
|
|
176
|
+
...keyboard
|
|
177
|
+
},
|
|
178
|
+
markdown: {
|
|
179
|
+
...def.markdown,
|
|
180
|
+
...markdown
|
|
181
|
+
},
|
|
182
|
+
messageCache: {
|
|
183
|
+
...def.messageCache,
|
|
184
|
+
...messageCache
|
|
185
|
+
},
|
|
186
|
+
event: {
|
|
187
|
+
...def.event,
|
|
188
|
+
...event
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* 默认配置
|
|
195
|
+
*/
|
|
196
|
+
const getDefaultConfig = () => [{
|
|
197
|
+
name: "",
|
|
198
|
+
appId: "",
|
|
199
|
+
secret: "",
|
|
200
|
+
proxy: {
|
|
201
|
+
prodApi: "https://api.sgroup.qq.com",
|
|
202
|
+
sandboxApi: "https://sandbox.api.sgroup.qq.com",
|
|
203
|
+
tokenApi: "https://bots.qq.com/app/getAppAccessToken",
|
|
204
|
+
prodWs: "wss://api.sgroup.qq.com/websocket/",
|
|
205
|
+
sandboxWs: "wss://sandbox.api.sgroup.qq.com/websocket/"
|
|
206
|
+
},
|
|
207
|
+
sandbox: false,
|
|
208
|
+
qqEnable: true,
|
|
209
|
+
guildEnable: true,
|
|
210
|
+
guildMode: 0,
|
|
211
|
+
regex: [{
|
|
212
|
+
reg: "^/",
|
|
213
|
+
rep: "#"
|
|
214
|
+
}],
|
|
215
|
+
keyboard: { enable: true },
|
|
216
|
+
markdown: { enable: true },
|
|
217
|
+
messageCache: {
|
|
218
|
+
enable: false,
|
|
219
|
+
self: false
|
|
220
|
+
},
|
|
221
|
+
event: { type: 2 }
|
|
222
|
+
}];
|
|
223
|
+
/**
|
|
224
|
+
* 监听配置文件,比较前后差异决定重建 / 销毁 bot
|
|
225
|
+
*/
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
watch(`${dirConfig}/config.json`, (old, now) => {
|
|
228
|
+
const oldConfig = formatConfig(old);
|
|
229
|
+
const nowConfig = formatConfig(now);
|
|
230
|
+
syncCache(nowConfig);
|
|
231
|
+
const diff = common.diffArray(oldConfig, nowConfig);
|
|
232
|
+
diff.removed.forEach((v) => _destroyBot?.(v.appId));
|
|
233
|
+
if (diff.added.length > 0) {
|
|
234
|
+
const ids = new Set(diff.added.map((v) => v.appId));
|
|
235
|
+
nowConfig.forEach((v) => {
|
|
236
|
+
if (!ids.has(v.appId)) return;
|
|
237
|
+
if (v.event.type === 0) return;
|
|
238
|
+
_createBot?.(v);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
diff.common.forEach((curr) => {
|
|
242
|
+
const prev = oldConfig.find((c) => c.appId === curr.appId);
|
|
243
|
+
if (!prev) return;
|
|
244
|
+
if (JSON.stringify(prev) === JSON.stringify(curr)) return;
|
|
245
|
+
logger.info(`[QQ Official Bot][配置监听] 配置已变更: ${curr.appId},重新初始化`);
|
|
246
|
+
_destroyBot?.(prev.appId);
|
|
247
|
+
if (curr.event.type !== 0) _createBot?.(curr);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}, 2e3);
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
export { readConfigFile as a, joinHttpPath as c, normalizeWsGatewayUrl as d, pkg as f, basename as h, getDefaultConfig as i, normalizeHttpUrl as l, log as m, config as n, writeConfig as o, pluginDirName as p, getConfig as r, buildFallbackWsUrlFromApi as s, bindHandlers as t, normalizeProxyConfig as u };
|