@zhin.js/adapter-qq 1.1.15 → 1.1.17
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/CHANGELOG.md +47 -0
- package/README.md +6 -0
- package/agents/qq/messages.js +24 -0
- package/agents/qq/messages.ts +24 -0
- package/agents/qq/skills/qq/tools/add_role/index.js +2 -1
- package/agents/qq/skills/qq/tools/add_role/index.ts +2 -1
- package/agents/qq/skills/qq/tools/create_role/index.js +2 -1
- package/agents/qq/skills/qq/tools/create_role/index.ts +2 -1
- package/agents/qq/skills/qq/tools/remove_role/index.js +2 -1
- package/agents/qq/skills/qq/tools/remove_role/index.ts +2 -1
- package/commands/qq/endpoint/cancel/index.js +6 -5
- package/commands/qq/endpoint/cancel/index.ts +5 -5
- package/commands/qq/endpoint/definition.js +32 -30
- package/commands/qq/endpoint/definition.ts +36 -31
- package/lib/i18n.d.ts +1 -0
- package/lib/i18n.js +72 -0
- package/lib/qq-bind-flow.d.ts +2 -0
- package/lib/qq-bind-flow.js +2 -1
- package/lib/qq-bot-kind-prompt.d.ts +5 -6
- package/lib/qq-bot-kind-prompt.js +26 -26
- package/lib/qq-runtime-state.d.ts +6 -0
- package/middlewares/bot-kind-reply/index.js +6 -5
- package/middlewares/bot-kind-reply/index.ts +6 -5
- package/package.json +17 -17
- package/schema.json +59 -9
- package/src/i18n.ts +73 -0
- package/src/qq-bind-flow.ts +4 -1
- package/src/qq-bot-kind-prompt.ts +22 -37
- package/src/qq-runtime-state.ts +6 -0
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
export const messages = createMessages({
|
|
3
|
+
bindTaskFailed: { 'zh-CN': '获取绑定任务失败: {error}', en: 'Could not create the binding task: {error}' },
|
|
4
|
+
"kindPrompt": {
|
|
5
|
+
"zh-CN": "请回复 public(公域)或 private(私域),确认后一次性写入 .env 与配置。",
|
|
6
|
+
"en": "Reply public or private to save .env and configuration together."
|
|
7
|
+
},
|
|
8
|
+
"cancelHint": {
|
|
9
|
+
"zh-CN": "也可发 {command} 取消。",
|
|
10
|
+
"en": "Send {command} to cancel."
|
|
11
|
+
},
|
|
12
|
+
"bindingBusy": {
|
|
13
|
+
"zh-CN": "⚠️ 有进行中的扫码绑定,请先取消当前绑定流程",
|
|
14
|
+
"en": "⚠️ A QR binding is in progress; cancel it before starting another"
|
|
15
|
+
},
|
|
16
|
+
"kindBusy": {
|
|
17
|
+
"zh-CN": "⚠️ endpoint「{id}」等待公域/私域选择,请回复 public/private",
|
|
18
|
+
"en": "⚠️ Endpoint “{id}” is waiting for public/private selection"
|
|
19
|
+
},
|
|
20
|
+
"endpointSaved": {
|
|
21
|
+
"zh-CN": "✅ 已按 botKind={kind} 将 endpoint「{id}」写入 .env 与 {file}。\nintents: {intents}。\n⚠️ 需重启 zhin 后新 endpoint 才会生效。",
|
|
22
|
+
"en": "✅ Saved endpoint “{id}” with botKind={kind} to .env and {file}.\nintents: {intents}.\n⚠️ Restart zhin to activate the new endpoint."
|
|
23
|
+
},
|
|
24
|
+
"existingFlow": {
|
|
25
|
+
"zh-CN": "已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先取消后再试",
|
|
26
|
+
"en": "A QQ binding or public/private selection is already in progress; cancel it before trying again"
|
|
27
|
+
},
|
|
28
|
+
"scanLink": {
|
|
29
|
+
"zh-CN": "请用手机 QQ 打开以下链接完成扫码绑定(二维码图片出站暂未支持,故发送链接):\n{url}",
|
|
30
|
+
"en": "Open this link in mobile QQ to scan and bind the bot:\n{url}"
|
|
31
|
+
},
|
|
32
|
+
"qrExpired": {
|
|
33
|
+
"zh-CN": "二维码已过期,正在刷新,请扫描新链接…",
|
|
34
|
+
"en": "The QR code expired. Refreshing; scan the new link…"
|
|
35
|
+
},
|
|
36
|
+
"noInteractiveKind": {
|
|
37
|
+
"zh-CN": "\n(当前会话无法交互选择公/私域;私域请手动改配置。)",
|
|
38
|
+
"en": "\n(This source cannot answer public/private selection. Edit the configuration manually for a private bot.)"
|
|
39
|
+
},
|
|
40
|
+
"scanned": {
|
|
41
|
+
"zh-CN": "✅ 扫码成功!endpoint「{id}」。\n{prompt}",
|
|
42
|
+
"en": "✅ QR binding succeeded for endpoint “{id}”.\n{prompt}"
|
|
43
|
+
},
|
|
44
|
+
"writeFailed": {
|
|
45
|
+
"zh-CN": "绑定成功但写入配置失败:{error}",
|
|
46
|
+
"en": "Binding succeeded but configuration could not be saved: {error}"
|
|
47
|
+
},
|
|
48
|
+
"bindingFailed": {
|
|
49
|
+
"zh-CN": "❌ QQ 绑定失败:{error}",
|
|
50
|
+
"en": "❌ QQ binding failed: {error}"
|
|
51
|
+
},
|
|
52
|
+
"cancelled": {
|
|
53
|
+
"zh-CN": "已取消进行中的 QQ 绑定流程",
|
|
54
|
+
"en": "The active QQ binding was cancelled"
|
|
55
|
+
},
|
|
56
|
+
"pendingCancelled": {
|
|
57
|
+
"zh-CN": "已取消 endpoint「{name}」的绑定(尚未写入任何文件)",
|
|
58
|
+
"en": "Binding for endpoint “{name}” cancelled; no files were written"
|
|
59
|
+
},
|
|
60
|
+
"noFlow": {
|
|
61
|
+
"zh-CN": "当前没有进行中的 QQ 绑定流程",
|
|
62
|
+
"en": "No QQ binding is in progress"
|
|
63
|
+
},
|
|
64
|
+
"kindUnrecognized": {
|
|
65
|
+
"zh-CN": "未识别「{raw}」。{prompt}",
|
|
66
|
+
"en": "Unrecognized answer “{raw}”. {prompt}"
|
|
67
|
+
},
|
|
68
|
+
"configFailed": {
|
|
69
|
+
"zh-CN": "写入配置失败:{error}",
|
|
70
|
+
"en": "Could not save configuration: {error}"
|
|
71
|
+
}
|
|
72
|
+
});
|
package/lib/qq-bind-flow.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Locale } from 'zhin.js/i18n';
|
|
1
2
|
export interface QqBindCredentials {
|
|
2
3
|
appId: string;
|
|
3
4
|
appSecret: string;
|
|
@@ -10,6 +11,7 @@ export interface QqBindCallbacks {
|
|
|
10
11
|
onQrExpired?: () => void | Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
export interface QqBindFlowOptions {
|
|
14
|
+
locale?: Locale;
|
|
13
15
|
signal?: AbortSignal;
|
|
14
16
|
source?: string;
|
|
15
17
|
}
|
package/lib/qq-bind-flow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messages as t } from './i18n.js';
|
|
1
2
|
/**
|
|
2
3
|
* QQ Bot 扫码绑定流程(内联,等价 qqbot-connector startQrConnect)
|
|
3
4
|
*/
|
|
@@ -60,7 +61,7 @@ export function startQqBindFlow(callbacks, options = {}) {
|
|
|
60
61
|
task = await createBindTask();
|
|
61
62
|
}
|
|
62
63
|
catch (err) {
|
|
63
|
-
throw new Error(
|
|
64
|
+
throw new Error(t(options.locale, 'bindTaskFailed', { error: err instanceof Error ? err.message : String(err) }), { cause: err });
|
|
64
65
|
}
|
|
65
66
|
const connectUrl = buildConnectUrl(task.taskId, options.source ?? 'zhin');
|
|
66
67
|
await callbacks.onQrDisplayed?.(connectUrl);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import { type QqBotKind } from './qq-intents.js';
|
|
2
|
-
/**
|
|
3
|
+
/** Bind replies to the complete canonical conversation and actual sender. */
|
|
3
4
|
export declare function qqCommandSessionKey(input: unknown): string | undefined;
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
export declare function parseQqBotKindAnswer(raw: unknown): QqBotKind | undefined;
|
|
5
|
+
/** Public/private are stable botKind values; Chinese aliases apply only to Chinese flows. */
|
|
6
|
+
export declare function parseQqBotKindAnswer(raw: unknown, locale?: Locale): QqBotKind | undefined;
|
|
7
|
+
export declare function qqBotKindPrompt(locale?: Locale, cancelCommand?: string): string;
|
|
9
8
|
export declare const QQ_BOT_KIND_PROMPT: string;
|
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
37
|
-
+ '
|
|
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 → 描述) */
|
|
@@ -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,
|
|
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(
|
|
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?.(
|
|
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?.(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"qq-official-bot": "^1.3.3",
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/agent-feature": "1.1.
|
|
39
|
-
"@zhin.js/core": "1.1.
|
|
40
|
-
"@zhin.js/feature-kit": "1.1.
|
|
41
|
-
"@zhin.js/host-http": "1.1.
|
|
42
|
-
"@zhin.js/im-contract": "1.1.
|
|
37
|
+
"@zhin.js/adapter": "1.1.24",
|
|
38
|
+
"@zhin.js/agent-feature": "1.1.10",
|
|
39
|
+
"@zhin.js/core": "1.1.52",
|
|
40
|
+
"@zhin.js/feature-kit": "1.1.9",
|
|
41
|
+
"@zhin.js/host-http": "1.1.11",
|
|
42
|
+
"@zhin.js/im-contract": "1.1.8",
|
|
43
43
|
"@zhin.js/logger": "1.1.2",
|
|
44
|
-
"@zhin.js/permission": "1.1.
|
|
45
|
-
"@zhin.js/skill": "1.1.
|
|
46
|
-
"@zhin.js/tool": "1.1.
|
|
44
|
+
"@zhin.js/permission": "1.1.9",
|
|
45
|
+
"@zhin.js/skill": "1.1.10",
|
|
46
|
+
"@zhin.js/tool": "1.1.11"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"zod": "^4.0.0",
|
|
50
|
-
"@zhin.js/adapter": "^1.1.
|
|
51
|
-
"@zhin.js/command": "^1.1.
|
|
52
|
-
"@zhin.js/core": "^1.1.
|
|
53
|
-
"@zhin.js/middleware": "^1.1.
|
|
54
|
-
"zhin.js": "^1.1.
|
|
50
|
+
"@zhin.js/adapter": "^1.1.24",
|
|
51
|
+
"@zhin.js/command": "^1.1.11",
|
|
52
|
+
"@zhin.js/core": "^1.1.52",
|
|
53
|
+
"@zhin.js/middleware": "^1.1.10",
|
|
54
|
+
"zhin.js": "^1.1.17"
|
|
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.
|
|
76
|
-
"zhin.js": "1.1.
|
|
75
|
+
"@zhin.js/host-http": "1.1.11",
|
|
76
|
+
"zhin.js": "1.1.17"
|
|
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",
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
|
|
3
|
+
export const messages = createMessages({
|
|
4
|
+
bindTaskFailed: { 'zh-CN': '获取绑定任务失败: {error}', en: 'Could not create the binding task: {error}' },
|
|
5
|
+
"kindPrompt": {
|
|
6
|
+
"zh-CN": "请回复 public(公域)或 private(私域),确认后一次性写入 .env 与配置。",
|
|
7
|
+
"en": "Reply public or private to save .env and configuration together."
|
|
8
|
+
},
|
|
9
|
+
"cancelHint": {
|
|
10
|
+
"zh-CN": "也可发 {command} 取消。",
|
|
11
|
+
"en": "Send {command} to cancel."
|
|
12
|
+
},
|
|
13
|
+
"bindingBusy": {
|
|
14
|
+
"zh-CN": "⚠️ 有进行中的扫码绑定,请先取消当前绑定流程",
|
|
15
|
+
"en": "⚠️ A QR binding is in progress; cancel it before starting another"
|
|
16
|
+
},
|
|
17
|
+
"kindBusy": {
|
|
18
|
+
"zh-CN": "⚠️ endpoint「{id}」等待公域/私域选择,请回复 public/private",
|
|
19
|
+
"en": "⚠️ Endpoint “{id}” is waiting for public/private selection"
|
|
20
|
+
},
|
|
21
|
+
"endpointSaved": {
|
|
22
|
+
"zh-CN": "✅ 已按 botKind={kind} 将 endpoint「{id}」写入 .env 与 {file}。\nintents: {intents}。\n⚠️ 需重启 zhin 后新 endpoint 才会生效。",
|
|
23
|
+
"en": "✅ Saved endpoint “{id}” with botKind={kind} to .env and {file}.\nintents: {intents}.\n⚠️ Restart zhin to activate the new endpoint."
|
|
24
|
+
},
|
|
25
|
+
"existingFlow": {
|
|
26
|
+
"zh-CN": "已有进行中的 QQ 机器人绑定或待确认的公域/私域选择,请先取消后再试",
|
|
27
|
+
"en": "A QQ binding or public/private selection is already in progress; cancel it before trying again"
|
|
28
|
+
},
|
|
29
|
+
"scanLink": {
|
|
30
|
+
"zh-CN": "请用手机 QQ 打开以下链接完成扫码绑定(二维码图片出站暂未支持,故发送链接):\n{url}",
|
|
31
|
+
"en": "Open this link in mobile QQ to scan and bind the bot:\n{url}"
|
|
32
|
+
},
|
|
33
|
+
"qrExpired": {
|
|
34
|
+
"zh-CN": "二维码已过期,正在刷新,请扫描新链接…",
|
|
35
|
+
"en": "The QR code expired. Refreshing; scan the new link…"
|
|
36
|
+
},
|
|
37
|
+
"noInteractiveKind": {
|
|
38
|
+
"zh-CN": "\n(当前会话无法交互选择公/私域;私域请手动改配置。)",
|
|
39
|
+
"en": "\n(This source cannot answer public/private selection. Edit the configuration manually for a private bot.)"
|
|
40
|
+
},
|
|
41
|
+
"scanned": {
|
|
42
|
+
"zh-CN": "✅ 扫码成功!endpoint「{id}」。\n{prompt}",
|
|
43
|
+
"en": "✅ QR binding succeeded for endpoint “{id}”.\n{prompt}"
|
|
44
|
+
},
|
|
45
|
+
"writeFailed": {
|
|
46
|
+
"zh-CN": "绑定成功但写入配置失败:{error}",
|
|
47
|
+
"en": "Binding succeeded but configuration could not be saved: {error}"
|
|
48
|
+
},
|
|
49
|
+
"bindingFailed": {
|
|
50
|
+
"zh-CN": "❌ QQ 绑定失败:{error}",
|
|
51
|
+
"en": "❌ QQ binding failed: {error}"
|
|
52
|
+
},
|
|
53
|
+
"cancelled": {
|
|
54
|
+
"zh-CN": "已取消进行中的 QQ 绑定流程",
|
|
55
|
+
"en": "The active QQ binding was cancelled"
|
|
56
|
+
},
|
|
57
|
+
"pendingCancelled": {
|
|
58
|
+
"zh-CN": "已取消 endpoint「{name}」的绑定(尚未写入任何文件)",
|
|
59
|
+
"en": "Binding for endpoint “{name}” cancelled; no files were written"
|
|
60
|
+
},
|
|
61
|
+
"noFlow": {
|
|
62
|
+
"zh-CN": "当前没有进行中的 QQ 绑定流程",
|
|
63
|
+
"en": "No QQ binding is in progress"
|
|
64
|
+
},
|
|
65
|
+
"kindUnrecognized": {
|
|
66
|
+
"zh-CN": "未识别「{raw}」。{prompt}",
|
|
67
|
+
"en": "Unrecognized answer “{raw}”. {prompt}"
|
|
68
|
+
},
|
|
69
|
+
"configFailed": {
|
|
70
|
+
"zh-CN": "写入配置失败:{error}",
|
|
71
|
+
"en": "Could not save configuration: {error}"
|
|
72
|
+
}
|
|
73
|
+
});
|
package/src/qq-bind-flow.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Locale } from 'zhin.js/i18n';
|
|
2
|
+
import { messages as t } from './i18n.js';
|
|
1
3
|
/**
|
|
2
4
|
* QQ Bot 扫码绑定流程(内联,等价 qqbot-connector startQrConnect)
|
|
3
5
|
*/
|
|
@@ -25,6 +27,7 @@ export interface QqBindCallbacks {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export interface QqBindFlowOptions {
|
|
30
|
+
locale?: Locale;
|
|
28
31
|
signal?: AbortSignal;
|
|
29
32
|
source?: string;
|
|
30
33
|
}
|
|
@@ -99,7 +102,7 @@ export function startQqBindFlow(
|
|
|
99
102
|
task = await createBindTask();
|
|
100
103
|
} catch (err) {
|
|
101
104
|
throw new Error(
|
|
102
|
-
|
|
105
|
+
t(options.locale, 'bindTaskFailed', { error: err instanceof Error ? err.message : String(err) }),
|
|
103
106
|
{ cause: err },
|
|
104
107
|
);
|
|
105
108
|
}
|
|
@@ -1,47 +1,32 @@
|
|
|
1
|
+
import { conversationRefKey } from '@zhin.js/im-contract';
|
|
2
|
+
import { createDeliveryTarget } from 'zhin.js';
|
|
3
|
+
import { resolveLocale, type Locale } from 'zhin.js/i18n';
|
|
4
|
+
import { messages as t } from './i18n.js';
|
|
1
5
|
import { parseQqBotKind, type QqBotKind } from './qq-intents.js';
|
|
2
6
|
|
|
3
|
-
/**
|
|
7
|
+
/** Bind replies to the complete canonical conversation and actual sender. */
|
|
4
8
|
export function qqCommandSessionKey(input: unknown): string | undefined {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} | null | undefined;
|
|
13
|
-
const conversation = message?.conversation;
|
|
14
|
-
const adapter = conversation?.endpoint?.adapter;
|
|
15
|
-
const endpointKey = conversation?.endpoint?.id;
|
|
16
|
-
const kind = conversation?.kind;
|
|
17
|
-
const id = conversation?.id;
|
|
18
|
-
const sender = message?.sender;
|
|
19
|
-
if (
|
|
20
|
-
adapter == null
|
|
21
|
-
|| endpointKey == null
|
|
22
|
-
|| (kind !== 'private' && kind !== 'group' && kind !== 'channel')
|
|
23
|
-
|| typeof id !== 'string'
|
|
24
|
-
|| !id
|
|
25
|
-
|| sender == null
|
|
26
|
-
|| String(sender).trim() === ''
|
|
27
|
-
) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
return `${String(adapter)}\0${String(endpointKey)}\0${kind}\0${id}\0${String(sender).trim()}`;
|
|
9
|
+
if (!input || typeof input !== 'object') return undefined;
|
|
10
|
+
const message = input as { conversation?: unknown; sender?: { id?: unknown } };
|
|
11
|
+
if (typeof message.sender?.id !== 'string' || !message.sender.id.trim()) return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const conversation = createDeliveryTarget({ conversation: message.conversation as Parameters<typeof createDeliveryTarget>[0]['conversation'] });
|
|
14
|
+
return JSON.stringify([conversationRefKey(conversation), message.sender.id]);
|
|
15
|
+
} catch { return undefined; }
|
|
31
16
|
}
|
|
32
17
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
* 接受:public / private / 公域 / 私域(大小写不敏感,可带前后空白)。
|
|
36
|
-
*/
|
|
37
|
-
export function parseQqBotKindAnswer(raw: unknown): QqBotKind | undefined {
|
|
18
|
+
/** Public/private are stable botKind values; Chinese aliases apply only to Chinese flows. */
|
|
19
|
+
export function parseQqBotKindAnswer(raw: unknown, locale?: Locale): QqBotKind | undefined {
|
|
38
20
|
const text = String(raw ?? '').trim().toLowerCase();
|
|
39
21
|
if (!text) return undefined;
|
|
40
|
-
if (
|
|
41
|
-
|
|
22
|
+
if (resolveLocale(locale) === 'zh-CN') {
|
|
23
|
+
if (text === '公域' || text === '公开' || text === '1') return 'public';
|
|
24
|
+
if (text === '私域' || text === '2') return 'private';
|
|
25
|
+
}
|
|
42
26
|
return parseQqBotKind(text);
|
|
43
27
|
}
|
|
44
28
|
|
|
45
|
-
export
|
|
46
|
-
'
|
|
47
|
-
|
|
29
|
+
export function qqBotKindPrompt(locale?: Locale, cancelCommand?: string): string {
|
|
30
|
+
return t(locale, 'kindPrompt') + (cancelCommand ? '\n' + t(locale, 'cancelHint', { command: cancelCommand }) : '');
|
|
31
|
+
}
|
|
32
|
+
export const QQ_BOT_KIND_PROMPT = qqBotKindPrompt();
|