@zhin.js/adapter-qq 1.1.14 → 1.1.16

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 (54) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +31 -1
  3. package/agents/qq/messages.js +24 -0
  4. package/agents/qq/messages.ts +24 -0
  5. package/agents/qq/skills/qq/SKILL.md +16 -10
  6. package/agents/qq/skills/qq/tools/add_role/index.js +3 -1
  7. package/agents/qq/skills/qq/tools/add_role/index.ts +3 -1
  8. package/agents/qq/skills/qq/tools/channel_info/index.js +1 -0
  9. package/agents/qq/skills/qq/tools/channel_info/index.ts +1 -0
  10. package/agents/qq/skills/qq/tools/create_role/index.js +3 -1
  11. package/agents/qq/skills/qq/tools/create_role/index.ts +3 -1
  12. package/agents/qq/skills/qq/tools/list_channels/index.js +1 -0
  13. package/agents/qq/skills/qq/tools/list_channels/index.ts +1 -0
  14. package/agents/qq/skills/qq/tools/list_roles/index.js +1 -0
  15. package/agents/qq/skills/qq/tools/list_roles/index.ts +1 -0
  16. package/agents/qq/skills/qq/tools/member_detail/index.js +1 -0
  17. package/agents/qq/skills/qq/tools/member_detail/index.ts +1 -0
  18. package/agents/qq/skills/qq/tools/remove_role/index.js +3 -1
  19. package/agents/qq/skills/qq/tools/remove_role/index.ts +3 -1
  20. package/agents/qq/skills/qq-directory/SKILL.md +14 -0
  21. package/commands/qq/endpoint/cancel/index.js +6 -5
  22. package/commands/qq/endpoint/cancel/index.ts +5 -5
  23. package/commands/qq/endpoint/definition.js +32 -30
  24. package/commands/qq/endpoint/definition.ts +36 -31
  25. package/lib/endpoint.js +28 -36
  26. package/lib/i18n.d.ts +1 -0
  27. package/lib/i18n.js +72 -0
  28. package/lib/passive-reply.d.ts +61 -0
  29. package/lib/passive-reply.js +122 -0
  30. package/lib/qq-bind-flow.d.ts +2 -0
  31. package/lib/qq-bind-flow.js +2 -1
  32. package/lib/qq-bot-kind-prompt.d.ts +5 -6
  33. package/lib/qq-bot-kind-prompt.js +26 -26
  34. package/lib/qq-runtime-state.d.ts +6 -0
  35. package/lib/qq-send.d.ts +28 -0
  36. package/lib/qq-send.js +109 -0
  37. package/lib/webhook.js +4 -4
  38. package/lib/ws.d.ts +10 -4
  39. package/lib/ws.js +4 -4
  40. package/middlewares/bot-kind-reply/index.js +6 -5
  41. package/middlewares/bot-kind-reply/index.ts +6 -5
  42. package/package.json +18 -18
  43. package/schema.json +59 -9
  44. package/src/endpoint.ts +26 -34
  45. package/src/i18n.ts +73 -0
  46. package/src/passive-reply.ts +159 -0
  47. package/src/qq-bind-flow.ts +4 -1
  48. package/src/qq-bot-kind-prompt.ts +22 -37
  49. package/src/qq-runtime-state.ts +6 -0
  50. package/src/qq-send.ts +138 -0
  51. package/src/webhook.ts +4 -4
  52. package/src/ws.ts +15 -8
  53. /package/agents/qq/skills/{qq → qq-directory}/tools/list_guilds/index.js +0 -0
  54. /package/agents/qq/skills/{qq → qq-directory}/tools/list_guilds/index.ts +0 -0
@@ -1,37 +1,37 @@
1
+ import { conversationRefKey } from '@zhin.js/im-contract';
2
+ import { createDeliveryTarget } from 'zhin.js';
3
+ import { resolveLocale } from 'zhin.js/i18n';
4
+ import { messages as t } from './i18n.js';
1
5
  import { parseQqBotKind } from './qq-intents.js';
2
- /** 从命令/中间件入站消息提取会话键;无法识别时返回 undefined。 */
6
+ /** Bind replies to the complete canonical conversation and actual sender. */
3
7
  export function qqCommandSessionKey(input) {
8
+ if (!input || typeof input !== 'object')
9
+ return undefined;
4
10
  const message = input;
5
- const conversation = message?.conversation;
6
- const adapter = conversation?.endpoint?.adapter;
7
- const endpointKey = conversation?.endpoint?.id;
8
- const kind = conversation?.kind;
9
- const id = conversation?.id;
10
- const sender = message?.sender;
11
- if (adapter == null
12
- || endpointKey == null
13
- || (kind !== 'private' && kind !== 'group' && kind !== 'channel')
14
- || typeof id !== 'string'
15
- || !id
16
- || sender == null
17
- || String(sender).trim() === '') {
11
+ if (typeof message.sender?.id !== 'string' || !message.sender.id.trim())
12
+ return undefined;
13
+ try {
14
+ const conversation = createDeliveryTarget({ conversation: message.conversation });
15
+ return JSON.stringify([conversationRefKey(conversation), message.sender.id]);
16
+ }
17
+ catch {
18
18
  return undefined;
19
19
  }
20
- return `${String(adapter)}\0${String(endpointKey)}\0${kind}\0${id}\0${String(sender).trim()}`;
21
20
  }
22
- /**
23
- * 解析用户对公域/私域的回复。
24
- * 接受:public / private / 公域 / 私域(大小写不敏感,可带前后空白)。
25
- */
26
- export function parseQqBotKindAnswer(raw) {
21
+ /** Public/private are stable botKind values; Chinese aliases apply only to Chinese flows. */
22
+ export function parseQqBotKindAnswer(raw, locale) {
27
23
  const text = String(raw ?? '').trim().toLowerCase();
28
24
  if (!text)
29
25
  return undefined;
30
- if (text === '公域' || text === '公开' || text === '1')
31
- return 'public';
32
- if (text === '私域' || text === '2')
33
- return 'private';
26
+ if (resolveLocale(locale) === 'zh-CN') {
27
+ if (text === '公域' || text === '公开' || text === '1')
28
+ return 'public';
29
+ if (text === '私域' || text === '2')
30
+ return 'private';
31
+ }
34
32
  return parseQqBotKind(text);
35
33
  }
36
- export const QQ_BOT_KIND_PROMPT = '请回复 public(公域)或 private(私域),确认后一次性写入 .env 与配置。\n'
37
- + '也可发 qq endpoint cancel 取消。';
34
+ export function qqBotKindPrompt(locale, cancelCommand) {
35
+ return t(locale, 'kindPrompt') + (cancelCommand ? '\n' + t(locale, 'cancelHint', { command: cancelCommand }) : '');
36
+ }
37
+ export const QQ_BOT_KIND_PROMPT = qqBotKindPrompt();
@@ -6,6 +6,9 @@ export interface QqBindFlowHandle {
6
6
  /** 绑定期望的 endpoint id(未指定时为 undefined,成功后取 appId) */
7
7
  id?: string;
8
8
  stop: () => void;
9
+ readonly locale?: import('zhin.js/i18n').Locale;
10
+ readonly cancelCommand?: string;
11
+ readonly commandHeads?: readonly string[];
9
12
  }
10
13
  /**
11
14
  * 扫码成功后暂存在内存:确认公域/私域后再一次性写 .env + yaml。
@@ -17,6 +20,9 @@ export interface QqPendingBotKind {
17
20
  readonly appSecret: string;
18
21
  /** 发起 add 的会话键;仅同会话回复可完成选择 */
19
22
  readonly sessionKey: string;
23
+ readonly locale?: import('zhin.js/i18n').Locale;
24
+ readonly cancelCommand?: string;
25
+ readonly commandHeads?: readonly string[];
20
26
  }
21
27
  export interface QqRuntimeState {
22
28
  /** 当前 generation 已成功创建的 endpoint(id → 描述) */
@@ -0,0 +1,28 @@
1
+ /**
2
+ * One QQ outbound send: passive reply when a usable source exists, active message otherwise, with
3
+ * the fallbacks the platform's error codes allow. Shared by the websocket and webhook Endpoints.
4
+ *
5
+ * 1. Each message claims a passive source (explicit reply segment first, else the conversation's
6
+ * newest usable msg_id / event_id) with its own msg_seq.
7
+ * 2. Source unusable (expired, over the limit, invalid id): mark it and send that message once as
8
+ * an active message. Never after a timeout: the first request may have been delivered.
9
+ * 3. Markdown not allowed: resend the same message as plain text.
10
+ * 4. Active message refused (no permission, quota, user switched it off): remember the conversation
11
+ * for a while and fail fast instead of calling the platform again.
12
+ */
13
+ import type { ConversationRef } from '@zhin.js/im-contract';
14
+ import type { QqOutboundMessage } from './outbound.js';
15
+ import { type QqActiveMessageGate, type QqPassiveReplyLedger, type QqReplySource } from './passive-reply.js';
16
+ import type { QqBotTransport } from './ws.js';
17
+ export interface QqSendContext {
18
+ readonly bot: QqBotTransport;
19
+ readonly ledger: QqPassiveReplyLedger;
20
+ readonly activeGate: QqActiveMessageGate;
21
+ readonly log: (fields: Record<string, string | number | undefined>) => void;
22
+ }
23
+ /** The reply segment the payload already names, as a passive source. */
24
+ export declare function explicitReplySource(message: QqOutboundMessage): QqReplySource | undefined;
25
+ /** Plain-text version of a Markdown message (buttons dropped); undefined when it has no Markdown. */
26
+ export declare function markdownAsText(message: QqOutboundMessage): QqOutboundMessage | undefined;
27
+ /** Sends every message of one delivery in order; returns the last platform result. */
28
+ export declare function sendQqMessages(context: QqSendContext, conversation: ConversationRef, messages: readonly QqOutboundMessage[]): Promise<unknown>;
package/lib/qq-send.js ADDED
@@ -0,0 +1,109 @@
1
+ import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
+ import { QQ_ACTIVE_UNAVAILABLE_CODES, QQ_MARKDOWN_DENIED_CODES, QQ_PASSIVE_SOURCE_CODES, } from './passive-reply.js';
3
+ import { qqDeliveryFailure } from './delivery-error.js';
4
+ import { qqOutboundKind } from './protocol.js';
5
+ function elements(message) {
6
+ return Array.isArray(message) ? [...message] : [message];
7
+ }
8
+ /** The reply segment the payload already names, as a passive source. */
9
+ export function explicitReplySource(message) {
10
+ for (const part of elements(message)) {
11
+ if (typeof part === 'string' || part.type !== 'reply')
12
+ continue;
13
+ if (typeof part.data.event_id === 'string' && part.data.event_id)
14
+ return { event_id: part.data.event_id };
15
+ if (typeof part.data.id === 'string' && part.data.id)
16
+ return { id: part.data.id };
17
+ }
18
+ return undefined;
19
+ }
20
+ /** Plain-text version of a Markdown message (buttons dropped); undefined when it has no Markdown. */
21
+ export function markdownAsText(message) {
22
+ const parts = elements(message);
23
+ if (!parts.some(part => typeof part !== 'string' && (part.type === 'markdown' || part.type === 'keyboard' || part.type === 'button')))
24
+ return undefined;
25
+ const text = parts.map(part => {
26
+ if (typeof part === 'string')
27
+ return part;
28
+ if (part.type === 'text')
29
+ return String(part.data.text ?? '');
30
+ if (part.type === 'markdown') {
31
+ return String(part.data.content ?? '')
32
+ .replace(/!\[[^\]\n]{0,500}\]\(([^)\s]{1,2000})\)/g, '$1')
33
+ .replace(/\[([^\]\n]{1,500})\]\(([^)\s]{1,2000})\)/g, '$1 $2')
34
+ .replace(/^#{1,6}\s+/gm, '')
35
+ .replace(/(\*\*|__|~~|`)/g, '');
36
+ }
37
+ return '';
38
+ }).join('').trim();
39
+ const kept = parts.filter((part) => typeof part !== 'string' && (part.type === 'reply' || part.type === 'at'));
40
+ return text ? [...kept, text] : undefined;
41
+ }
42
+ function dispatch(bot, kind, id, message, source, options) {
43
+ switch (kind) {
44
+ case 'private': return bot.sendPrivateMessage(id, message, source, options);
45
+ case 'group': return bot.sendGroupMessage(id, message, source, options);
46
+ case 'channel': return bot.sendGuildMessage(id, message, source, options);
47
+ case 'direct':
48
+ if (!bot.sendDirectMessage)
49
+ throw new Error('QQ direct message not supported by transport');
50
+ return bot.sendDirectMessage(id, message, source, options);
51
+ }
52
+ }
53
+ function platformCode(error) {
54
+ return qqDeliveryFailure(error).diagnostic.platformCode;
55
+ }
56
+ /** Sends one message, passive if possible; returns the platform result. */
57
+ async function sendOne(context, kind, id, message) {
58
+ const passive = (body, grant) => dispatch(context.bot, kind, id, body, grant.source, { msgSeq: grant.msgSeq });
59
+ const active = async (body) => {
60
+ if (context.activeGate.blocked(kind, id)) {
61
+ throw new EndpointDeliveryError('platform_rejected', 'QQ active messages are unavailable for this conversation', 'not_sent');
62
+ }
63
+ try {
64
+ return await dispatch(context.bot, kind, id, body);
65
+ }
66
+ catch (error) {
67
+ const code = platformCode(error);
68
+ if (code !== undefined && QQ_ACTIVE_UNAVAILABLE_CODES.has(code) && context.activeGate.block(kind, id)) {
69
+ context.log({ op: 'qq_active_unavailable', target: `${kind}:${id}`, platformCode: code });
70
+ }
71
+ throw error;
72
+ }
73
+ };
74
+ /** Markdown refused: the same message once more as plain text, under a fresh msg_seq when passive. */
75
+ const asText = (error, previous) => {
76
+ const code = platformCode(error);
77
+ const text = code !== undefined && QQ_MARKDOWN_DENIED_CODES.has(code) ? markdownAsText(message) : undefined;
78
+ if (!text)
79
+ throw error;
80
+ context.log({ op: 'qq_markdown_denied_as_text', target: `${kind}:${id}`, platformCode: code });
81
+ const retry = previous ? context.ledger.acquire(kind, id, previous.source) : undefined;
82
+ return retry ? passive(text, retry) : active(text);
83
+ };
84
+ const activeOrText = (body) => active(body).catch((error) => asText(error, undefined));
85
+ const grant = context.ledger.acquire(kind, id, explicitReplySource(message));
86
+ if (!grant)
87
+ return activeOrText(message);
88
+ try {
89
+ return await passive(message, grant);
90
+ }
91
+ catch (error) {
92
+ const code = platformCode(error);
93
+ if (code !== undefined && QQ_PASSIVE_SOURCE_CODES.has(code)) {
94
+ context.ledger.exhaust(kind, id, grant.source);
95
+ context.log({ op: 'qq_passive_source_unusable', target: `${kind}:${id}`, platformCode: code });
96
+ return activeOrText(message);
97
+ }
98
+ // Anything else, including a timeout (outcome unknown), is not retried as an active message.
99
+ return asText(error, grant);
100
+ }
101
+ }
102
+ /** Sends every message of one delivery in order; returns the last platform result. */
103
+ export async function sendQqMessages(context, conversation, messages) {
104
+ const kind = qqOutboundKind(conversation);
105
+ let result;
106
+ for (const message of messages)
107
+ result = await sendOne(context, kind, conversation.id, message);
108
+ return result;
109
+ }
package/lib/webhook.js CHANGED
@@ -73,10 +73,10 @@ export function defaultCreateHttpBot(config) {
73
73
  removeAllListeners: () => {
74
74
  bot.removeAllListeners(undefined);
75
75
  },
76
- sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message),
77
- sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
78
- sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
79
- sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
76
+ sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
77
+ sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
78
+ sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
79
+ sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
80
80
  recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
81
81
  recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
82
82
  recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
package/lib/ws.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { Bot, type ApproveJoinRequestOptions } from 'qq-official-bot';
2
2
  import type { EndpointTransportState } from 'zhin.js/adapter';
3
3
  import type { QqOutboundMessage } from './outbound.js';
4
+ import type { QqReplySource } from './passive-reply.js';
5
+ /** Per-send options. `msgSeq` numbers replies to one passive source (qq-official-bot ≥ 1.3.3). */
6
+ export interface QqSendOptions {
7
+ readonly msgSeq?: number;
8
+ }
4
9
  import type { QqInboundMessage, ResolvedQqWebsocketConfig } from './protocol.js';
5
10
  /** SDK business surface exposed to plugins without bypassing Endpoint lifecycle ownership. */
6
11
  export type QqOfficialApi = Pick<Bot, 'messageService' | 'fileProcessor' | 'guildService' | 'channelService' | 'memberService' | 'permissionService' | 'reactionService' | 'scheduleService' | 'threadService' | 'audioService' | 'botService' | 'groupService' | 'menuPanelService' | 'group' | 'user' | 'channel' | 'direct' | 'guild'>;
@@ -13,10 +18,11 @@ export interface QqBotTransport {
13
18
  stop(): Promise<void>;
14
19
  on(event: string, listener: (...args: unknown[]) => void): void;
15
20
  removeAllListeners(): void;
16
- sendPrivateMessage(userId: string, message: QqOutboundMessage): Promise<unknown>;
17
- sendGroupMessage(groupId: string, message: QqOutboundMessage): Promise<unknown>;
18
- sendGuildMessage(channelId: string, message: QqOutboundMessage): Promise<unknown>;
19
- sendDirectMessage?(guildId: string, message: QqOutboundMessage): Promise<unknown>;
21
+ /** `source` makes it a passive reply; without it the platform treats the message as active. */
22
+ sendPrivateMessage(userId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
23
+ sendGroupMessage(groupId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
24
+ sendGuildMessage(channelId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
25
+ sendDirectMessage?(guildId: string, message: QqOutboundMessage, source?: QqReplySource, options?: QqSendOptions): Promise<unknown>;
20
26
  recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
21
27
  recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
22
28
  recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
package/lib/ws.js CHANGED
@@ -240,10 +240,10 @@ export function defaultCreateBot(config) {
240
240
  receiver.removeListener(entry.event, entry.listener);
241
241
  bot.removeAllListeners(undefined);
242
242
  },
243
- sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message),
244
- sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message),
245
- sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message),
246
- sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message),
243
+ sendPrivateMessage: (userId, message, source, options) => bot.sendPrivateMessage(userId, message, source, options),
244
+ sendGroupMessage: (groupId, message, source, options) => bot.sendGroupMessage(groupId, message, source, options),
245
+ sendGuildMessage: (channelId, message, source, options) => bot.sendGuildMessage(channelId, message, source, options),
246
+ sendDirectMessage: (guildId, message, source, options) => bot.sendDirectMessage(guildId, message, source, options),
247
247
  recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
248
248
  recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
249
249
  recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
@@ -1,8 +1,9 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { messages as t } from "../../lib/i18n.js";
2
3
  import { defineMiddleware } from 'zhin.js/middleware';
3
4
  import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
4
5
  import { completeQqPendingBotKind, } from '../../commands/qq/endpoint/definition.js';
5
- import { parseQqBotKindAnswer, qqCommandSessionKey, QQ_BOT_KIND_PROMPT, } from "../../lib/qq-bot-kind-prompt.js";
6
+ import { parseQqBotKindAnswer, qqCommandSessionKey, qqBotKindPrompt, } from "../../lib/qq-bot-kind-prompt.js";
6
7
  import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
7
8
  /**
8
9
  * 扫码绑定成功后,在同一会话收取 public/private(或 公域/私域)回复并写入 intents。
@@ -26,13 +27,13 @@ export default defineMiddleware({
26
27
  }
27
28
  const raw = String(context.input.content ?? '').trim();
28
29
  // 让 qq endpoint cancel 等命令继续走命令链
29
- if (raw.toLowerCase().startsWith('qq endpoint')) {
30
+ if ([...(pending.commandHeads ?? []), ...(pending.cancelCommand ? [pending.cancelCommand] : [])].some(head => raw.toLowerCase() === head.toLowerCase() || raw.toLowerCase().startsWith(`${head.toLowerCase()} `))) {
30
31
  await next();
31
32
  return;
32
33
  }
33
- const botKind = parseQqBotKindAnswer(raw);
34
+ const botKind = parseQqBotKindAnswer(raw, pending.locale);
34
35
  if (!botKind) {
35
- await context.input.$reply?.(`未识别「${raw}」。${QQ_BOT_KIND_PROMPT}`);
36
+ await context.input.$reply?.(t(pending.locale, 'kindUnrecognized', { raw, prompt: qqBotKindPrompt(pending.locale, pending.cancelCommand) }));
36
37
  return;
37
38
  }
38
39
  try {
@@ -41,7 +42,7 @@ export default defineMiddleware({
41
42
  await context.input.$reply?.(text);
42
43
  }
43
44
  catch (error) {
44
- await context.input.$reply?.(`写入配置失败:${error instanceof Error ? error.message : String(error)}`);
45
+ await context.input.$reply?.(t(pending.locale, 'configFailed', { error: error instanceof Error ? error.message : String(error) }));
45
46
  }
46
47
  },
47
48
  });
@@ -1,3 +1,4 @@
1
+ import { messages as t } from '../../src/i18n.js';
1
2
  import { defineMiddleware } from 'zhin.js/middleware';
2
3
  import type { CommandMessage } from 'zhin.js/command';
3
4
  import { endpointConfigurationStoreToken } from 'zhin.js/adapter';
@@ -7,7 +8,7 @@ import {
7
8
  import {
8
9
  parseQqBotKindAnswer,
9
10
  qqCommandSessionKey,
10
- QQ_BOT_KIND_PROMPT,
11
+ qqBotKindPrompt,
11
12
  } from '../../src/qq-bot-kind-prompt.js';
12
13
  import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
13
14
 
@@ -35,15 +36,15 @@ export default defineMiddleware<CommandMessage>({
35
36
 
36
37
  const raw = String(context.input.content ?? '').trim();
37
38
  // 让 qq endpoint cancel 等命令继续走命令链
38
- if (raw.toLowerCase().startsWith('qq endpoint')) {
39
+ if ([...(pending.commandHeads ?? []), ...(pending.cancelCommand ? [pending.cancelCommand] : [])].some(head => raw.toLowerCase() === head.toLowerCase() || raw.toLowerCase().startsWith(`${head.toLowerCase()} `))) {
39
40
  await next();
40
41
  return;
41
42
  }
42
43
 
43
- const botKind = parseQqBotKindAnswer(raw);
44
+ const botKind = parseQqBotKindAnswer(raw, pending.locale);
44
45
  if (!botKind) {
45
46
  await context.input.$reply?.(
46
- `未识别「${raw}」。${QQ_BOT_KIND_PROMPT}`,
47
+ t(pending.locale, 'kindUnrecognized', { raw, prompt: qqBotKindPrompt(pending.locale, pending.cancelCommand) }),
47
48
  );
48
49
  return;
49
50
  }
@@ -58,7 +59,7 @@ export default defineMiddleware<CommandMessage>({
58
59
  await context.input.$reply?.(text);
59
60
  } catch (error) {
60
61
  await context.input.$reply?.(
61
- `写入配置失败:${error instanceof Error ? error.message : String(error)}`,
62
+ t(pending.locale, 'configFailed', { error: error instanceof Error ? error.message : String(error) }),
62
63
  );
63
64
  }
64
65
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-qq",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,25 +33,25 @@
33
33
  "directory": "plugins/adapters/qq"
34
34
  },
35
35
  "dependencies": {
36
- "qq-official-bot": "^1.3.1",
37
- "@zhin.js/adapter": "1.1.21",
38
- "@zhin.js/agent-feature": "1.1.8",
39
- "@zhin.js/core": "1.1.49",
40
- "@zhin.js/feature-kit": "1.1.7",
41
- "@zhin.js/host-http": "1.1.8",
42
- "@zhin.js/im-contract": "1.1.5",
36
+ "qq-official-bot": "^1.3.3",
37
+ "@zhin.js/adapter": "1.1.23",
38
+ "@zhin.js/agent-feature": "1.1.10",
39
+ "@zhin.js/core": "1.1.51",
40
+ "@zhin.js/feature-kit": "1.1.9",
41
+ "@zhin.js/host-http": "1.1.10",
42
+ "@zhin.js/im-contract": "1.1.7",
43
43
  "@zhin.js/logger": "1.1.2",
44
- "@zhin.js/permission": "1.1.7",
45
- "@zhin.js/skill": "1.1.8",
46
- "@zhin.js/tool": "1.1.8"
44
+ "@zhin.js/permission": "1.1.9",
45
+ "@zhin.js/skill": "1.1.10",
46
+ "@zhin.js/tool": "1.1.10"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "zod": "^4.0.0",
50
- "@zhin.js/adapter": "^1.1.21",
51
- "@zhin.js/command": "^1.1.9",
52
- "@zhin.js/core": "^1.1.49",
53
- "@zhin.js/middleware": "^1.1.8",
54
- "zhin.js": "^1.1.14"
50
+ "@zhin.js/adapter": "^1.1.23",
51
+ "@zhin.js/command": "^1.1.11",
52
+ "@zhin.js/core": "^1.1.51",
53
+ "@zhin.js/middleware": "^1.1.10",
54
+ "zhin.js": "^1.1.16"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "@zhin.js/command": {
@@ -72,8 +72,8 @@
72
72
  "typescript": "^6.0.3",
73
73
  "vitest": "^4.1.10",
74
74
  "zod": "^4.4.3",
75
- "@zhin.js/host-http": "1.1.8",
76
- "zhin.js": "1.1.14"
75
+ "@zhin.js/host-http": "1.1.10",
76
+ "zhin.js": "1.1.16"
77
77
  },
78
78
  "files": [
79
79
  "adapters",
package/schema.json CHANGED
@@ -24,14 +24,24 @@
24
24
  "private"
25
25
  ],
26
26
  "default": "public",
27
- "description": "公域/私域:均含 GROUP_AND_C2C_EVENT;频道消息分别为 PUBLIC_GUILD_MESSAGES / GUILD_MESSAGES。未显式配置 intents 时按此展开"
27
+ "description": "公域/私域:均含 GROUP_AND_C2C_EVENT;频道消息分别为 PUBLIC_GUILD_MESSAGES / GUILD_MESSAGES。未显式配置 intents 时按此展开",
28
+ "x-i18n": {
29
+ "en": {
30
+ "description": "Public/private bot type. Both include GROUP_AND_C2C_EVENT; channel messages use PUBLIC_GUILD_MESSAGES or GUILD_MESSAGES respectively. Used to derive intents when they are not explicitly configured."
31
+ }
32
+ }
28
33
  },
29
34
  "master": {
30
35
  "type": [
31
36
  "string",
32
37
  "number"
33
38
  ],
34
- "description": "框架 master(endpoint 管理命令、AI/工具权限);openid。endpoints[i].master 可逐项覆盖"
39
+ "description": "框架 master(endpoint 管理命令、AI/工具权限);openid。endpoints[i].master 可逐项覆盖",
40
+ "x-i18n": {
41
+ "en": {
42
+ "description": "Framework masters (openid) for endpoint management and AI/tool permissions. endpoints[i].master can override this value."
43
+ }
44
+ }
35
45
  },
36
46
  "trusted": {
37
47
  "type": "array",
@@ -42,14 +52,24 @@
42
52
  ],
43
53
  "description": "Trusted openid"
44
54
  },
45
- "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
55
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加",
56
+ "x-i18n": {
57
+ "en": {
58
+ "description": "Framework trusted users, below master authority. endpoints[i].trusted can add users."
59
+ }
60
+ }
46
61
  },
47
62
  "intents": {
48
63
  "type": "array",
49
64
  "items": {
50
65
  "type": "string"
51
66
  },
52
- "description": "WebSocket Identify intents;省略时按 botKind 自动生成(public→含 PUBLIC_GUILD_MESSAGES)"
67
+ "description": "WebSocket Identify intents;省略时按 botKind 自动生成(public→含 PUBLIC_GUILD_MESSAGES)",
68
+ "x-i18n": {
69
+ "en": {
70
+ "description": "WebSocket Identify intents. When omitted, derived from botKind; public includes PUBLIC_GUILD_MESSAGES."
71
+ }
72
+ }
53
73
  },
54
74
  "accessTokenUrl": {
55
75
  "type": "string"
@@ -94,7 +114,12 @@
94
114
  "string",
95
115
  "number"
96
116
  ],
97
- "description": "本 endpoint 的框架 master(openid);覆盖顶层 master"
117
+ "description": "本 endpoint 的框架 master(openid);覆盖顶层 master",
118
+ "x-i18n": {
119
+ "en": {
120
+ "description": "Framework masters for this endpoint (openid), overriding the top-level master list."
121
+ }
122
+ }
98
123
  },
99
124
  "trusted": {
100
125
  "type": "array",
@@ -105,7 +130,12 @@
105
130
  ],
106
131
  "description": "Trusted openid"
107
132
  },
108
- "description": "本 endpoint 的 trusted 列表"
133
+ "description": "本 endpoint 的 trusted 列表",
134
+ "x-i18n": {
135
+ "en": {
136
+ "description": "Trusted users for this endpoint."
137
+ }
138
+ }
109
139
  },
110
140
  "botKind": {
111
141
  "type": "string",
@@ -113,14 +143,24 @@
113
143
  "public",
114
144
  "private"
115
145
  ],
116
- "description": "覆盖顶层 botKind(公域/私域),用于按 endpoint 生成 intents"
146
+ "description": "覆盖顶层 botKind(公域/私域),用于按 endpoint 生成 intents",
147
+ "x-i18n": {
148
+ "en": {
149
+ "description": "Override top-level botKind (public/private) when deriving this endpoint's intents."
150
+ }
151
+ }
117
152
  },
118
153
  "intents": {
119
154
  "type": "array",
120
155
  "items": {
121
156
  "type": "string"
122
157
  },
123
- "description": "覆盖顶层 intents"
158
+ "description": "覆盖顶层 intents",
159
+ "x-i18n": {
160
+ "en": {
161
+ "description": "Override top-level intents."
162
+ }
163
+ }
124
164
  },
125
165
  "accessTokenUrl": {
126
166
  "type": "string"
@@ -164,12 +204,22 @@
164
204
  "appid",
165
205
  "secret"
166
206
  ]
207
+ },
208
+ "x-i18n": {
209
+ "en": {
210
+ "description": "Multiple accounts: each entry defines an endpoint. id, appid and secret are required; other fields override instance defaults."
211
+ }
167
212
  }
168
213
  },
169
214
  "commandPrefix": {
170
215
  "type": "string",
171
216
  "default": "",
172
- "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
217
+ "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖",
218
+ "x-i18n": {
219
+ "en": {
220
+ "description": "Command prefix. The default empty string allows unprefixed commands; '/' requires a leading slash. Each endpoints[i] can override it."
221
+ }
222
+ }
173
223
  },
174
224
  "streamProxy": {
175
225
  "type": "object",