@karinjs/adapter-qqbot 2.1.0 → 3.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.
@@ -1,4 +1,4 @@
1
- import { a as writeConfig, c as log, i as getDefaultConfig, n as config } from "./config-DKa7596f.js";
1
+ import { i as getDefaultConfig, m as log, n as config, o as writeConfig } from "./config-BUuU6FnQ.js";
2
2
  import { a as sleep } from "./common-by1GpV0b.js";
3
3
  import qrcode from "qrcode";
4
4
  import axios from "node-karin/axios";
@@ -0,0 +1,4 @@
1
+ import "./config-BUuU6FnQ.js";
2
+ import "./common-by1GpV0b.js";
3
+
4
+ export { };
@@ -1,22 +1,27 @@
1
1
  //#region src/web.config.d.ts
2
- declare const _default: import("node-karin").DefineConfig<{
3
- qqbot: Array<{
4
- name: string;
5
- appId: string;
6
- secret: string;
7
- prodApi: string;
8
- sandboxApi: string;
9
- tokenApi: string;
10
- sandbox: boolean;
11
- qqEnable: boolean;
12
- guildEnable: boolean;
13
- guildMode: boolean;
14
- regex: string[];
15
- "keyboard:enable": boolean;
16
- "messageCache:enable": boolean;
17
- "messageCache:self": boolean;
18
- "event:type": string;
19
- }>;
20
- }>;
2
+ type WebQQBotInput = {
3
+ name: unknown;
4
+ appId: unknown;
5
+ secret: unknown;
6
+ 'proxy:api': unknown;
7
+ 'proxy:ws': unknown;
8
+ sandbox: unknown;
9
+ qqEnable: unknown;
10
+ guildEnable: unknown;
11
+ guildMode: unknown;
12
+ regex: unknown;
13
+ 'keyboard:enable': unknown;
14
+ 'markdown:enable': unknown;
15
+ 'messageCache:enable': unknown;
16
+ 'messageCache:self': unknown;
17
+ 'messageCache:level': unknown;
18
+ 'messageCache:ttlHours': unknown;
19
+ 'messageCache:maxRows': unknown;
20
+ 'event:type': unknown;
21
+ };
22
+ type WebConfigInput = {
23
+ qqbot: WebQQBotInput[];
24
+ };
25
+ declare const _default: import("node-karin").DefineComponentConfig<WebConfigInput>;
21
26
  //#endregion
22
27
  export { _default as default };
@@ -1,8 +1,92 @@
1
- import { a as writeConfig, n as config, o as pkg } from "./config-DKa7596f.js";
2
- import "./utils-BPbnNAix.js";
1
+ import { a as readConfigFile, f as pkg, i as getDefaultConfig, n as config, o as writeConfig, s as buildFallbackWsUrlFromApi, u as normalizeProxyConfig } from "./config-BUuU6FnQ.js";
2
+ import "./utils-N9PNiHjE.js";
3
3
  import { components, defineConfig } from "node-karin";
4
4
 
5
5
  //#region src/web.config.ts
6
+ const defaultProxy = () => getDefaultConfig()[0].proxy;
7
+ const unwrapValue = (value) => {
8
+ if (value && typeof value === "object" && "value" in value) return value.value;
9
+ return value;
10
+ };
11
+ const toBool = (value, fallback) => {
12
+ const raw = unwrapValue(value);
13
+ if (typeof raw === "boolean") return raw;
14
+ if (typeof raw === "number") return raw !== 0;
15
+ if (typeof raw === "string") {
16
+ const normalized = raw.trim().toLowerCase();
17
+ if ([
18
+ "false",
19
+ "0",
20
+ "off",
21
+ "no",
22
+ ""
23
+ ].includes(normalized)) return false;
24
+ if ([
25
+ "true",
26
+ "1",
27
+ "on",
28
+ "yes"
29
+ ].includes(normalized)) return true;
30
+ }
31
+ if (raw === null || raw === void 0) return fallback;
32
+ return fallback;
33
+ };
34
+ const toStringValue = (value, fallback = "") => {
35
+ const raw = unwrapValue(value);
36
+ if (typeof raw === "string") return raw;
37
+ if (raw === null || raw === void 0) return fallback;
38
+ return String(raw);
39
+ };
40
+ const toStringList = (value) => {
41
+ const raw = unwrapValue(value);
42
+ if (!Array.isArray(raw)) return [];
43
+ return raw.map((item) => toStringValue(item)).filter(Boolean);
44
+ };
45
+ const proxyKeys = (sandbox) => sandbox ? {
46
+ api: "sandboxApi",
47
+ ws: "sandboxWs"
48
+ } : {
49
+ api: "prodApi",
50
+ ws: "prodWs"
51
+ };
52
+ const getBotProxyInput = (item) => {
53
+ const keys = proxyKeys(!!item.sandbox);
54
+ const proxy = item.proxy || defaultProxy();
55
+ return {
56
+ "proxy:api": proxy[keys.api] || defaultProxy()[keys.api],
57
+ "proxy:ws": proxy[keys.ws] || defaultProxy()[keys.ws]
58
+ };
59
+ };
60
+ const proxyFromInput = (item, previous) => {
61
+ const def = defaultProxy();
62
+ const sandbox = toBool(item.sandbox, previous?.sandbox ?? false);
63
+ const previousSandbox = previous?.sandbox ?? sandbox;
64
+ const keys = proxyKeys(sandbox);
65
+ const previousVisibleKeys = proxyKeys(previousSandbox);
66
+ const previousProxy = previous?.proxy;
67
+ const base = {
68
+ ...def,
69
+ ...previousProxy
70
+ };
71
+ const envChanged = !!previous && previousSandbox !== sandbox;
72
+ const rawApi = toStringValue(item["proxy:api"]).trim();
73
+ const rawWs = toStringValue(item["proxy:ws"]).trim();
74
+ const previousVisibleApi = previousProxy?.[previousVisibleKeys.api] || def[previousVisibleKeys.api];
75
+ const previousVisibleWs = previousProxy?.[previousVisibleKeys.ws] || def[previousVisibleKeys.ws];
76
+ const api = envChanged && rawApi === previousVisibleApi ? base[keys.api] || def[keys.api] : rawApi || base[keys.api] || def[keys.api];
77
+ const previousApi = previousProxy?.[keys.api] || def[keys.api];
78
+ const previousWs = previousProxy?.[keys.ws] || def[keys.ws];
79
+ const wsInput = envChanged && rawWs === previousVisibleWs ? base[keys.ws] || def[keys.ws] : rawWs;
80
+ let ws = wsInput || previousWs || def[keys.ws];
81
+ const previousAutoWs = buildFallbackWsUrlFromApi(previousApi);
82
+ if (!wsInput || wsInput === previousWs && previousWs === previousAutoWs && api !== previousApi) ws = buildFallbackWsUrlFromApi(api);
83
+ return normalizeProxyConfig({
84
+ ...base,
85
+ [keys.api]: api,
86
+ tokenApi: base.tokenApi || def.tokenApi,
87
+ [keys.ws]: ws
88
+ });
89
+ };
6
90
  var web_config_default = defineConfig({
7
91
  info: {
8
92
  id: pkg().name,
@@ -23,120 +107,173 @@ var web_config_default = defineConfig({
23
107
  name: item.name,
24
108
  appId: item.appId,
25
109
  secret: item.secret,
26
- prodApi: item.prodApi,
27
- sandboxApi: item.sandboxApi,
28
- tokenApi: item.tokenApi,
110
+ "event:type": String(item.event?.type ?? 2),
29
111
  sandbox: item.sandbox,
30
112
  qqEnable: item.qqEnable,
31
113
  guildEnable: item.guildEnable,
32
114
  guildMode: item.guildMode === 1,
33
115
  regex: item.regex.map((r) => `${r.reg} ${r.rep}`),
34
116
  "keyboard:enable": item.keyboard?.enable !== false,
117
+ "markdown:enable": item.markdown?.enable !== false,
35
118
  "messageCache:enable": item.messageCache?.enable === true,
36
119
  "messageCache:self": item.messageCache?.self === true,
37
- "event:type": String(item.event?.type ?? 2)
120
+ "messageCache:level": item.messageCache?.level || "standard",
121
+ "messageCache:ttlHours": String(item.messageCache?.ttlHours ?? 24),
122
+ "messageCache:maxRows": String(item.messageCache?.maxRows ?? 2e5),
123
+ ...getBotProxyInput(item)
38
124
  });
39
125
  });
40
- return [components.accordionPro.create("qqbot", data, {
41
- label: "QQBot",
42
- description: "QQBot 配置",
126
+ return [components.divider.horizontal("bot-section", {
127
+ description: "机器人账号",
128
+ descPosition: 5
129
+ }), components.accordionPro.create("qqbot", data, {
130
+ label: "QQ 官方机器人列表",
131
+ description: "每一项对应一个 QQ 官方机器人账号。新增机器人时先填 AppID 和 Secret,再按需调整接收方式、场景能力和发送能力。",
43
132
  children: {
44
133
  key: "qqbotConfig",
45
134
  children: [
135
+ components.radio.group("event:type", {
136
+ label: "事件接收方式",
137
+ description: "QQ 官方机器人只有 Webhook 推送和 WebSocket 主动连接两种接收方式。不确定时选择 WebSocket;如果 QQ 后台已启用 Webhook,可在下方高级设置填写第三方 Webhook 转 WebSocket 服务地址。",
138
+ defaultValue: "2",
139
+ radio: [
140
+ components.radio.create("0", {
141
+ label: "关闭接收",
142
+ value: "0",
143
+ description: "停用这个机器人,不连接 WebSocket,也不接收 Webhook 事件。"
144
+ }),
145
+ components.radio.create("1", {
146
+ label: "Webhook 推送",
147
+ value: "1",
148
+ description: "由 QQ 官方把事件推送到 Karin 的 /qqbot/webhook 路由。通常需要公网地址,并在 QQ 后台配置回调地址。"
149
+ }),
150
+ components.radio.create("2", {
151
+ label: "WebSocket 主动连接",
152
+ value: "2",
153
+ description: "适配器主动连接 WebSocket 地址接收事件。默认连接 QQ 官方网关;如果 QQ 后台已开 Webhook,可改为第三方 Webhook 转 WebSocket 服务地址。"
154
+ })
155
+ ]
156
+ }),
157
+ components.divider.horizontal("bot-basic-section", {
158
+ description: "基础信息",
159
+ descPosition: 5
160
+ }),
46
161
  components.input.create("name", {
47
- label: "机器人名称",
48
- description: "机器人名称,扫码登录会自动获取"
162
+ label: "机器人显示名称",
163
+ description: "只用于 Karin 配置页和日志里识别这个机器人,不影响 QQ 平台资料。扫码登录写入配置时通常会自动填充。"
49
164
  }),
50
165
  components.input.create("appId", {
51
- label: "AppID",
52
- description: "机器人 AppID",
166
+ label: "机器人 AppID",
167
+ description: "QQ 开放平台机器人应用的 AppID,是识别机器人账号的唯一编号。填错会导致鉴权和收发消息失败。",
53
168
  isRequired: true
54
169
  }),
55
170
  components.input.create("secret", {
56
- label: "Secret",
57
- description: "机器人 Secret",
171
+ label: "机器人 Secret",
172
+ description: "QQ 开放平台机器人应用的 Secret,用于获取 access token。请不要泄露;扫码授权会刷新 Secret,旧 Secret 会立即失效。",
58
173
  isRequired: true
59
174
  }),
60
- components.input.create("prodApi", {
61
- label: "正式环境 API",
62
- defaultValue: "https://api.sgroup.qq.com",
63
- isRequired: true
64
- }),
65
- components.input.create("sandboxApi", {
66
- label: "沙盒环境 API",
67
- defaultValue: "https://sandbox.api.sgroup.qq.com",
68
- isRequired: true
69
- }),
70
- components.input.create("tokenApi", {
71
- label: "AccessToken 接口",
72
- defaultValue: "https://bots.qq.com/app/getAppAccessToken",
73
- isRequired: true
175
+ components.divider.horizontal("bot-scene-section", {
176
+ description: "场景能力",
177
+ descPosition: 5
74
178
  }),
75
179
  components.switch.create("sandbox", {
76
- label: "沙盒环境",
77
- description: "启用后调用沙盒 API",
180
+ label: "使用沙盒环境",
181
+ description: "开启后这个机器人会使用沙盒环境的 OpenAPI 和 WebSocket 地址;关闭时使用正式环境地址。正式上线机器人请关闭。",
78
182
  defaultSelected: false
79
183
  }),
80
184
  components.switch.create("qqEnable", {
81
- label: "QQ 场景",
82
- description: "是否处理私聊和群聊消息",
185
+ label: "启用 QQ 私聊 / 群聊",
186
+ description: "开启后会处理 QQ 单聊和群聊里的消息事件;如果这个机器人只用于频道,可以关闭。",
83
187
  defaultSelected: true
84
188
  }),
85
189
  components.switch.create("guildEnable", {
86
- label: "频道场景",
87
- description: "是否处理频道消息",
190
+ label: "启用频道消息",
191
+ description: "开启后会处理 QQ 频道消息事件;如果这个机器人只用于私聊或群聊,可以关闭。",
88
192
  defaultSelected: true
89
193
  }),
90
194
  components.switch.create("guildMode", {
91
- label: "频道私域模式",
92
- description: "开启 = 私域机器人;关闭 = 公域机器人",
195
+ label: "频道私域机器人模式",
196
+ description: "关闭时按公域机器人处理,通常只接收 @ 机器人相关消息;开启时按私域机器人处理,可接收更多频道消息,需确认机器人权限匹配。",
93
197
  defaultSelected: false
94
198
  }),
199
+ components.divider.horizontal("bot-message-section", {
200
+ description: "消息发送",
201
+ descPosition: 5
202
+ }),
95
203
  components.input.group("regex", {
96
204
  data: [],
97
205
  label: "消息正则替换",
98
- description: "每行格式:正则表达式 替换内容。如:^/ #",
206
+ description: "收到文本消息后,先按这里的规则替换内容,再交给 Karin 插件处理。每行格式是“正则表达式 替换内容”,中间用空格分隔;例如“^/ #”会把“/菜单”改成“#菜单”。不懂正则可以保留默认或清空。",
99
207
  template: components.input.create("regex", {
100
- label: "正则规则",
208
+ label: "一条替换规则",
101
209
  placeholder: "^/ #"
102
210
  })
103
211
  }),
104
212
  components.switch.create("keyboard:enable", {
105
- label: "URL 自动按钮",
106
- description: "将文本中的链接自动转为 keyboard 按钮",
213
+ label: "把链接自动转成按钮",
214
+ description: "开启后,发送文本里出现的 http(s) 链接会尽量转成 QQ keyboard 按钮,用户可以直接点按钮打开链接。该能力依赖 Markdown 通道。",
107
215
  defaultSelected: true
108
216
  }),
217
+ components.switch.create("markdown:enable", {
218
+ label: "启用 Markdown 发送通道",
219
+ description: "开启后普通文本和图片优先合并为 QQ Markdown 消息,Markdown 里的图片会以内嵌图片形式发送;关闭后普通图片会按官方富媒体单独上传发送。插件显式传入的 segment.markdown 不受此开关影响。",
220
+ defaultSelected: true
221
+ }),
222
+ components.divider.horizontal("bot-cache-section", {
223
+ description: "消息缓存",
224
+ descPosition: 5
225
+ }),
109
226
  components.switch.create("messageCache:enable", {
110
- label: "启用数据库缓存消息",
111
- description: "用于 bot.getMsg 查询最近一天消息。开启后会将收到的消息写入本地数据库,并把接收到的富媒体保存到本地;高并发或富媒体消息量较大时不建议开启,可能快速占用磁盘空间甚至导致磁盘溢出。",
227
+ label: "缓存收到的消息到数据库",
228
+ description: "开启后会把最近一天收到的消息写入本地数据库,供 bot.getMsg 等查询使用;收到的图片、语音、视频等富媒体也会保存到本地。消息量大时会明显占用磁盘空间。",
112
229
  defaultSelected: false
113
230
  }),
114
231
  components.switch.create("messageCache:self", {
115
- label: "缓存自己消息",
116
- description: "仅在启用数据库缓存消息时生效,发送成功后缓存机器人自己的消息",
232
+ label: "同时缓存机器人自己发出的消息",
233
+ description: "只有开启“缓存收到的消息到数据库”后才生效。开启后,机器人发送成功的消息也会写入缓存,方便之后通过消息 ID 查询。",
117
234
  defaultSelected: false
118
235
  }),
119
- components.radio.group("event:type", {
120
- label: "事件接收方式",
121
- description: "选择 QQ 官方推送事件的方式",
122
- defaultValue: "2",
236
+ components.radio.group("messageCache:level", {
237
+ label: "缓存存储分级",
238
+ description: "控制每条消息落库的内容多少,直接影响数据库体积。撤回和引用解析在任何分级下都可用。",
239
+ defaultValue: "standard",
123
240
  radio: [
124
- components.radio.create("0", {
125
- label: "关闭",
126
- value: "0",
127
- description: "不接收任何事件"
241
+ components.radio.create("minimal", {
242
+ label: "最小",
243
+ value: "minimal",
244
+ description: "只记录消息 ID 映射与引用关系,不保存正文和媒体,体积最小。"
128
245
  }),
129
- components.radio.create("1", {
130
- label: "Webhook",
131
- value: "1",
132
- description: "QQ 官方主动推送事件到本机"
246
+ components.radio.create("standard", {
247
+ label: "标准(推荐)",
248
+ value: "standard",
249
+ description: "保存文本、表情和媒体本地文件,不保存 markdown 原文。"
133
250
  }),
134
- components.radio.create("2", {
135
- label: "WebSocket",
136
- value: "2",
137
- description: "主动连接 QQ 官方 WebSocket 网关接收事件(推荐)"
251
+ components.radio.create("full", {
252
+ label: "完整",
253
+ value: "full",
254
+ description: "所有支持的消息段原样保存,包含 markdown 原文,体积最大。"
138
255
  })
139
256
  ]
257
+ }),
258
+ components.input.create("messageCache:ttlHours", {
259
+ label: "缓存保留小时数",
260
+ description: "超过该时长的消息会被清理任务删除。范围 1~720 小时,默认 24。多个机器人同时开启缓存时按最大值生效。"
261
+ }),
262
+ components.input.create("messageCache:maxRows", {
263
+ label: "缓存最大消息条数",
264
+ description: "数据库消息行数硬上限,超出后从最旧的消息开始删除,保证磁盘占用可预期。范围 1000~5000000,默认 200000。"
265
+ }),
266
+ components.divider.horizontal("bot-proxy-section", {
267
+ description: "连接代理(高级设置:通常只在使用 Webhook 转 WebSocket 服务时填写,除非你知道你在做什么,否则不建议更改)",
268
+ descPosition: 5
269
+ }),
270
+ components.input.create("proxy:ws", {
271
+ label: "WebSocket 接入地址",
272
+ description: "事件接收方式为 WebSocket 时连接的地址。常见用途是填写第三方 Webhook 转 WebSocket 服务地址,因为 QQ 后台开启 Webhook 后通常无法再直连官方 WebSocket。不填会使用当前环境的官方默认网关。"
273
+ }),
274
+ components.input.create("proxy:api", {
275
+ label: "OpenAPI 反代地址",
276
+ description: "用于发送消息、上传媒体、查询网关和获取机器人资料的 QQ OpenAPI 根地址。多数 Webhook 转 WebSocket 场景不用改这里;只有 OpenAPI 请求也需要走反代时才填写。"
140
277
  })
141
278
  ]
142
279
  }
@@ -147,41 +284,75 @@ var web_config_default = defineConfig({
147
284
  success: false,
148
285
  message: "保存失败:配置格式错误"
149
286
  };
150
- const data = input.qqbot.map((item) => {
151
- const guildMode = item.guildMode === true ? 1 : 0;
152
- const eventType = Number(item["event:type"] ?? 0);
153
- const regex = (item.regex || []).map((str) => {
154
- const parts = str.split(" ");
287
+ const prevConfig = readConfigFile();
288
+ let data;
289
+ try {
290
+ data = input.qqbot.map((item) => {
291
+ const previous = prevConfig.find((cfg) => cfg.appId === toStringValue(item.appId));
292
+ const sandbox = toBool(item.sandbox, previous?.sandbox ?? false);
293
+ const qqEnable = toBool(item.qqEnable, previous?.qqEnable ?? true);
294
+ const guildEnable = toBool(item.guildEnable, previous?.guildEnable ?? true);
295
+ const guildMode = toBool(item.guildMode, previous?.guildMode === 1) ? 1 : 0;
296
+ const eventType = Number(toStringValue(item["event:type"], String(previous?.event?.type ?? 0)));
297
+ const keyboardEnable = toBool(item["keyboard:enable"], previous?.keyboard?.enable ?? true);
298
+ const markdownEnable = toBool(item["markdown:enable"], previous?.markdown?.enable ?? true);
299
+ const messageCacheEnable = toBool(item["messageCache:enable"], previous?.messageCache?.enable ?? false);
300
+ const messageCacheSelf = toBool(item["messageCache:self"], previous?.messageCache?.self ?? false);
301
+ /** 枚举与数值范围由 writeConfig 内的 formatConfig 统一校验回落。 */
302
+ const messageCacheLevel = toStringValue(item["messageCache:level"], previous?.messageCache?.level ?? "standard");
303
+ /** 输入被清空时回落上次值/默认值,避免 Number('') = 0 被夹到范围下限。 */
304
+ const ttlHoursRaw = toStringValue(item["messageCache:ttlHours"]).trim();
305
+ const messageCacheTtlHours = ttlHoursRaw ? Number(ttlHoursRaw) : previous?.messageCache?.ttlHours ?? 24;
306
+ const maxRowsRaw = toStringValue(item["messageCache:maxRows"]).trim();
307
+ const messageCacheMaxRows = maxRowsRaw ? Number(maxRowsRaw) : previous?.messageCache?.maxRows ?? 2e5;
308
+ const regex = toStringList(item.regex).map((str) => {
309
+ const parts = str.split(" ");
310
+ return {
311
+ reg: parts[0]?.replace(/^<|>$/g, "") || "",
312
+ rep: parts[1]?.replace(/^<|>$/g, "") || ""
313
+ };
314
+ });
315
+ const proxy = proxyFromInput(item, previous);
155
316
  return {
156
- reg: parts[0]?.replace(/^<|>$/g, "") || "",
157
- rep: parts[1]?.replace(/^<|>$/g, "") || ""
317
+ name: toStringValue(item.name),
318
+ appId: toStringValue(item.appId),
319
+ secret: toStringValue(item.secret),
320
+ proxy,
321
+ sandbox,
322
+ qqEnable,
323
+ guildEnable,
324
+ guildMode,
325
+ regex,
326
+ keyboard: { enable: keyboardEnable },
327
+ markdown: { enable: markdownEnable },
328
+ messageCache: {
329
+ enable: messageCacheEnable,
330
+ self: messageCacheSelf,
331
+ level: messageCacheLevel,
332
+ ttlHours: messageCacheTtlHours,
333
+ maxRows: messageCacheMaxRows
334
+ },
335
+ event: { type: eventType }
158
336
  };
159
337
  });
338
+ } catch (err) {
160
339
  return {
161
- name: item.name || "",
162
- appId: item.appId || "",
163
- secret: item.secret || "",
164
- prodApi: item.prodApi || "https://api.sgroup.qq.com",
165
- sandboxApi: item.sandboxApi || "https://sandbox.api.sgroup.qq.com",
166
- tokenApi: item.tokenApi || "https://bots.qq.com/app/getAppAccessToken",
167
- sandbox: !!item.sandbox,
168
- qqEnable: !!item.qqEnable,
169
- guildEnable: !!item.guildEnable,
170
- guildMode,
171
- regex,
172
- keyboard: { enable: item["keyboard:enable"] !== false },
173
- messageCache: {
174
- enable: !!item["messageCache:enable"],
175
- self: !!item["messageCache:self"]
176
- },
177
- event: { type: eventType }
340
+ success: false,
341
+ message: `保存失败:代理地址格式错误:${err?.message || "unknown"}`
178
342
  };
179
- });
180
- writeConfig(data);
181
- return {
182
- success: true,
183
- message: "保存成功"
184
- };
343
+ }
344
+ try {
345
+ writeConfig(data);
346
+ return {
347
+ success: true,
348
+ message: "保存成功"
349
+ };
350
+ } catch (err) {
351
+ return {
352
+ success: false,
353
+ message: `保存失败:${err?.message || "unknown"}`
354
+ };
355
+ }
185
356
  }
186
357
  });
187
358
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karinjs/adapter-qqbot",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "karin adapter for QQ Official Bot (2.0)",
5
5
  "homepage": "https://github.com/KarinJS/karin-plugin-adapter-qqbot",
6
6
  "bugs": {
@@ -24,7 +24,8 @@
24
24
  "lint": "eslint src",
25
25
  "lint:fix": "eslint src --fix",
26
26
  "pub": "npm publish --access public",
27
- "sort": "npx sort-package-json"
27
+ "sort": "npx sort-package-json",
28
+ "test": "node tests/schema-sql.test.mjs && node tests/codec.test.mjs && node tests/store.test.mjs"
28
29
  },
29
30
  "dependencies": {
30
31
  "@karinjs/geturls": "^1.0.2",
@@ -35,14 +36,15 @@
35
36
  "tweetnacl": "^1.0.3"
36
37
  },
37
38
  "devDependencies": {
39
+ "@tencent-connect/qqbot-nodejs": "^1.0.4",
38
40
  "@types/express": "5.0.6",
39
- "@types/node": "^24.13.2",
41
+ "@types/node": "^24.13.3",
40
42
  "@types/qrcode": "^1.5.6",
41
43
  "eslint": "^9.39.4",
42
44
  "neostandard": "^0.13.0",
43
- "node-karin": "^1.15.5",
44
- "tsdown": "^0.22.3",
45
- "tsx": "^4.22.4",
45
+ "node-karin": "1.16.3",
46
+ "tsdown": "^0.22.13",
47
+ "tsx": "^4.23.1",
46
48
  "typescript": "^6.0.3"
47
49
  },
48
50
  "publishConfig": {