@alemonjs/qq-bot 2.1.39 → 2.1.40

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/lib/config.d.ts CHANGED
@@ -44,7 +44,7 @@ declare const getQQBotBots: () => {
44
44
  bots: Map<string, Options$1>;
45
45
  defaultBot?: string;
46
46
  };
47
- declare const getIdentity: (UserId: string) => readonly [any, any];
48
- declare const getMaster: (UserId: string) => readonly [any, any];
47
+ declare const getIdentity: (UserId: string) => readonly [boolean, string];
48
+ declare const getMaster: (UserId: string) => readonly [boolean, string];
49
49
  //#endregion
50
50
  export { Options, getIdentity, getMaster, getQQBotBots, getQQBotConfig, platform, platformFullName };
package/lib/format.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { DataEnums, DataMarkDown } from "alemonjs";
2
2
  //#region src/format.d.ts
3
+ type QQMarkdownContext = 'group' | 'c2c' | 'guild';
3
4
  /** 群聊、私聊和频道共用有序正文;提及等原生文本由发送上下文提供。 */
4
- declare const formatQQContent: (items: DataEnums[], renderNative: (item: DataEnums) => string, plain?: boolean, hideUnsupported?: boolean | number) => string;
5
+ declare const formatQQContent: (items: DataEnums[], renderNative: (item: DataEnums) => string, plain?: boolean, hideUnsupported?: boolean | number, context?: QQMarkdownContext) => string;
5
6
  /**
6
7
  * 将结构化 Markdown 子元素数组转为可读纯文本
7
8
  */
@@ -24,6 +25,6 @@ declare const markdownRawToText: (raw: string, hideUnsupported?: boolean | numbe
24
25
  * 其余类型降级为文本
25
26
  */
26
27
  declare const dataEnumToText: (item: DataEnums, hideUnsupported?: boolean | number) => string;
27
- declare const createMarkdownText: (data: DataMarkDown['value']) => string;
28
+ declare const createMarkdownText: (data: DataMarkDown['value'], context?: QQMarkdownContext) => string;
28
29
  //#endregion
29
- export { buttonsToText, createMarkdownText, dataEnumToText, formatQQContent, markdownRawToText, markdownToText };
30
+ export { QQMarkdownContext, buttonsToText, createMarkdownText, dataEnumToText, formatQQContent, markdownRawToText, markdownToText };
package/lib/format.js CHANGED
@@ -2,9 +2,9 @@ import { joinMarkdownParts, renderMarkdownBlockquote } from "alemonjs/markdown";
2
2
 
3
3
  //#region src/format.ts
4
4
  /** 群聊、私聊和频道共用有序正文;提及等原生文本由发送上下文提供。 */
5
- const formatQQContent = (items, renderNative, plain = false, hideUnsupported) => {
5
+ const formatQQContent = (items, renderNative, plain = false, hideUnsupported, context = "group") => {
6
6
  return items.map((item) => {
7
- if (item.type === "Markdown") return plain ? markdownToText(item.value, hideUnsupported) : createMarkdownText(item.value);
7
+ if (item.type === "Markdown") return plain ? markdownToText(item.value, hideUnsupported) : createMarkdownText(item.value, context);
8
8
  if (item.type === "MarkdownOriginal") {
9
9
  const text = plain ? markdownRawToText(item.value, hideUnsupported) : item.value;
10
10
  return text ? `\n\n${text}\n\n` : "";
@@ -111,6 +111,7 @@ const dataEnumToText = (item, hideUnsupported) => {
111
111
  default: return "";
112
112
  }
113
113
  };
114
+ const escapeAttribute = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
114
115
  const mdFormatters = {
115
116
  "MD.title": (value) => `# ${value} `,
116
117
  "MD.subtitle": (value) => `## ${value} `,
@@ -120,7 +121,7 @@ const mdFormatters = {
120
121
  "MD.italic": (value) => `__${value}__ `,
121
122
  "MD.italicStar": (value) => `*${value}* `,
122
123
  "MD.strikethrough": (value) => `~~${value}~~ `,
123
- "MD.blockquote": (value) => renderMarkdownBlockquote(value, createMarkdownText),
124
+ "MD.blockquote": (value, _options, context) => renderMarkdownBlockquote(value, (children) => createMarkdownText(children, context)),
124
125
  "MD.newline": () => "\n",
125
126
  "MD.link": (value) => {
126
127
  if (!value?.text && !value?.url) return "";
@@ -136,15 +137,17 @@ const mdFormatters = {
136
137
  return `<qqbot-at-user id="${value}" />`;
137
138
  },
138
139
  "MD.content": (value) => `${value}`,
139
- "MD.button": (title, options) => {
140
+ "MD.button": (title, options, context) => {
140
141
  const { data, autoEnter } = options || {};
141
- if (autoEnter) return `<qqbot-cmd-enter text="${data}" show="${title}" />`;
142
- return `<qqbot-cmd-input text="${data}" show="${title}" />`;
142
+ const text = String(data ?? title ?? "");
143
+ if (autoEnter && context === "c2c") return `<qqbot-cmd-enter text="${escapeAttribute(text)}" />`;
144
+ const inputText = text.slice(0, 80).replace(/[\uD800-\uDBFF]$/u, "");
145
+ return `<qqbot-cmd-input text="${escapeAttribute(inputText)}" show="${escapeAttribute(String(title ?? ""))}" />`;
143
146
  }
144
147
  };
145
- const createMarkdownText = (data) => {
148
+ const createMarkdownText = (data, context = "group") => {
146
149
  return joinMarkdownParts(data, data.map((mdItem) => {
147
- if (mdFormatters[mdItem.type]) return mdFormatters[mdItem.type](mdItem?.value, mdItem?.options);
150
+ if (mdFormatters[mdItem.type]) return mdFormatters[mdItem.type](mdItem?.value, mdItem?.options, context);
148
151
  if (mdItem.type === "MD.list" && typeof mdItem.value !== "string") return `${mdItem.value.map((listItem) => {
149
152
  return typeof listItem.value === "object" ? `\n${listItem.value.index}. ${listItem.value.text}` : `\n- ${listItem.value}`;
150
153
  }).join("")}\n`;
@@ -3,25 +3,66 @@ import { OutgoingMessage } from "alemonjs";
3
3
  //#region src/framework-actions.d.ts
4
4
  declare const toQQMessage: (content: OutgoingMessage) => {
5
5
  msg_type: 0 | 2 | 3 | 7;
6
- content: any;
6
+ content: string;
7
7
  markdown: {
8
- content: any;
9
- custom_template_id: any;
10
- template_id: any;
11
- params: any;
12
- force_verify_image_resource: any;
8
+ content: string;
9
+ custom_template_id: string;
10
+ template_id: number;
11
+ params: {
12
+ key: string;
13
+ values: string[];
14
+ }[];
15
+ force_verify_image_resource: boolean;
13
16
  };
14
17
  media: {
15
- file_info: any;
18
+ file_info: string;
16
19
  };
17
20
  ark: {
18
- template_id: any;
19
- kv: any;
21
+ template_id: number;
22
+ kv: {
23
+ key: string;
24
+ value: string;
25
+ obj: {
26
+ obj_kv: {
27
+ key: string;
28
+ value: string;
29
+ }[];
30
+ }[];
31
+ }[];
20
32
  };
21
33
  keyboard: {
22
- id: any;
34
+ id: string;
23
35
  content: {
24
- rows: any;
36
+ rows: {
37
+ buttons: {
38
+ id: string;
39
+ group_id: string;
40
+ render_data: {
41
+ label: string;
42
+ visited_label: string;
43
+ style: number;
44
+ };
45
+ action: {
46
+ type: number;
47
+ data: string;
48
+ permission: {
49
+ type: number;
50
+ specify_user_ids: string[];
51
+ specify_role_ids: string[];
52
+ };
53
+ enter: boolean;
54
+ reply: boolean;
55
+ unsupport_tips: string;
56
+ anchor: number;
57
+ click_limit: number;
58
+ modal: {
59
+ content: string;
60
+ confirm_text: string;
61
+ cancel_text: string;
62
+ };
63
+ };
64
+ }[];
65
+ }[];
25
66
  };
26
67
  };
27
68
  };
@@ -29,6 +70,6 @@ declare const toQQMessage: (content: OutgoingMessage) => {
29
70
  declare const handleFrameworkAction: (client: QQBotAPI, data: {
30
71
  action: string;
31
72
  payload: any;
32
- }, botId: string) => Promise<ClientAPIMessageResult[]>;
73
+ }, botId: string) => Promise<import("alemonjs/common").Result<any>[]>;
33
74
  //#endregion
34
75
  export { handleFrameworkAction, toQQMessage };
package/lib/hook.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { QQBotAPI } from "./sdk/api.js";
1
2
  import { SUBSCRIBE_MESSAGE_STATUS_TYPE } from "./message/group/SUBSCRIBE_MESSAGE_STATUS.js";
2
3
  import { GROUP_MEMBER_ADD } from "./message/group/GROUP_MEMBER_ADD.js";
3
4
  import { GROUP_MEMBER_REMOVE } from "./message/group/GROUP_MEMBER_REMOVE.js";
@@ -76,7 +77,7 @@ declare const useValue: <T extends EventKeys>(event: Events[T]) => readonly [Eve
76
77
  * @param event
77
78
  * @returns
78
79
  */
79
- declare const useClient: <T extends EventKeys>(event: Events[T]) => readonly [any, EventValues[T]];
80
+ declare const useClient: <T extends EventKeys>(event: Events[T]) => readonly [QQBotAPI, EventValues[T]];
80
81
  /**
81
82
  * 判断当前模式
82
83
  * @param event
package/lib/index.d.ts CHANGED
@@ -7,6 +7,6 @@ import { useClient, useMode, useValue } from "./hook.js";
7
7
  import { QQBotGroupEventMap } from "./sdk/message.group.js";
8
8
  import { QQBotPublicEventMap } from "./sdk/message.public.js";
9
9
  //#region src/index.d.ts
10
- declare const _default: any;
10
+ declare const _default: () => any;
11
11
  //#endregion
12
12
  export { QQBotAPI as API, type ApiRequestData, type ButtonType, type FileType, type GlobalMuteRule, type GroupAction, type JoinApprovalStrategy, type JoinRequest, type KeyboardType, type MarkdownType, type MemberMuteState, type MessageType, type Options, type QQBotArkData, type QQBotAttachment, type QQBotBlacklistUser, type QQBotGroupEventMap, type QQBotGroupMember, type QQBotInteractionResolved, type QQBotMenu, type QQBotMenuItem, type QQBotMessageContent, type QQBotMessageElement, type QQBotMessageResult, type QQBotMessageUser, type QQBotPage, type QQBotPanel, type QQBotPanelCreate, type QQBotPanelRecord, type QQBotPanelScope, type QQBotPublicEventMap, type QQTextSanitizerOptions, type SetMemberMuteState, type StreamMessageData, type StreamMessageResult, type UploadConfig, type UploadPart, type UploadPartFinishData, type UploadPrepareData, type UploadPrepareResult, type VerifyInfo, _default as default, platform, sanitizeQQText, useClient, useMode, useValue };
package/lib/sends.js CHANGED
@@ -231,7 +231,7 @@ const buildBaseParams = (tag, messageId, interactionTag) => {
231
231
  return { msg_id: messageId };
232
232
  };
233
233
  /** 构建 Markdown 和按钮参数 */
234
- const buildMdAndButtonsParams = (val) => {
234
+ const buildMdAndButtonsParams = (val, context = "group") => {
235
235
  const items = val.filter((item) => item.type === "Markdown" || item.type === "MarkdownOriginal" || item.type === "BT.group" || item.type === "ButtonTemplate");
236
236
  if (items.length === 0) return null;
237
237
  const params = {};
@@ -255,7 +255,7 @@ const buildMdAndButtonsParams = (val) => {
255
255
  }
256
256
  } else params["keyboard"] = { content: createButtonsData(item.value) };
257
257
  } else if (item.type === "Markdown" && typeof item?.value !== "string") {
258
- const content = createMarkdownText(item.value);
258
+ const content = createMarkdownText(item.value, context);
259
259
  if (content) {
260
260
  if (params["markdown"]?.content) params["markdown"].content += content;
261
261
  else params["markdown"] = { content };
@@ -335,7 +335,7 @@ const resolveMediaUrl = async (media, uploadMedia) => {
335
335
  /** 移除富媒体占位符(dataEnumToText 降级产生的 [视频]/[音频]/[附件]) */
336
336
  const stripMediaPlaceholders = (text) => text.replace(/\[附件[^\]]*\]/g, "").replace(/\[音频\]/g, "").replace(/\[视频\]/g, "").trim();
337
337
  /** Open API 通用发送逻辑(群组 / C2C) */
338
- const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMessage, label, options) => {
338
+ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMessage, label, context, options) => {
339
339
  if (options?.wakeup && (options.replyId || options.eventId) || options?.replyId && options?.eventId) return [createResult(ResultCode.FailParams, "wakeup, replyId and eventId are mutually exclusive", null)];
340
340
  if (options?.wakeup || options?.replyId || options?.eventId) {
341
341
  baseParams = {};
@@ -363,7 +363,7 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
363
363
  ...res.ext_info?.ref_idx !== void 0 && { referenceId: res.ext_info.ref_idx }
364
364
  })];
365
365
  }
366
- if (config.hideUnsupported === true && !content && !buildMdAndButtonsParams(val) && !buildArkParams(val)) {
366
+ if (config.hideUnsupported === true && !content && !buildMdAndButtonsParams(val, context) && !buildArkParams(val)) {
367
367
  logger.info("[qq-bot] hideUnsupported: 消息内容转换后为空,跳过发送");
368
368
  return [];
369
369
  }
@@ -383,9 +383,9 @@ const sendOpenApiMessage = async (content, val, baseParams, uploadMedia, sendMes
383
383
  }
384
384
  return [];
385
385
  }
386
- const mdParams = buildMdAndButtonsParams(val);
386
+ const mdParams = buildMdAndButtonsParams(val, context);
387
387
  if (mdParams) {
388
- if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], "group"));
388
+ if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], "group"), false, void 0, context);
389
389
  if (mdParams.markdown && options?.forceVerifyImageResource !== void 0) mdParams.markdown.force_verify_image_resource = options.forceVerifyImageResource;
390
390
  const res = await sendMessage({
391
391
  msg_type: 2,
@@ -454,7 +454,7 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label, mo
454
454
  }, imageBuffer);
455
455
  return [createResult(ResultCode.Ok, label, { id: res?.id })];
456
456
  }
457
- if (config.hideUnsupported && !content && !buildMdAndButtonsParams(val) && !buildArkParams(val)) {
457
+ if (config.hideUnsupported && !content && !buildMdAndButtonsParams(val, "guild") && !buildArkParams(val)) {
458
458
  logger.info("[qq-bot] hideUnsupported: 消息内容转换后为空,跳过发送");
459
459
  return [];
460
460
  }
@@ -469,9 +469,9 @@ const sendGuildMessage = async (content, val, baseParams, sendMessage, label, mo
469
469
  }
470
470
  return [];
471
471
  }
472
- const mdParams = buildMdAndButtonsParams(val);
472
+ const mdParams = buildMdAndButtonsParams(val, "guild");
473
473
  if (mdParams) {
474
- if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], mode));
474
+ if (mdParams.markdown?.content) mdParams.markdown.content = formatQQContent(val, (item) => extractContent([item], mode), false, void 0, "guild");
475
475
  const res = await sendMessage({
476
476
  content: "",
477
477
  ...mdParams,
@@ -512,7 +512,7 @@ const GROUP_AT_MESSAGE_CREATE = async (client, event, val, options) => {
512
512
  const baseParams = buildBaseParams(event._tag, event.MessageId, "INTERACTION_CREATE_GROUP");
513
513
  const content = extractContent(val, "group");
514
514
  try {
515
- return await sendOpenApiMessage(content, val, baseParams, (data) => client.postRichMediaByGroup(event.ChannelId, data), (data) => client.groupOpenMessages(event.ChannelId, data), "client.groupOpenMessages", options);
515
+ return await sendOpenApiMessage(content, val, baseParams, (data) => client.postRichMediaByGroup(event.ChannelId, data), (data) => client.groupOpenMessages(event.ChannelId, data), "client.groupOpenMessages", "group", options);
516
516
  } catch (err) {
517
517
  return [createResult(ResultCode.Fail, err?.response?.data ?? err?.message ?? err, null)];
518
518
  }
@@ -524,7 +524,7 @@ const C2C_MESSAGE_CREATE = async (client, event, val, options) => {
524
524
  const baseParams = buildBaseParams(event._tag, event.MessageId, "INTERACTION_CREATE_C2C");
525
525
  const content = extractContent(val, "group");
526
526
  try {
527
- return await sendOpenApiMessage(content, val, baseParams, (data) => client.postRichMediaByUser(event.UserId, data), (data) => client.usersOpenMessages(event.UserId, data), "client.usersOpenMessages", options);
527
+ return await sendOpenApiMessage(content, val, baseParams, (data) => client.postRichMediaByUser(event.UserId, data), (data) => client.usersOpenMessages(event.UserId, data), "client.usersOpenMessages", "c2c", options);
528
528
  } catch (err) {
529
529
  return [createResult(ResultCode.Fail, err?.response?.data ?? err?.message ?? err, null)];
530
530
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alemonjs/qq-bot",
3
- "version": "2.1.39",
3
+ "version": "2.1.40",
4
4
  "description": "QQ官方平台",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",
@@ -12,12 +12,13 @@
12
12
  "test:send": "yarn build && node tests/use-send.notice.mjs",
13
13
  "test:channel": "yarn --cwd ../alemonjs build && yarn build && node tests/channel.contract.mjs",
14
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",
15
+ "test:non-channel": "yarn build && node tests/markdown-buttons.contract.mjs && 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
16
  "docs:snapshot": "python3 scripts/snapshot-official.py",
17
17
  "test:http": "yarn build && node tests/http-diagnostics.contract.mjs && node tests/webhook-ingress.contract.mjs",
18
18
  "test:text": "yarn build && node tests/text-sanitizer.contract.mjs",
19
19
  "test:materials": "yarn build && node tests/materials.contract.mjs",
20
- "test:mention": "yarn build && node tests/everyone.contract.mjs"
20
+ "test:mention": "yarn build && node tests/everyone.contract.mjs",
21
+ "test:buttons": "yarn build && node tests/markdown-buttons.contract.mjs"
21
22
  },
22
23
  "dependencies": {
23
24
  "@noble/curves": "^1.7.0",
@@ -101,5 +102,5 @@
101
102
  "type": "git",
102
103
  "url": "https://github.com/lemonade-lab/alemonjs.git"
103
104
  },
104
- "gitHead": "114f342c08cb3ebbfee39b1c93fb30607d276083"
105
+ "gitHead": "c6aa5616afe091a37610dad22fbb2d2618d943b8"
105
106
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
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.