@alemonjs/qq-bot 2.1.36 → 2.1.38

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.
Files changed (60) hide show
  1. package/README.md +68 -1979
  2. package/lib/config.d.ts +5 -0
  3. package/lib/framework-actions.d.ts +75 -0
  4. package/lib/framework-actions.js +620 -0
  5. package/lib/framework-events.d.ts +10 -0
  6. package/lib/framework-events.js +182 -0
  7. package/lib/hook.d.ts +16 -12
  8. package/lib/hook.js +5 -9
  9. package/lib/index.d.ts +6 -1
  10. package/lib/index.js +2 -1
  11. package/lib/index.websoket.js +6 -2
  12. package/lib/message/AUDIO_EVENT.d.ts +20 -0
  13. package/lib/message/AUDIO_EVENT.js +1 -0
  14. package/lib/message/FORUM_EVENT.d.ts +33 -0
  15. package/lib/message/FORUM_EVENT.js +1 -0
  16. package/lib/message/GUILD_AUDIT.d.ts +14 -0
  17. package/lib/message/GUILD_AUDIT.js +1 -0
  18. package/lib/message/INTERACTION_CREATE.d.ts +13 -20
  19. package/lib/message/MESSAGE_CREATE.d.ts +3 -9
  20. package/lib/message/MESSAGE_DELETE.d.ts +15 -1
  21. package/lib/message/group/C2C_MESSAGE_CREATE.d.ts +3 -13
  22. package/lib/message/group/C2C_MSG_RECEIVE.d.ts +1 -1
  23. package/lib/message/group/C2C_MSG_REJECT.d.ts +1 -1
  24. package/lib/message/group/FRIEND_ADD.d.ts +7 -1
  25. package/lib/message/group/FRIEND_DEL.d.ts +4 -1
  26. package/lib/message/group/GROUP_AT_MESSAGE_CREATE.d.ts +6 -27
  27. package/lib/message/group/GROUP_MEMBER_ADD.d.ts +4 -2
  28. package/lib/message/group/GROUP_MEMBER_REMOVE.d.ts +4 -2
  29. package/lib/message/group/GROUP_MESSAGE_CREATE.d.ts +6 -27
  30. package/lib/message/group/GROUP_MSG_RECEIVE.d.ts +1 -1
  31. package/lib/message/group/GROUP_MSG_REJECT.d.ts +1 -1
  32. package/lib/message/group/MESSAGE_AUDIT_PASS.d.ts +2 -0
  33. package/lib/message/group/MESSAGE_AUDIT_REJECT.d.ts +2 -0
  34. package/lib/message/group/SUBSCRIBE_MESSAGE_STATUS.d.ts +17 -0
  35. package/lib/message/group/SUBSCRIBE_MESSAGE_STATUS.js +1 -0
  36. package/lib/register.d.ts +1 -0
  37. package/lib/register.js +287 -115
  38. package/lib/sdk/api.d.ts +93 -14
  39. package/lib/sdk/api.js +241 -45
  40. package/lib/sdk/client.webhook.js +39 -19
  41. package/lib/sdk/client.websoket.js +2 -1
  42. package/lib/sdk/instance.d.ts +1 -6
  43. package/lib/sdk/instance.js +20 -66
  44. package/lib/sdk/intents.d.ts +1 -0
  45. package/lib/sdk/intents.js +1 -0
  46. package/lib/sdk/message.d.ts +1 -1
  47. package/lib/sdk/message.group.d.ts +4 -2
  48. package/lib/sdk/message.guild.d.ts +19 -0
  49. package/lib/sdk/non-channel-types.d.ts +138 -0
  50. package/lib/sdk/non-channel-types.js +1 -0
  51. package/lib/sdk/registry.js +2 -2
  52. package/lib/sdk/typing.d.ts +13 -1
  53. package/lib/sdk/webhook.secret.d.ts +1 -1
  54. package/lib/sdk/webhook.secret.js +10 -5
  55. package/lib/sends.d.ts +3 -3
  56. package/lib/sends.js +46 -10
  57. package/lib/text-sanitizer.d.ts +13 -0
  58. package/lib/text-sanitizer.js +26 -0
  59. package/lib/upload.js +13 -37
  60. package/package.json +8 -6
package/lib/sends.js CHANGED
@@ -335,6 +335,15 @@ const resolveMediaUrl = async (media, uploadMedia) => {
335
335
  const stripMediaPlaceholders = (text) => text.replace(/\[附件[^\]]*\]/g, "").replace(/\[音频\]/g, "").replace(/\[视频\]/g, "").trim();
336
336
  /** Open API 通用发送逻辑(群组 / C2C) */
337
337
  const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMessage, label, options) => {
338
+ if (options?.wakeup && (options.replyId || options.eventId) || options?.replyId && options?.eventId) return [createResult(ResultCode.FailParams, "wakeup, replyId and eventId are mutually exclusive", null)];
339
+ if (options?.wakeup || options?.replyId || options?.eventId) {
340
+ baseParams = {};
341
+ if (options.wakeup) baseParams.is_wakeup = true;
342
+ else if (options.eventId) baseParams.event_id = options.eventId;
343
+ else baseParams.msg_id = options.replyId;
344
+ }
345
+ if (options?.sequence !== void 0) baseParams.msg_seq = options.sequence;
346
+ if (options?.referenceId) baseParams.message_reference = { message_id: options.referenceId };
338
347
  const config = getQQBotConfig();
339
348
  const mdToText = config.markdownToText === true;
340
349
  const media = filterMedia(val);
@@ -347,7 +356,11 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
347
356
  msg_type: 7,
348
357
  ...baseParams
349
358
  });
350
- return [createResult(ResultCode.Ok, label, { id: res.id })];
359
+ return [createResult(ResultCode.Ok, label, {
360
+ id: res.id,
361
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
362
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
363
+ })];
351
364
  }
352
365
  if (config.hideUnsupported === true && !content && !buildMdAndButtonsParams(val) && !buildArkParams(val)) {
353
366
  logger.info("[qq-bot] hideUnsupported: 消息内容转换后为空,跳过发送");
@@ -361,21 +374,28 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
361
374
  msg_type: 0,
362
375
  ...baseParams
363
376
  });
364
- return [createResult(ResultCode.Ok, label, { id: res.id })];
377
+ return [createResult(ResultCode.Ok, label, {
378
+ id: res.id,
379
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
380
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
381
+ })];
365
382
  }
366
383
  return [];
367
384
  }
368
385
  const mdParams = buildMdAndButtonsParams(val);
369
386
  if (mdParams) {
370
387
  if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], "group"));
388
+ if (mdParams.markdown && options?.forceVerifyImageResource !== void 0) mdParams.markdown.force_verify_image_resource = options.forceVerifyImageResource;
371
389
  const res = await sendMessage({
372
- content,
373
390
  msg_type: 2,
374
391
  ...mdParams,
375
- ...baseParams,
376
- force_verify_image_resource: options?.forceVerifyImageResource
392
+ ...baseParams
377
393
  });
378
- return [createResult(ResultCode.Ok, label, { id: res.id })];
394
+ return [createResult(ResultCode.Ok, label, {
395
+ id: res.id,
396
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
397
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
398
+ })];
379
399
  }
380
400
  const arkParams = buildArkParams(val);
381
401
  if (arkParams) {
@@ -385,7 +405,11 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
385
405
  ...arkParams,
386
406
  ...baseParams
387
407
  });
388
- return [createResult(ResultCode.Ok, label, { id: res.id })];
408
+ return [createResult(ResultCode.Ok, label, {
409
+ id: res.id,
410
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
411
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
412
+ })];
389
413
  }
390
414
  if (content) {
391
415
  const res = await sendMessage({
@@ -393,7 +417,11 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
393
417
  msg_type: 0,
394
418
  ...baseParams
395
419
  });
396
- return [createResult(ResultCode.Ok, label, { id: res.id })];
420
+ return [createResult(ResultCode.Ok, label, {
421
+ id: res.id,
422
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
423
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
424
+ })];
397
425
  }
398
426
  return [];
399
427
  };
@@ -448,7 +476,11 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label, mo
448
476
  ...mdParams,
449
477
  ...baseParams
450
478
  });
451
- return [createResult(ResultCode.Ok, label, { id: res.id })];
479
+ return [createResult(ResultCode.Ok, label, {
480
+ id: res.id,
481
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
482
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
483
+ })];
452
484
  }
453
485
  const arkParams = buildArkParams(val);
454
486
  if (arkParams) {
@@ -457,7 +489,11 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label, mo
457
489
  ...arkParams,
458
490
  ...baseParams
459
491
  });
460
- return [createResult(ResultCode.Ok, label, { id: res.id })];
492
+ return [createResult(ResultCode.Ok, label, {
493
+ id: res.id,
494
+ ...res.timestamp !== void 0 && { timestamp: res.timestamp },
495
+ ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
496
+ })];
461
497
  }
462
498
  if (content) {
463
499
  const res = await sendMessage({
@@ -0,0 +1,13 @@
1
+ //#region src/text-sanitizer.d.ts
2
+ type QQTextSanitizerOptions = {
3
+ /** Strip only this bot by default. Other users' mentions retain meaning. */
4
+ botMention?: 'remove' | 'preserve';
5
+ otherMentions?: 'remove' | 'preserve';
6
+ faces?: 'preserve' | 'remove' | 'label';
7
+ /** Disabled by default to preserve paragraphs and code indentation. */
8
+ collapseWhitespace?: boolean;
9
+ };
10
+ /** Converts QQ protocol markup, never HTML. Raw event content is preserved. */
11
+ declare const sanitizeQQText: (text: string, botId: string, options?: QQTextSanitizerOptions) => string;
12
+ //#endregion
13
+ export { QQTextSanitizerOptions, sanitizeQQText };
@@ -0,0 +1,26 @@
1
+ //#region src/text-sanitizer.ts
2
+ /** Converts QQ protocol markup, never HTML. Raw event content is preserved. */
3
+ const sanitizeQQText = (text, botId, options = {}) => {
4
+ let result = text.replace(/<@!?([^>\s]+)>/g, (marker, id) => {
5
+ return (id === botId ? options.botMention !== "preserve" : options.otherMentions === "remove") ? "" : marker;
6
+ });
7
+ const faces = options.faces ?? "preserve";
8
+ if (faces !== "preserve") {
9
+ result = result.replace(/\[<face,id=(\d+)\/?>\]/g, (_marker, id) => faces === "label" ? `[表情:${id}]` : "");
10
+ result = result.replace(/<faceType=\d+,faceId="([^"]*)",ext="([^"]*)">/g, (_marker, id, ext) => {
11
+ if (faces === "remove") return "";
12
+ try {
13
+ if (ext.length <= 1e4) {
14
+ const data = JSON.parse(Buffer.from(ext, "base64").toString("utf8"));
15
+ if (typeof data?.text === "string" && data.text) return `[表情:${data.text.slice(0, 100)}]`;
16
+ }
17
+ } catch {}
18
+ return `[表情:${id}]`;
19
+ });
20
+ }
21
+ if (options.collapseWhitespace) result = result.replace(/\s+/g, " ");
22
+ return result.trim();
23
+ };
24
+
25
+ //#endregion
26
+ export { sanitizeQQText };
package/lib/upload.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "fs";
1
+ import { readFileSync, statSync } from "fs";
2
2
  import { createHash } from "crypto";
3
3
 
4
4
  //#region src/upload.ts
@@ -47,42 +47,18 @@ const computeFileDigests = (filePath) => {
47
47
  * @param file 本地文件路径或文件内容
48
48
  * @param options 上传选项
49
49
  */
50
- const chunkedUpload = async (client, scope, openId, file, options) => {
51
- const buffer = typeof file === "string" ? readFileSync(file) : file;
52
- const md5 = createHash("md5").update(buffer).digest("hex");
53
- const sha1 = createHash("sha1").update(buffer).digest("hex");
54
- const md5_10m = createHash("md5").update(buffer.subarray(0, CHUNK_THRESHOLD)).digest("hex");
55
- const file_name = options.file_name ?? (typeof file === "string" ? file.split(/[\\/]/).pop() ?? "file" : "file");
56
- const prepareData = {
57
- file_type: options.file_type,
58
- file_name,
59
- file_size: String(buffer.byteLength),
60
- md5,
61
- sha1,
62
- md5_10m
63
- };
64
- const prepare = await (scope === "user" ? client.usersUploadPrepare(openId, prepareData) : client.groupUploadPrepare(openId, prepareData));
65
- const blockSize = Number(prepare.block_size);
66
- for (const part of prepare.parts) {
67
- const start = part.index * blockSize;
68
- const chunk = Buffer.from(buffer.subarray(start, start + Number(part.block_size)));
69
- const partMd5 = createHash("md5").update(chunk).digest("hex");
70
- await client.uploadPartDirect(part.presigned_url, chunk);
71
- const finishData = {
72
- upload_id: prepare.upload_id,
73
- part_index: part.index,
74
- block_size: part.block_size,
75
- md5: partMd5
76
- };
77
- await (scope === "user" ? client.usersUploadPartFinish(openId, finishData) : client.groupUploadPartFinish(openId, finishData));
78
- }
79
- const merge = {
80
- file_type: options.file_type,
81
- upload_id: prepare.upload_id,
82
- file_name,
83
- srv_send_msg: options.srv_send_msg
84
- };
85
- return scope === "user" ? client.postRichMediaByUser(openId, merge) : client.postRichMediaByGroup(openId, merge);
50
+ const chunkedUpload = (client, scope, openId, file, options) => {
51
+ return client.postChunkedRichMedia({
52
+ scope: scope === "user" ? "c2c" : "group",
53
+ targetId: openId,
54
+ fileType: options.file_type,
55
+ ...typeof file === "string" ? {
56
+ filePath: file,
57
+ size: statSync(file).size
58
+ } : { data: file },
59
+ name: options.file_name ?? (typeof file === "string" ? file.split(/[\\/]/).pop() : void 0),
60
+ send: options.srv_send_msg
61
+ });
86
62
  };
87
63
 
88
64
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alemonjs/qq-bot",
3
- "version": "2.1.36",
3
+ "version": "2.1.38",
4
4
  "description": "QQ官方平台",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",
@@ -10,7 +10,12 @@
10
10
  "build": "lvy build",
11
11
  "test:ws": "yarn build && node tests/websocket.integration.mjs",
12
12
  "test:send": "yarn build && node tests/use-send.notice.mjs",
13
- "test:qr": "yarn build && node tests/qr-auth.mjs"
13
+ "test:channel": "yarn --cwd ../alemonjs build && yarn build && node tests/channel.contract.mjs",
14
+ "test:qr": "yarn build && node tests/qr-auth.mjs",
15
+ "test:non-channel": "yarn build && node tests/non-channel.contract.mjs && node tests/framework.contract.mjs && node tests/non-channel.transport.mjs && node tests/media-upload.contract.mjs && node tests/routing.contract.mjs && node tests/lifecycle.contract.mjs && node tests/http-diagnostics.contract.mjs && node tests/webhook-ingress.contract.mjs && node tests/text-sanitizer.contract.mjs && tsc -p tests/tsconfig.non-channel.json",
16
+ "docs:snapshot": "python3 scripts/snapshot-official.py",
17
+ "test:http": "yarn build && node tests/http-diagnostics.contract.mjs && node tests/webhook-ingress.contract.mjs",
18
+ "test:text": "yarn build && node tests/text-sanitizer.contract.mjs"
14
19
  },
15
20
  "dependencies": {
16
21
  "@noble/curves": "^1.7.0",
@@ -29,9 +34,6 @@
29
34
  "ws": "^8.13.0",
30
35
  "yaml": "^2.5.1"
31
36
  },
32
- "peerDependencies": {
33
- "alemonjs": "^2.1.108"
34
- },
35
37
  "types": "lib",
36
38
  "exports": {
37
39
  ".": {
@@ -97,5 +99,5 @@
97
99
  "type": "git",
98
100
  "url": "https://github.com/lemonade-lab/alemonjs.git"
99
101
  },
100
- "gitHead": "a4d4fded0874b070e356e6a83b06bd3e474f9133"
102
+ "gitHead": "e736f4e180a8694f8c9eb1796a23fdfadcc48f3e"
101
103
  }