@zhin.js/adapter-icqq 1.0.71 → 1.1.0
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 +1125 -52
- package/README.md +116 -431
- package/adapters/icqq.js +31 -0
- package/adapters/icqq.ts +42 -0
- package/agent/PERMITS.md +24 -0
- package/agent/prompt-sections/platform.ts +17 -0
- package/commands/endpoint/add/[[id]].js +3 -0
- package/commands/endpoint/add/[[id]].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +19 -0
- package/lib/client.js +3 -0
- package/lib/cq-message.d.ts +14 -0
- package/lib/cq-message.js +106 -0
- package/lib/endpoint.d.ts +31 -0
- package/lib/endpoint.js +1046 -0
- package/lib/forward-msg.d.ts +6 -0
- package/lib/forward-msg.js +107 -0
- package/lib/icqq-endpoint-commands.d.ts +1 -0
- package/lib/icqq-endpoint-commands.js +26 -0
- package/lib/icqq-guild.d.ts +57 -0
- package/lib/icqq-guild.js +109 -0
- package/lib/icqq-inbound.d.ts +165 -0
- package/lib/icqq-inbound.js +651 -0
- package/lib/icqq-inbox.d.ts +25 -0
- package/lib/icqq-inbox.js +129 -0
- package/lib/icqq-login-assist.d.ts +27 -0
- package/lib/icqq-login-assist.js +109 -0
- package/lib/icqq-runtime-state.d.ts +1 -0
- package/lib/icqq-runtime-state.js +6 -0
- package/lib/index.d.ts +5 -18
- package/lib/index.js +5 -124
- package/lib/outbound-media.d.ts +14 -0
- package/lib/outbound-media.js +76 -0
- package/lib/protocol.d.ts +276 -0
- package/lib/protocol.js +425 -0
- package/lib/types.d.ts +37 -11
- package/lib/types.js +0 -1
- package/package.json +80 -32
- package/plugin.js +19 -0
- package/schema.json +162 -0
- package/src/client.ts +29 -0
- package/src/cq-message.ts +113 -0
- package/src/endpoint.ts +1138 -0
- package/src/forward-msg.ts +112 -0
- package/src/icqq-endpoint-commands.ts +28 -0
- package/src/icqq-guild.ts +154 -0
- package/src/icqq-inbound.ts +814 -0
- package/src/icqq-inbox.ts +147 -0
- package/src/icqq-login-assist.ts +132 -0
- package/src/icqq-runtime-state.ts +7 -0
- package/src/index.ts +30 -149
- package/src/outbound-media.ts +103 -0
- package/src/protocol.ts +575 -0
- package/src/types.ts +40 -11
- package/tools/announce.js +16 -0
- package/tools/announce.ts +15 -0
- package/tools/essence.js +22 -0
- package/tools/essence.ts +20 -0
- package/tools/friend_list.js +18 -0
- package/tools/friend_list.ts +18 -0
- package/tools/group_files.js +22 -0
- package/tools/group_files.ts +20 -0
- package/tools/group_invite.js +23 -0
- package/tools/group_invite.ts +20 -0
- package/tools/group_kick.js +26 -0
- package/tools/group_kick.ts +22 -0
- package/tools/group_list.js +19 -0
- package/tools/group_list.ts +19 -0
- package/tools/group_member_info.js +24 -0
- package/tools/group_member_info.ts +26 -0
- package/tools/group_sign.js +19 -0
- package/tools/group_sign.ts +17 -0
- package/tools/list_muted.js +17 -0
- package/tools/list_muted.ts +16 -0
- package/tools/poke.js +16 -0
- package/tools/poke.ts +15 -0
- package/tools/send_user_like.js +25 -0
- package/tools/send_user_like.ts +22 -0
- package/tools/set_anonymous.js +21 -0
- package/tools/set_anonymous.ts +19 -0
- package/tools/set_mute.js +24 -0
- package/tools/set_mute.ts +21 -0
- package/tools/set_title.js +25 -0
- package/tools/set_title.ts +22 -0
- package/tools/user_info.js +14 -0
- package/tools/user_info.ts +13 -0
- package/LICENSE +0 -21
- package/client/ICQQManagement.tsx +0 -139
- package/client/LoginAssistPanel.tsx +0 -293
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -20
- package/dist/index.js +0 -1
- package/lib/adapter.d.ts +0 -34
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -396
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -46
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -457
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/skills/icqq/SKILL.md +0 -22
- package/src/adapter.ts +0 -448
- package/src/bot.ts +0 -521
package/schema.json
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"master": {
|
|
7
|
+
"type": ["string", "number"],
|
|
8
|
+
"description": "框架 master QQ uin(/approve、AI/工具权限);endpoints[i].master 可逐项覆盖"
|
|
9
|
+
},
|
|
10
|
+
"trusted": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "type": ["string", "number"] },
|
|
13
|
+
"description": "框架 trusted QQ uin 列表(弱于 master)"
|
|
14
|
+
},
|
|
15
|
+
"password": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "QQ 密码(可选,不填则扫码登录)"
|
|
18
|
+
},
|
|
19
|
+
"platform": {
|
|
20
|
+
"type": "number",
|
|
21
|
+
"enum": [1, 2, 3, 4, 5, 6, 7],
|
|
22
|
+
"default": 1,
|
|
23
|
+
"description": "登录设备平台:1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom"
|
|
24
|
+
},
|
|
25
|
+
"ver": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "协议版本号,仅在对应 platform 有多个版本时有效,不填则使用最新版本"
|
|
28
|
+
},
|
|
29
|
+
"signApiAddr": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "签名服务器地址;未配置时若安装了 @icqqjs/qqsign 则自动使用本地签名"
|
|
32
|
+
},
|
|
33
|
+
"dataDir": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "数据存储文件夹路径,默认为主模块下的 data 文件夹"
|
|
36
|
+
},
|
|
37
|
+
"autoReconnect": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": true,
|
|
40
|
+
"description": "断线后是否自动重连(默认 true)"
|
|
41
|
+
},
|
|
42
|
+
"outboundMedia": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": ["file", "base64"],
|
|
45
|
+
"default": "file",
|
|
46
|
+
"description": "出站媒体模式:file=落盘本地路径(默认); base64=CQ base64:// 内联"
|
|
47
|
+
},
|
|
48
|
+
"ignoreSelf": {
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"default": true,
|
|
51
|
+
"description": "群聊和频道中是否过滤自己的消息(默认 true)"
|
|
52
|
+
},
|
|
53
|
+
"resend": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "被风控时是否尝试用分片发送(默认 false)"
|
|
57
|
+
},
|
|
58
|
+
"cacheGroupMember": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"default": true,
|
|
61
|
+
"description": "是否缓存群员列表(默认 true);群多时(500+)会多占约 100MB+ 内存"
|
|
62
|
+
},
|
|
63
|
+
"autoServer": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"default": true,
|
|
66
|
+
"description": "是否自动选择最优服务器(默认 true)"
|
|
67
|
+
},
|
|
68
|
+
"qqnt": {
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"default": true,
|
|
71
|
+
"description": "是否使用 QQNT 协议(默认 true)"
|
|
72
|
+
},
|
|
73
|
+
"ntLogin": {
|
|
74
|
+
"type": "boolean",
|
|
75
|
+
"description": "是否使用 NT 登录方式"
|
|
76
|
+
},
|
|
77
|
+
"endpoints": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"additionalProperties": true,
|
|
83
|
+
"properties": {
|
|
84
|
+
"id": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "QQ uin"
|
|
87
|
+
},
|
|
88
|
+
"master": {
|
|
89
|
+
"type": ["string", "number"],
|
|
90
|
+
"description": "本 endpoint 的 master QQ uin;覆盖顶层 master"
|
|
91
|
+
},
|
|
92
|
+
"trusted": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": { "type": ["string", "number"] },
|
|
95
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
96
|
+
},
|
|
97
|
+
"password": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "本 endpoint 的 QQ 密码"
|
|
100
|
+
},
|
|
101
|
+
"platform": {
|
|
102
|
+
"type": "number",
|
|
103
|
+
"enum": [1, 2, 3, 4, 5, 6, 7],
|
|
104
|
+
"description": "登录设备平台:1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom"
|
|
105
|
+
},
|
|
106
|
+
"ver": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "协议版本号"
|
|
109
|
+
},
|
|
110
|
+
"signApiAddr": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "签名服务器地址"
|
|
113
|
+
},
|
|
114
|
+
"dataDir": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"description": "数据存储文件夹路径"
|
|
117
|
+
},
|
|
118
|
+
"autoReconnect": {
|
|
119
|
+
"type": "boolean",
|
|
120
|
+
"description": "断线后是否自动重连"
|
|
121
|
+
},
|
|
122
|
+
"outboundMedia": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"enum": ["file", "base64"],
|
|
125
|
+
"description": "出站媒体模式"
|
|
126
|
+
},
|
|
127
|
+
"ignoreSelf": {
|
|
128
|
+
"type": "boolean",
|
|
129
|
+
"description": "是否过滤自己的消息"
|
|
130
|
+
},
|
|
131
|
+
"resend": {
|
|
132
|
+
"type": "boolean",
|
|
133
|
+
"description": "被风控时是否分片发送"
|
|
134
|
+
},
|
|
135
|
+
"cacheGroupMember": {
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"description": "是否缓存群员列表"
|
|
138
|
+
},
|
|
139
|
+
"autoServer": {
|
|
140
|
+
"type": "boolean",
|
|
141
|
+
"description": "是否自动选择最优服务器"
|
|
142
|
+
},
|
|
143
|
+
"qqnt": {
|
|
144
|
+
"type": "boolean",
|
|
145
|
+
"description": "是否使用 QQNT 协议"
|
|
146
|
+
},
|
|
147
|
+
"ntLogin": {
|
|
148
|
+
"type": "boolean",
|
|
149
|
+
"description": "是否使用 NT 登录方式"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"required": ["id"]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"commandPrefix": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"default": "",
|
|
158
|
+
"description": "命令前缀(默认 '' 无前缀)。endpoints[i] 可逐项覆盖"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"required": ["master", "endpoints"]
|
|
162
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Client, EventMap } from '@icqqjs/icqq';
|
|
2
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
3
|
+
|
|
4
|
+
type EventPayload<TListener> = TListener extends (
|
|
5
|
+
event: infer TEvent,
|
|
6
|
+
...rest: readonly unknown[]
|
|
7
|
+
) => unknown
|
|
8
|
+
? TEvent
|
|
9
|
+
: never;
|
|
10
|
+
|
|
11
|
+
/** The actual ICQQ SDK instance exposed by every ICQQ Endpoint event. */
|
|
12
|
+
export type IcqqClient = Client;
|
|
13
|
+
|
|
14
|
+
/** Native ICQQ event-name → first public event payload, derived from the SDK SSOT. */
|
|
15
|
+
export type IcqqClientEventMap = {
|
|
16
|
+
readonly [TName in keyof EventMap]: EventPayload<EventMap[TName]>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare module '@zhin.js/feature-kit' {
|
|
20
|
+
interface AdapterClientRegistry {
|
|
21
|
+
readonly icqq: {
|
|
22
|
+
readonly client: IcqqClient;
|
|
23
|
+
readonly events: IcqqClientEventMap;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Typed, generation-checked identity used by handlers, commands, tools and schedules. */
|
|
29
|
+
export const icqqClient = defineEndpointClient<IcqqClient, IcqqClientEventMap>('icqq');
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** ICQQ 历史文本视图 → canonical MessageSegment。出站只使用原生 Sendable。 */
|
|
2
|
+
import { isMediaRef, type MediaRef } from "@zhin.js/core";
|
|
3
|
+
import type { MessageSegment } from "zhin.js";
|
|
4
|
+
|
|
5
|
+
const MAX_CQ_PARSE_LEN = 256_000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CQ 媒体参数 / 入站元素媒体值 → canonical MediaRef:
|
|
9
|
+
* `base64://` → base64;http(s) → url;本机路径 → path;其余视为平台不透明引用(file)。
|
|
10
|
+
*/
|
|
11
|
+
export function icqqMediaRefFromString(value: string): MediaRef {
|
|
12
|
+
if (value.startsWith("base64://")) {
|
|
13
|
+
return { kind: "base64", value: value.slice("base64://".length) };
|
|
14
|
+
}
|
|
15
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
16
|
+
return { kind: "url", value };
|
|
17
|
+
}
|
|
18
|
+
if (value.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(value)) {
|
|
19
|
+
return { kind: "path", value };
|
|
20
|
+
}
|
|
21
|
+
return { kind: "file", value };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* canonical MediaRef(`data.media`,唯一媒体来源)→ ICQQ native
|
|
26
|
+
* element 的 file 参数。base64 使用 ICQQ 原生支持的 `base64://` 前缀。
|
|
27
|
+
*/
|
|
28
|
+
export function resolveIcqqMediaFile(
|
|
29
|
+
data: Record<string, unknown> | undefined,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
const media = data?.media;
|
|
32
|
+
if (!isMediaRef(media)) return undefined;
|
|
33
|
+
if (media.kind === "base64") {
|
|
34
|
+
return media.value.startsWith("base64://") ? media.value : `base64://${media.value}`;
|
|
35
|
+
}
|
|
36
|
+
return media.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pushCqSegment(segments: MessageSegment[], type: string, arg: string): void {
|
|
40
|
+
switch (type) {
|
|
41
|
+
case "face":
|
|
42
|
+
segments.push({ type: "face", data: { id: Number(arg) } });
|
|
43
|
+
break;
|
|
44
|
+
case "image":
|
|
45
|
+
segments.push({ type: "image", data: { media: icqqMediaRefFromString(arg) } });
|
|
46
|
+
break;
|
|
47
|
+
case "at":
|
|
48
|
+
if (arg === "all") {
|
|
49
|
+
segments.push({ type: "at", data: { qq: "all" } });
|
|
50
|
+
} else {
|
|
51
|
+
segments.push({ type: "at", data: { qq: arg } });
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case "dice":
|
|
55
|
+
segments.push({ type: "dice", data: {} });
|
|
56
|
+
break;
|
|
57
|
+
case "rps":
|
|
58
|
+
segments.push({ type: "rps", data: {} });
|
|
59
|
+
break;
|
|
60
|
+
case "record":
|
|
61
|
+
case "audio":
|
|
62
|
+
segments.push({ type: "audio", data: { media: icqqMediaRefFromString(arg) } });
|
|
63
|
+
break;
|
|
64
|
+
case "video":
|
|
65
|
+
segments.push({ type: "video", data: { media: icqqMediaRefFromString(arg) } });
|
|
66
|
+
break;
|
|
67
|
+
case "reply":
|
|
68
|
+
segments.push({ type: "reply", data: { message_id: arg } });
|
|
69
|
+
break;
|
|
70
|
+
case "json":
|
|
71
|
+
segments.push({ type: "json", data: { text: arg } });
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
segments.push({ type, data: { text: `[${type}:${arg}]` } });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseCqMessage(raw: string): MessageSegment[] {
|
|
80
|
+
const text = raw.length > MAX_CQ_PARSE_LEN ? raw.slice(0, MAX_CQ_PARSE_LEN) : raw;
|
|
81
|
+
const segments: MessageSegment[] = [];
|
|
82
|
+
let i = 0;
|
|
83
|
+
|
|
84
|
+
while (i < text.length) {
|
|
85
|
+
if (text[i] !== "[") {
|
|
86
|
+
const next = text.indexOf("[", i);
|
|
87
|
+
const chunk = next === -1 ? text.slice(i) : text.slice(i, next);
|
|
88
|
+
if (chunk) segments.push({ type: "text", data: { text: chunk } });
|
|
89
|
+
if (next === -1) break;
|
|
90
|
+
i = next;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const close = text.indexOf("]", i + 1);
|
|
95
|
+
if (close === -1) {
|
|
96
|
+
segments.push({ type: "text", data: { text: text.slice(i) } });
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const inner = text.slice(i + 1, close);
|
|
101
|
+
const colon = inner.indexOf(":");
|
|
102
|
+
const type = (colon === -1 ? inner : inner.slice(0, colon)).trim().toLowerCase();
|
|
103
|
+
const arg = colon === -1 ? "" : inner.slice(colon + 1);
|
|
104
|
+
if (/^[a-z_]+$/.test(type)) {
|
|
105
|
+
pushCqSegment(segments, type, arg);
|
|
106
|
+
} else {
|
|
107
|
+
segments.push({ type: "text", data: { text: text.slice(i, close + 1) } });
|
|
108
|
+
}
|
|
109
|
+
i = close + 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return segments.length ? segments : [{ type: "text", data: { text: raw } }];
|
|
113
|
+
}
|