@alemonjs/qq-bot 2.1.35 → 2.1.36

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 ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-present, Yuxi (Evan) You
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIdED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -131,13 +131,13 @@ export default async () => {
131
131
 
132
132
  ### 说明
133
133
 
134
- | 事项 | 说明 |
135
- | -------- | ------------------------------------------------------------------------------------------------ |
136
- | 触发条件 | 配置中未填写 `app_id`/`secret`,且未配置 `bots`(WebSocket 模式) |
137
- | 二维码 | CBP 统一输出,过期自动刷新(最多 3 次),总超时 10 分钟 |
138
- | 凭证安全 | AppSecret 全程密文传输、本地解密,密钥仅存内存不落盘 |
139
- | 失败处理 | 超时 / 刷新次数用尽 / 授权取消时跳过连接并输出日志,可重启重试或手动配置 |
140
- | Webhook | Webhook 模式(配置 `route`/`port`/`ws`)不触发扫码流程,需自行填写完整配置 |
134
+ | 事项 | 说明 |
135
+ | -------- | -------------------------------------------------------------------------- |
136
+ | 触发条件 | 配置中未填写 `app_id`/`secret`,且未配置 `bots`(WebSocket 模式) |
137
+ | 二维码 | CBP 统一输出,过期自动刷新(最多 3 次),总超时 10 分钟 |
138
+ | 凭证安全 | AppSecret 全程密文传输、本地解密,密钥仅存内存不落盘 |
139
+ | 失败处理 | 超时 / 刷新次数用尽 / 授权取消时跳过连接并输出日志,可重启重试或手动配置 |
140
+ | Webhook | Webhook 模式(配置 `route`/`port`/`ws`)不触发扫码流程,需自行填写完整配置 |
141
141
 
142
142
  ---
143
143
 
package/lib/format.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { DataEnums, DataMarkDown } from "alemonjs";
2
2
  //#region src/format.d.ts
3
+ /** 群聊、私聊和频道共用有序正文;提及等原生文本由发送上下文提供。 */
4
+ declare const formatQQContent: (items: DataEnums[], renderNative: (item: DataEnums) => string, plain?: boolean, hideUnsupported?: boolean | number) => string;
3
5
  /**
4
6
  * 将结构化 Markdown 子元素数组转为可读纯文本
5
7
  */
@@ -22,5 +24,6 @@ declare const markdownRawToText: (raw: string, hideUnsupported?: boolean | numbe
22
24
  * 其余类型降级为文本
23
25
  */
24
26
  declare const dataEnumToText: (item: DataEnums, hideUnsupported?: boolean | number) => string;
27
+ declare const createMarkdownText: (data: DataMarkDown['value']) => string;
25
28
  //#endregion
26
- export { buttonsToText, dataEnumToText, markdownRawToText, markdownToText };
29
+ export { buttonsToText, createMarkdownText, dataEnumToText, formatQQContent, markdownRawToText, markdownToText };
package/lib/format.js CHANGED
@@ -1,10 +1,32 @@
1
+ import { joinMarkdownParts, renderMarkdownBlockquote } from "alemonjs/markdown";
2
+
1
3
  //#region src/format.ts
4
+ /** 群聊、私聊和频道共用有序正文;提及等原生文本由发送上下文提供。 */
5
+ const formatQQContent = (items, renderNative, plain = false, hideUnsupported) => {
6
+ return items.map((item) => {
7
+ if (item.type === "Markdown") return plain ? markdownToText(item.value, hideUnsupported) : createMarkdownText(item.value);
8
+ if (item.type === "MarkdownOriginal") {
9
+ const text = plain ? markdownRawToText(item.value, hideUnsupported) : item.value;
10
+ return text ? `\n\n${text}\n\n` : "";
11
+ }
12
+ if (item.type === "BT.group") {
13
+ if (!plain || Number(hideUnsupported) >= 4) return "";
14
+ const text = item.value.map((row) => row.value.map((button) => {
15
+ if (Number(hideUnsupported) >= 3) return "";
16
+ if (Number(hideUnsupported) >= 2) return button.options?.data || button.value;
17
+ return hideUnsupported ? button.value : `[${button.value}]`;
18
+ }).filter(Boolean).join(" ")).filter(Boolean).join("\n");
19
+ return text ? `\n\n${text}\n\n` : "";
20
+ }
21
+ return renderNative(item);
22
+ }).join("");
23
+ };
2
24
  /**
3
25
  * 将结构化 Markdown 子元素数组转为可读纯文本
4
26
  */
5
27
  const markdownToText = (items, hideUnsupported) => {
6
28
  if (Number(hideUnsupported) >= 4) return "";
7
- return items.map((item) => {
29
+ return joinMarkdownParts(items, items.map((item) => {
8
30
  switch (item.type) {
9
31
  case "MD.text": return item.value;
10
32
  case "MD.title": return hideUnsupported ? `${item.value}\n` : `【${item.value}】\n`;
@@ -16,6 +38,7 @@ const markdownToText = (items, hideUnsupported) => {
16
38
  case "MD.link": {
17
39
  const v = item.value;
18
40
  if (Number(hideUnsupported) >= 3) return "";
41
+ if (!v.url) return v.text;
19
42
  return Number(hideUnsupported) >= 2 ? v.url : `${v.text}( ${v.url} )`;
20
43
  }
21
44
  case "MD.image": return hideUnsupported ? "" : "[图片]";
@@ -23,7 +46,7 @@ const markdownToText = (items, hideUnsupported) => {
23
46
  if (typeof li.value === "object") return `${li.value.index}. ${li.value.text ?? ""}`;
24
47
  return `· ${li.value}`;
25
48
  }).join("\n") + "\n";
26
- case "MD.blockquote": return `> ${item.value}\n`;
49
+ case "MD.blockquote": return renderMarkdownBlockquote(item.value, (children) => markdownToText(children, hideUnsupported));
27
50
  case "MD.divider": return hideUnsupported ? "" : "————————\n";
28
51
  case "MD.newline": return "\n";
29
52
  case "MD.code": return item.value;
@@ -37,7 +60,7 @@ const markdownToText = (items, hideUnsupported) => {
37
60
  return hideUnsupported ? String(item.value) : `[${item.value}]`;
38
61
  default: return String(item?.value ?? "");
39
62
  }
40
- }).join("");
63
+ }));
41
64
  };
42
65
  /**
43
66
  * 将按钮组转为可读纯文本
@@ -88,6 +111,47 @@ const dataEnumToText = (item, hideUnsupported) => {
88
111
  default: return "";
89
112
  }
90
113
  };
114
+ const mdFormatters = {
115
+ "MD.title": (value) => `# ${value} `,
116
+ "MD.subtitle": (value) => `## ${value} `,
117
+ "MD.text": (value) => `${value} `,
118
+ "MD.bold": (value) => `**${value}** `,
119
+ "MD.divider": () => "\n***\n",
120
+ "MD.italic": (value) => `__${value}__ `,
121
+ "MD.italicStar": (value) => `*${value}* `,
122
+ "MD.strikethrough": (value) => `~~${value}~~ `,
123
+ "MD.blockquote": (value) => renderMarkdownBlockquote(value, createMarkdownText),
124
+ "MD.newline": () => "\n",
125
+ "MD.link": (value) => {
126
+ if (!value?.text && !value?.url) return "";
127
+ if (!value?.text || !value?.url) return `<${value?.url ?? value?.text}> `;
128
+ return `[🔗${value?.text}](${value?.url}) `;
129
+ },
130
+ "MD.image": (value, options) => `\n![text #${options?.width || 208}px #${options?.height || 320}px](${value})\n`,
131
+ "MD.mention": (value, options) => {
132
+ const { belong } = options || {};
133
+ if (belong === "channel") return "";
134
+ if (value === "everyone") return "<qqbot-at-everyone />";
135
+ if (belong === "user") return `<qqbot-at-user id="${value}" />`;
136
+ return `<qqbot-at-user id="${value}" />`;
137
+ },
138
+ "MD.content": (value) => `${value}`,
139
+ "MD.button": (title, options) => {
140
+ const { data, autoEnter } = options || {};
141
+ if (autoEnter) return `<qqbot-cmd-enter text="${data}" show="${title}" />`;
142
+ return `<qqbot-cmd-input text="${data}" show="${title}" />`;
143
+ }
144
+ };
145
+ const createMarkdownText = (data) => {
146
+ return joinMarkdownParts(data, data.map((mdItem) => {
147
+ if (mdFormatters[mdItem.type]) return mdFormatters[mdItem.type](mdItem?.value, mdItem?.options);
148
+ if (mdItem.type === "MD.list" && typeof mdItem.value !== "string") return `${mdItem.value.map((listItem) => {
149
+ return typeof listItem.value === "object" ? `\n${listItem.value.index}. ${listItem.value.text}` : `\n- ${listItem.value}`;
150
+ }).join("")}\n`;
151
+ if (mdItem.type === "MD.code") return `\n\`\`\`${mdItem?.options?.language || ""}\n${mdItem.value}\n\`\`\`\n`;
152
+ return String(mdItem["value"] || "");
153
+ }));
154
+ };
91
155
 
92
156
  //#endregion
93
- export { buttonsToText, dataEnumToText, markdownRawToText, markdownToText };
157
+ export { buttonsToText, createMarkdownText, dataEnumToText, formatQQContent, markdownRawToText, markdownToText };
package/lib/sends.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { getQQBotConfig } from "./config.js";
2
- import { buttonsToText, dataEnumToText, markdownToText } from "./format.js";
2
+ import { createMarkdownText, dataEnumToText, formatQQContent } from "./format.js";
3
3
  import { ResultCode, createResult, logger } from "alemonjs";
4
4
  import { readFileSync } from "fs";
5
5
  import axios from "axios";
@@ -172,47 +172,6 @@ const createArkListData = (value) => {
172
172
  ]
173
173
  };
174
174
  };
175
- const mdFormatters = {
176
- "MD.title": (value) => `# ${value} `,
177
- "MD.subtitle": (value) => `## ${value} `,
178
- "MD.text": (value) => `${value} `,
179
- "MD.bold": (value) => `**${value}** `,
180
- "MD.divider": () => "\n***\n",
181
- "MD.italic": (value) => `__${value}__ `,
182
- "MD.italicStar": (value) => `*${value}* `,
183
- "MD.strikethrough": (value) => `~~${value}~~ `,
184
- "MD.blockquote": (value) => `\n> ${value} `,
185
- "MD.newline": () => "\n",
186
- "MD.link": (value) => {
187
- if (!value?.text && !value?.url) return "";
188
- if (!value?.text || !value?.url) return `<${value?.url ?? value?.text}> `;
189
- return `[🔗${value?.text}](${value?.url}) `;
190
- },
191
- "MD.image": (value, options) => `\n![text #${options?.width || 208}px #${options?.height || 320}px](${value})\n`,
192
- "MD.mention": (value, options) => {
193
- const { belong } = options || {};
194
- if (belong === "channel") return "";
195
- if (belong === "user") return `<qqbot-at-user id="${value}" />`;
196
- if (value === "everyone") return "<qqbot-at-everyone />";
197
- return `<qqbot-at-user id="${value}" />`;
198
- },
199
- "MD.content": (value) => `${value}`,
200
- "MD.button": (title, options) => {
201
- const { data, autoEnter } = options || {};
202
- if (autoEnter) return `<qqbot-cmd-enter text="${data}" show="${title}" />`;
203
- return `<qqbot-cmd-input text="${data}" show="${title}" />`;
204
- }
205
- };
206
- const createMarkdownText = (data) => {
207
- return data.map((mdItem) => {
208
- if (mdFormatters[mdItem.type]) return mdFormatters[mdItem.type](mdItem?.value, mdItem?.options);
209
- if (mdItem.type === "MD.list" && typeof mdItem.value !== "string") return `${mdItem.value.map((listItem) => {
210
- return typeof listItem.value === "object" ? `\n${listItem.value.index}. ${listItem.value.text}` : `\n- ${listItem.value}`;
211
- }).join("")}\n`;
212
- if (mdItem.type === "MD.code") return `\n\`\`\`${mdItem?.options?.language || ""}\n${mdItem.value}\n\`\`\`\n`;
213
- return String(mdItem["value"] || "");
214
- }).join("");
215
- };
216
175
  const formatMention = (item, mode) => {
217
176
  if (mode === "guild-direct") return "";
218
177
  const value = item.value;
@@ -373,16 +332,7 @@ const resolveMediaUrl = async (media, uploadMedia) => {
373
332
  }
374
333
  };
375
334
  /** 移除富媒体占位符(dataEnumToText 降级产生的 [视频]/[音频]/[附件]) */
376
- const stripMediaPlaceholders = (text) => text.replace(/\[附件[^\]]*\]/g, "").replace(/\[音频\]/g, "").replace(/\[视频\]/g, "").replace(/\n{2,}/g, "\n").trim();
377
- /** 当 markdownToText 选项开启时,将 Markdown 和按钮降级为纯文本并追加到 content */
378
- const flattenMdToText = (content, val) => {
379
- const mdItems = val.filter((item) => item.type === "Markdown");
380
- const btnItems = val.filter((item) => item.type === "BT.group");
381
- const parts = [content];
382
- for (const item of mdItems) if (item.type === "Markdown" && typeof item.value !== "string") parts.push(markdownToText(item.value));
383
- for (const item of btnItems) if (item.type === "BT.group" && typeof item.value !== "string") parts.push(buttonsToText(item.value));
384
- return parts.filter(Boolean).join("\n").replace(/^[^\S\n\r]+|[^\S\n\r]+$/g, "");
385
- };
335
+ const stripMediaPlaceholders = (text) => text.replace(/\[附件[^\]]*\]/g, "").replace(/\[音频\]/g, "").replace(/\[视频\]/g, "").trim();
386
336
  /** Open API 通用发送逻辑(群组 / C2C) */
387
337
  const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMessage, label, options) => {
388
338
  const config = getQQBotConfig();
@@ -392,7 +342,7 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
392
342
  const fileInfo = await resolveMediaUrl(media, uploadMedia);
393
343
  if (!fileInfo) return [createResult(ResultCode.Fail, "媒体上传失败", null)];
394
344
  const res = await sendMessage({
395
- content: stripMediaPlaceholders(flattenMdToText(content, val)),
345
+ content: stripMediaPlaceholders(formatQQContent(val, (item) => extractContent([item], "group"), true, config.hideUnsupported)),
396
346
  media: { file_info: fileInfo },
397
347
  msg_type: 7,
398
348
  ...baseParams
@@ -404,7 +354,7 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
404
354
  return [];
405
355
  }
406
356
  if (mdToText) {
407
- const textContent = flattenMdToText(content, val);
357
+ const textContent = formatQQContent(val, (item) => extractContent([item], "group"), true, config.hideUnsupported);
408
358
  if (textContent) {
409
359
  const res = await sendMessage({
410
360
  content: textContent,
@@ -417,7 +367,7 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
417
367
  }
418
368
  const mdParams = buildMdAndButtonsParams(val);
419
369
  if (mdParams) {
420
- if (mdParams.markdown?.content && content) mdParams.markdown.content = content + "\n" + mdParams.markdown.content;
370
+ if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], "group"));
421
371
  const res = await sendMessage({
422
372
  content,
423
373
  msg_type: 2,
@@ -463,14 +413,14 @@ const resolveImageBuffer = async (images) => {
463
413
  return null;
464
414
  };
465
415
  /** Guild API 通用发送逻辑(频道公聊 / 频道私聊) */
466
- const sendGuildMessage = async (content, val, baseParams, sendMessage, label) => {
416
+ const sendGuildMessage = async (content, val, baseParams, sendMessage, label, mode) => {
467
417
  const config = getQQBotConfig();
468
418
  const mdToText = config.markdownToText === true;
469
419
  const images = filterImages(val);
470
420
  if (images.length > 0) {
471
421
  const imageBuffer = await resolveImageBuffer(images);
472
422
  const res = await sendMessage({
473
- content: flattenMdToText(content, val),
423
+ content: formatQQContent(val, (item) => extractContent([item], mode), true, config.hideUnsupported),
474
424
  ...baseParams
475
425
  }, imageBuffer);
476
426
  return [createResult(ResultCode.Ok, label, { id: res?.id })];
@@ -480,7 +430,7 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label) =>
480
430
  return [];
481
431
  }
482
432
  if (mdToText) {
483
- const textContent = flattenMdToText(content, val);
433
+ const textContent = formatQQContent(val, (item) => extractContent([item], mode), true, config.hideUnsupported);
484
434
  if (textContent) {
485
435
  const res = await sendMessage({
486
436
  content: textContent,
@@ -492,7 +442,7 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label) =>
492
442
  }
493
443
  const mdParams = buildMdAndButtonsParams(val);
494
444
  if (mdParams) {
495
- if (mdParams.markdown?.content && content) mdParams.markdown.content = content + "\n" + mdParams.markdown.content;
445
+ if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], mode));
496
446
  const res = await sendMessage({
497
447
  content: "",
498
448
  ...mdParams,
@@ -549,7 +499,7 @@ const DIRECT_MESSAGE_CREATE = async (client, event, val) => {
549
499
  const baseParams = buildBaseParams(event._tag, event.MessageId, "INTERACTION_CREATE_GUILD");
550
500
  const content = extractContent(val, "guild-direct");
551
501
  try {
552
- return await sendGuildMessage(content, val, baseParams, (data, buf) => client.dmsMessages(event.UserId, data, buf), "client.dmsMessage");
502
+ return await sendGuildMessage(content, val, baseParams, (data, buf) => client.dmsMessages(event.UserId, data, buf), "client.dmsMessage", "guild-direct");
553
503
  } catch (err) {
554
504
  return [createResult(ResultCode.Fail, err?.response?.data ?? err?.message ?? err, null)];
555
505
  }
@@ -561,7 +511,7 @@ const MESSAGE_CREATE = async (client, event, val) => {
561
511
  const baseParams = buildBaseParams(event._tag, event.MessageId, "INTERACTION_CREATE_GUILD");
562
512
  const content = extractContent(val, "guild-public");
563
513
  try {
564
- return await sendGuildMessage(content, val, baseParams, (data, buf) => client.channelsMessages(event.ChannelId, data, buf), "client.channelsMessagesPost");
514
+ return await sendGuildMessage(content, val, baseParams, (data, buf) => client.channelsMessages(event.ChannelId, data, buf), "client.channelsMessagesPost", "guild-public");
565
515
  } catch (err) {
566
516
  return [createResult(ResultCode.Fail, err?.response?.data ?? err?.message ?? err, null)];
567
517
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alemonjs/qq-bot",
3
- "version": "2.1.35",
3
+ "version": "2.1.36",
4
4
  "description": "QQ官方平台",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "yaml": "^2.5.1"
31
31
  },
32
32
  "peerDependencies": {
33
- "alemonjs": "^2.1.0-alpha.15"
33
+ "alemonjs": "^2.1.108"
34
34
  },
35
35
  "types": "lib",
36
36
  "exports": {
@@ -97,5 +97,5 @@
97
97
  "type": "git",
98
98
  "url": "https://github.com/lemonade-lab/alemonjs.git"
99
99
  },
100
- "gitHead": "c6aa5616afe091a37610dad22fbb2d2618d943b8"
101
- }
100
+ "gitHead": "a4d4fded0874b070e356e6a83b06bd3e474f9133"
101
+ }