@zhin.js/adapter-icqq 5.0.2 → 6.0.1
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 +32 -0
- package/README.md +32 -4
- package/client/LoginAssistPanel.tsx +38 -3
- package/dist/index.js +2 -2
- package/lib/adapter.d.ts +12 -4
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +14 -6
- package/lib/adapter.js.map +1 -1
- package/lib/cq-message.d.ts.map +1 -1
- package/lib/cq-message.js +12 -3
- package/lib/cq-message.js.map +1 -1
- package/lib/endpoint.d.ts +12 -8
- package/lib/endpoint.d.ts.map +1 -1
- package/lib/endpoint.js +135 -47
- package/lib/endpoint.js.map +1 -1
- package/lib/icqq-guild.d.ts +48 -0
- package/lib/icqq-guild.d.ts.map +1 -0
- package/lib/icqq-guild.js +115 -0
- package/lib/icqq-guild.js.map +1 -0
- package/lib/icqq-inbound.d.ts +7 -0
- package/lib/icqq-inbound.d.ts.map +1 -1
- package/lib/icqq-inbound.js +39 -6
- package/lib/icqq-inbound.js.map +1 -1
- package/lib/icqq-login-assist-bridge.d.ts +5 -0
- package/lib/icqq-login-assist-bridge.d.ts.map +1 -0
- package/lib/icqq-login-assist-bridge.js +148 -0
- package/lib/icqq-login-assist-bridge.js.map +1 -0
- package/lib/index.d.ts +0 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/login-assist-routes.d.ts +7 -0
- package/lib/login-assist-routes.d.ts.map +1 -0
- package/lib/login-assist-routes.js +29 -0
- package/lib/login-assist-routes.js.map +1 -0
- package/lib/login-ipc-contract.d.ts +44 -0
- package/lib/login-ipc-contract.d.ts.map +1 -0
- package/lib/login-ipc-contract.js +35 -0
- package/lib/login-ipc-contract.js.map +1 -0
- package/lib/protocol.d.ts +32 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +23 -0
- package/lib/protocol.js.map +1 -1
- package/lib/segment-mapper.d.ts +2 -0
- package/lib/segment-mapper.d.ts.map +1 -0
- package/lib/segment-mapper.js +2 -0
- package/lib/segment-mapper.js.map +1 -0
- package/lib/tools/index.d.ts +1 -1
- package/lib/tools/index.js +8 -8
- package/lib/types.d.ts +0 -44
- package/lib/types.d.ts.map +1 -1
- package/package.json +18 -18
- package/skills/icqq/PERMITS.md +6 -6
- package/src/adapter.ts +14 -6
- package/src/cq-message.ts +11 -3
- package/src/endpoint.ts +169 -52
- package/src/icqq-guild.ts +157 -0
- package/src/icqq-inbound.ts +52 -7
- package/src/icqq-login-assist-bridge.ts +196 -0
- package/src/index.ts +2 -8
- package/src/login-assist-routes.ts +39 -0
- package/src/login-ipc-contract.ts +52 -0
- package/src/protocol.ts +32 -1
- package/src/segment-mapper.ts +1 -0
- package/src/tools/index.ts +9 -9
- package/src/types.ts +0 -45
- package/lib/typing-indicator-example.d.ts +0 -108
- package/lib/typing-indicator-example.d.ts.map +0 -1
- package/lib/typing-indicator-example.js +0 -220
- package/lib/typing-indicator-example.js.map +0 -1
- package/lib/typing-indicator.d.ts +0 -87
- package/lib/typing-indicator.d.ts.map +0 -1
- package/lib/typing-indicator.js +0 -225
- package/lib/typing-indicator.js.map +0 -1
- package/src/typing-indicator-example.ts +0 -269
- package/src/typing-indicator.ts +0 -313
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将 icqq 守护进程 IPC 的 system.login.* 事件桥接到 core LoginAssist。
|
|
3
|
+
*
|
|
4
|
+
* 推送契约(@icqqjs/cli IpcEvent):
|
|
5
|
+
* { id: "*", event: "system.login.auth", data: { url, device } }
|
|
6
|
+
* 须在 endpoint.handleEvent 中按 event.event 先行处理(非 OneBot post_type)。
|
|
7
|
+
*
|
|
8
|
+
* 续传契约(LoginIpcActions ↔ LoginFlow)见 login-ipc-contract.ts。
|
|
9
|
+
*/
|
|
10
|
+
import type { LoginAssist, LoginAssistType } from '@zhin.js/core';
|
|
11
|
+
import type { IcqqEndpoint } from './endpoint.js';
|
|
12
|
+
import {
|
|
13
|
+
LoginIpcActions,
|
|
14
|
+
type IcqqLoginDeviceInfo,
|
|
15
|
+
} from './login-ipc-contract.js';
|
|
16
|
+
|
|
17
|
+
const activeByEndpoint = new Set<string>();
|
|
18
|
+
|
|
19
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
20
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
21
|
+
? (value as Record<string, unknown>)
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveQrcodeImage(data: Record<string, unknown>): string | undefined {
|
|
26
|
+
const image = data.image;
|
|
27
|
+
if (image && typeof image === 'object') {
|
|
28
|
+
const buf = image as { type?: string; data?: string };
|
|
29
|
+
if (buf.type === 'Buffer' && typeof buf.data === 'string') {
|
|
30
|
+
return `data:image/png;base64,${buf.data}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (typeof data.url === 'string' && data.url.startsWith('data:image')) {
|
|
34
|
+
return data.url;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function mapLoginEvent(
|
|
40
|
+
eventName: string,
|
|
41
|
+
data: Record<string, unknown>,
|
|
42
|
+
): { type: LoginAssistType; payload: Record<string, unknown> } | null {
|
|
43
|
+
switch (eventName) {
|
|
44
|
+
case 'system.login.qrcode':
|
|
45
|
+
return {
|
|
46
|
+
type: 'qrcode',
|
|
47
|
+
payload: {
|
|
48
|
+
message: '请使用手机 QQ 扫码登录',
|
|
49
|
+
image: resolveQrcodeImage(data),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
case 'system.login.slider':
|
|
53
|
+
return {
|
|
54
|
+
type: 'slider',
|
|
55
|
+
payload: {
|
|
56
|
+
message: '请完成滑块验证并提交 ticket',
|
|
57
|
+
url: String(data.url ?? ''),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
case 'system.login.device':
|
|
61
|
+
return {
|
|
62
|
+
type: 'device',
|
|
63
|
+
payload: {
|
|
64
|
+
message: data.phone
|
|
65
|
+
? `请完成设备锁验证(密保手机 ${String(data.phone)})`
|
|
66
|
+
: '请完成设备锁验证',
|
|
67
|
+
url: String(data.url ?? ''),
|
|
68
|
+
phone: data.phone,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
case 'system.login.auth':
|
|
72
|
+
return {
|
|
73
|
+
type: 'auth',
|
|
74
|
+
payload: {
|
|
75
|
+
message: '请在浏览器完成身份验证(需使用下方设备信息)',
|
|
76
|
+
url: String(data.url ?? ''),
|
|
77
|
+
device: data.device as IcqqLoginDeviceInfo | undefined,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 对齐 @icqqjs/cli LoginFlow.handleSubmit → Client 方法 */
|
|
85
|
+
async function continueLoginAfterAssist(
|
|
86
|
+
endpoint: IcqqEndpoint,
|
|
87
|
+
loginType: LoginAssistType,
|
|
88
|
+
value: string | Record<string, unknown>,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
if (!endpoint.ipc || endpoint.ipc.closed) return;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (loginType === 'slider') {
|
|
94
|
+
const ticket =
|
|
95
|
+
typeof value === 'string'
|
|
96
|
+
? value.trim()
|
|
97
|
+
: typeof value === 'object' && value !== null
|
|
98
|
+
? String((value as { ticket?: string }).ticket ?? '').trim()
|
|
99
|
+
: '';
|
|
100
|
+
if (!ticket) return;
|
|
101
|
+
await endpoint.ipc.request(LoginIpcActions.SUBMIT_SLIDER, { ticket });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (loginType === 'sms') {
|
|
106
|
+
const code = typeof value === 'string' ? value.trim() : '';
|
|
107
|
+
if (!code) return;
|
|
108
|
+
await endpoint.ipc.request(LoginIpcActions.SUBMIT_SMS_CODE, { code });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (loginType === 'device') {
|
|
113
|
+
if (
|
|
114
|
+
typeof value === 'object' &&
|
|
115
|
+
value !== null &&
|
|
116
|
+
(value as { action?: string }).action === 'sms'
|
|
117
|
+
) {
|
|
118
|
+
await endpoint.ipc.request(LoginIpcActions.SEND_SMS_CODE, {});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === 'string' && value.trim()) {
|
|
122
|
+
await endpoint.ipc.request(LoginIpcActions.SUBMIT_SMS_CODE, {
|
|
123
|
+
code: value.trim(),
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// qrcode / device 链接确认 / auth 237 确认 → client.login()
|
|
130
|
+
await endpoint.ipc.request(LoginIpcActions.LOGIN, {});
|
|
131
|
+
} catch (err) {
|
|
132
|
+
endpoint.logger.warn(
|
|
133
|
+
`登录辅助提交后 IPC 续传失败:${
|
|
134
|
+
err instanceof Error ? err.message : String(err)
|
|
135
|
+
}(action 须与 @icqqjs/cli action-catalog 一致)`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function handleIcqqLoginIpcEvent(
|
|
141
|
+
endpoint: IcqqEndpoint,
|
|
142
|
+
eventName: string,
|
|
143
|
+
rawData: unknown,
|
|
144
|
+
): void {
|
|
145
|
+
if (!eventName.startsWith('system.login.')) return;
|
|
146
|
+
|
|
147
|
+
const root = endpoint.adapter.plugin.root;
|
|
148
|
+
const loginAssist = root.inject('loginAssist') as LoginAssist | undefined;
|
|
149
|
+
if (!loginAssist) {
|
|
150
|
+
endpoint.logger.debug(`收到 ${eventName},但 loginAssist 未启用`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (eventName === 'system.login.error') {
|
|
155
|
+
const data = asRecord(rawData);
|
|
156
|
+
endpoint.logger.warn(
|
|
157
|
+
`icqq 登录错误:${String(data?.message ?? 'unknown')}${
|
|
158
|
+
data?.code != null ? ` [code=${String(data.code)}]` : ''
|
|
159
|
+
}`,
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const data = asRecord(rawData);
|
|
165
|
+
if (!data) return;
|
|
166
|
+
|
|
167
|
+
const mapped = mapLoginEvent(eventName, data);
|
|
168
|
+
if (!mapped) return;
|
|
169
|
+
|
|
170
|
+
const guardKey = endpoint.$id;
|
|
171
|
+
if (activeByEndpoint.has(guardKey)) {
|
|
172
|
+
endpoint.logger.debug(`跳过重复登录待办:${eventName}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
activeByEndpoint.add(guardKey);
|
|
176
|
+
|
|
177
|
+
endpoint.logger.info(`icqq 登录待办:${mapped.type}(${eventName})`);
|
|
178
|
+
|
|
179
|
+
void loginAssist
|
|
180
|
+
.waitForInput('icqq', endpoint.$id, mapped.type, mapped.payload)
|
|
181
|
+
.then((value) => continueLoginAfterAssist(endpoint, mapped.type, value))
|
|
182
|
+
.catch((err) => {
|
|
183
|
+
if (err instanceof Error && err.message !== 'cancelled') {
|
|
184
|
+
endpoint.logger.warn(`登录辅助已取消或失败:${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
.finally(() => {
|
|
188
|
+
activeByEndpoint.delete(guardKey);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function disposeIcqqLoginAssistBridge(endpointId: string): void {
|
|
193
|
+
activeByEndpoint.delete(endpointId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type { IcqqLoginDeviceInfo } from './login-ipc-contract.js';
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { PageManager } from "@zhin.js/host-api";
|
|
|
14
14
|
import { IcqqAdapter } from "./adapter.js";
|
|
15
15
|
import { registerTools } from "./tools/index.js";
|
|
16
16
|
import { registerRoutes } from "./routes.js";
|
|
17
|
+
import { registerLoginAssistRoutes } from "./login-assist-routes.js";
|
|
17
18
|
|
|
18
19
|
declare module "zhin.js" {
|
|
19
20
|
namespace Plugin {
|
|
@@ -30,14 +31,6 @@ declare module "zhin.js" {
|
|
|
30
31
|
export * from "./types.js";
|
|
31
32
|
export { IcqqEndpoint } from "./endpoint.js";
|
|
32
33
|
export { IcqqAdapter } from "./adapter.js";
|
|
33
|
-
export {
|
|
34
|
-
ICQQTypingIndicatorManager,
|
|
35
|
-
createICQQTypingIndicatorManager,
|
|
36
|
-
startICQQTypingIndicator,
|
|
37
|
-
enableTypingIndicator,
|
|
38
|
-
type ICQQTypingIndicatorConfig,
|
|
39
|
-
} from "./typing-indicator.js";
|
|
40
|
-
export type { TypingIndicatorConfig } from "./types.js";
|
|
41
34
|
|
|
42
35
|
const plugin = usePlugin();
|
|
43
36
|
const { provide, useContext, addCommand, root } = plugin;
|
|
@@ -76,4 +69,5 @@ useContext("web", (pageManager) => {
|
|
|
76
69
|
// ── HTTP 路由 ──────────────────────────────────────────────────────
|
|
77
70
|
useContext("router", "icqq", async (router: Router, icqq: IcqqAdapter) => {
|
|
78
71
|
registerRoutes(router, icqq, root);
|
|
72
|
+
registerLoginAssistRoutes(router, root);
|
|
79
73
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loginAssist HTTP 路由(供 Web 控制台 /icqq 登录辅助 Tab)
|
|
3
|
+
*/
|
|
4
|
+
import type { Router } from "@zhin.js/host-router";
|
|
5
|
+
import type { Plugin } from "zhin.js";
|
|
6
|
+
|
|
7
|
+
export function registerLoginAssistRoutes(router: Router, root: Plugin): void {
|
|
8
|
+
const loginAssist = root.inject("loginAssist");
|
|
9
|
+
if (!loginAssist) return;
|
|
10
|
+
|
|
11
|
+
router.get("/api/login-assist/pending", async (ctx) => {
|
|
12
|
+
ctx.body = loginAssist.listPending();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
router.post("/api/login-assist/submit", async (ctx: any) => {
|
|
16
|
+
const body = (ctx.request.body ?? {}) as {
|
|
17
|
+
id?: string;
|
|
18
|
+
value?: string | Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
if (!body.id) {
|
|
21
|
+
ctx.status = 400;
|
|
22
|
+
ctx.body = { success: false, message: "missing id" };
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const ok = loginAssist.submit(body.id, body.value ?? "");
|
|
26
|
+
ctx.body = { success: ok };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
router.post("/api/login-assist/cancel", async (ctx: any) => {
|
|
30
|
+
const body = (ctx.request.body ?? {}) as { id?: string; reason?: string };
|
|
31
|
+
if (!body.id) {
|
|
32
|
+
ctx.status = 400;
|
|
33
|
+
ctx.body = { success: false, message: "missing id" };
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const ok = loginAssist.cancel(body.id, body.reason);
|
|
37
|
+
ctx.body = { success: ok };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ICQQ 登录 IPC 契约(与 @icqqjs/cli / @icqqjs/icqq 对齐)
|
|
3
|
+
*
|
|
4
|
+
* **推送(Daemon → Client)** — 见 @icqqjs/cli `src/daemon/protocol.ts` 的 `IpcEvent`:
|
|
5
|
+
* ```json
|
|
6
|
+
* { "id": "*", "event": "system.login.auth", "data": { "url", "device" } }
|
|
7
|
+
* ```
|
|
8
|
+
* 事件名与载荷形状以 @icqqjs/icqq `EventMap` 为准(非 OneBot `post_type`)。
|
|
9
|
+
*
|
|
10
|
+
* **续传(Client → Daemon)** — 与 CLI `LoginFlow` / `AccountBootstrap` 对 icqq Client 的调用一致,
|
|
11
|
+
* action 名为 Client 方法的 snake_case;收录于守护进程 `action-catalog` 后可通过 IPC invoke。
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/icqqjs/cli/blob/master/src/daemon/protocol.ts
|
|
14
|
+
* @see https://github.com/icqqjs/cli/blob/master/src/lib/account-bootstrap.ts
|
|
15
|
+
* @see https://github.com/icqqjs/cli/blob/master/src/components/LoginFlow.tsx
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** system.login.auth 等设备指纹(@icqqjs/icqq EventMap) */
|
|
19
|
+
export interface IcqqLoginDeviceInfo {
|
|
20
|
+
guid?: string;
|
|
21
|
+
qimei?: string;
|
|
22
|
+
qimei36?: string;
|
|
23
|
+
subappid?: string;
|
|
24
|
+
platform?: string;
|
|
25
|
+
brand?: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
bssid?: string;
|
|
28
|
+
devInfo?: string;
|
|
29
|
+
sysVersion?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 登录续传 IPC action(与 LoginFlow → Client 方法一一对应) */
|
|
33
|
+
export const LoginIpcActions = {
|
|
34
|
+
/** 扫码确认 / 设备锁链接确认 / auth 237 验证完成后继续 — client.login() */
|
|
35
|
+
LOGIN: 'login',
|
|
36
|
+
/** 滑块 ticket — client.submitSlider(ticket) */
|
|
37
|
+
SUBMIT_SLIDER: 'submit_slider',
|
|
38
|
+
/** 设备锁短信验证码 — client.submitSmsCode(code) */
|
|
39
|
+
SUBMIT_SMS_CODE: 'submit_sms_code',
|
|
40
|
+
/** 请求发送设备锁短信 — client.sendSmsCode() */
|
|
41
|
+
SEND_SMS_CODE: 'send_sms_code',
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export type LoginIpcAction = (typeof LoginIpcActions)[keyof typeof LoginIpcActions];
|
|
45
|
+
|
|
46
|
+
/** CLI LoginFlow 237 验证步骤(供 Web 控制台展示) */
|
|
47
|
+
export const AUTH_DEVICE_STEPS = [
|
|
48
|
+
'打开上方验证链接,正常完成验证流程,直到发送完验证码,不要点击「我已发送」',
|
|
49
|
+
'复制下方 JS,打开浏览器开发者工具(F12)→ Console,粘贴并回车',
|
|
50
|
+
'在弹窗中粘贴设备信息 JSON 并确认(可使用下方单行 JSON)',
|
|
51
|
+
'回到验证页点击「我已发送」,完成 237 验证后回到控制台点击确认',
|
|
52
|
+
] as const;
|
package/src/protocol.ts
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* CLI 与守护进程通过 Unix Domain Socket 通信,
|
|
5
5
|
* 使用 JSON + 换行符(\n)分隔的文本协议。
|
|
6
|
+
*
|
|
7
|
+
* 通信流程(@icqqjs/cli `src/daemon/protocol.ts`):
|
|
8
|
+
* 1. CLI 连接 Socket → auth(token)
|
|
9
|
+
* 2. 认证通过后发送 IpcRequest → 等待 IpcResponse
|
|
10
|
+
* 3. 认证通过后自动接收 icqq 事件推送(IpcEvent),断开连接自动停止
|
|
11
|
+
*
|
|
12
|
+
* 登录相关:**推送**走 IpcEvent(`system.login.*`),续传 invoke 见 `login-ipc-contract.ts`。
|
|
6
13
|
*/
|
|
7
14
|
|
|
8
15
|
/** CLI → Daemon 请求 */
|
|
@@ -20,10 +27,18 @@ export type IpcResponse = {
|
|
|
20
27
|
error?: string;
|
|
21
28
|
};
|
|
22
29
|
|
|
23
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Daemon → CLI 事件推送(icqq client.em 分发的全部事件)
|
|
32
|
+
*
|
|
33
|
+
* 登录/重连时推送 `system.login.*`(如 `system.login.auth` 含 url + device),
|
|
34
|
+
* 语义在 `event` 字段,不在 OneBot 式 `post_type` 里。
|
|
35
|
+
*/
|
|
24
36
|
export type IpcEvent = {
|
|
37
|
+
/** 固定为 `"*"`,客户端应忽略,由本地 handler 按 `event` 过滤 */
|
|
25
38
|
id: string;
|
|
39
|
+
/** icqq 事件名,如 `system.login.auth`、`message.group.normal` */
|
|
26
40
|
event: string;
|
|
41
|
+
/** icqq 事件 toJSON 后的 plain object */
|
|
27
42
|
data: unknown;
|
|
28
43
|
};
|
|
29
44
|
|
|
@@ -53,6 +68,7 @@ export interface IpcGuildMessageEventData {
|
|
|
53
68
|
*/
|
|
54
69
|
export const Actions = {
|
|
55
70
|
PING: "ping",
|
|
71
|
+
LOGOUT: "logout",
|
|
56
72
|
|
|
57
73
|
// ── 列表查询 ──
|
|
58
74
|
LIST_FRIENDS: "list_friends",
|
|
@@ -66,7 +82,10 @@ export const Actions = {
|
|
|
66
82
|
GET_GROUP_INFO: "get_group_info",
|
|
67
83
|
GET_GROUP_MEMBER_INFO: "get_group_member_info",
|
|
68
84
|
GET_STRANGER_INFO: "get_stranger_info",
|
|
85
|
+
LIST_STRANGERS: "list_strangers",
|
|
86
|
+
GET_PROFILE: "get_profile",
|
|
69
87
|
GET_STATUS: "get_status",
|
|
88
|
+
GET_ONLINE_STATUS: "get_online_status",
|
|
70
89
|
GET_SELF_PROFILE: "get_self_profile",
|
|
71
90
|
|
|
72
91
|
// ── 消息发送 ──
|
|
@@ -78,6 +97,8 @@ export const Actions = {
|
|
|
78
97
|
GET_MSG: "get_msg",
|
|
79
98
|
HISTORY_PRIVATE: "history_private",
|
|
80
99
|
HISTORY_GROUP: "history_group",
|
|
100
|
+
HISTORY_BY_MSG_ID: "history_by_msg_id",
|
|
101
|
+
SEND_LONG_MSG: "send_long_msg",
|
|
81
102
|
MARK_READ: "mark_read",
|
|
82
103
|
DELETE_MSG: "delete_msg",
|
|
83
104
|
|
|
@@ -165,6 +186,9 @@ export const Actions = {
|
|
|
165
186
|
SEND_PRIVATE_FILE: "send_private_file",
|
|
166
187
|
SEND_GROUP_FILE: "send_group_file",
|
|
167
188
|
FRIEND_RECALL_FILE: "friend_recall_file",
|
|
189
|
+
FRIEND_FORWARD_FILE: "friend_forward_file",
|
|
190
|
+
SEARCH_SAME_GROUP: "search_same_group",
|
|
191
|
+
SEND_CONTACT_SHARE: "send_contact_share",
|
|
168
192
|
GFS_UPLOAD: "gfs_upload",
|
|
169
193
|
|
|
170
194
|
// ── 群消息表态 ──
|
|
@@ -179,6 +203,7 @@ export const Actions = {
|
|
|
179
203
|
GUILD_LIST: "guild_list",
|
|
180
204
|
GUILD_INFO: "guild_info",
|
|
181
205
|
GUILD_CHANNELS: "guild_channels",
|
|
206
|
+
GET_CHANNEL_INFO: "get_channel_info",
|
|
182
207
|
GUILD_MEMBERS: "guild_members",
|
|
183
208
|
GUILD_SEND_MSG: "guild_send_msg",
|
|
184
209
|
GUILD_RECALL_MSG: "guild_recall_msg",
|
|
@@ -208,7 +233,13 @@ export const Actions = {
|
|
|
208
233
|
GET_CLIENT_KEY: "get_client_key",
|
|
209
234
|
GET_PSKEY: "get_pskey",
|
|
210
235
|
UID2UIN: "uid2uin",
|
|
236
|
+
UID2UINS: "uid2uins",
|
|
211
237
|
UIN2UID: "uin2uid",
|
|
238
|
+
UIN2UIDS: "uin2uids",
|
|
239
|
+
GET_COOKIES: "get_cookies",
|
|
240
|
+
GET_CSRF_TOKEN: "get_csrf_token",
|
|
241
|
+
REFRESH_NT_PIC_RKEY: "refresh_nt_pic_rkey",
|
|
242
|
+
SEND_DISCUSS_MSG: "send_discuss_msg",
|
|
212
243
|
|
|
213
244
|
// ── 视频/加好友设置 ──
|
|
214
245
|
GET_VIDEO_URL: "get_video_url",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
|
package/src/tools/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ICQQ 平台特有工具注册 — 通过 IPC 调用守护进程 Actions
|
|
3
3
|
*
|
|
4
|
-
* 通用群管理工具由
|
|
4
|
+
* 通用群管理工具由 createSceneManagementTools() 自动生成;
|
|
5
5
|
* 本模块注册 ICQQ 独有的扩展工具。
|
|
6
6
|
*/
|
|
7
7
|
import type { ToolFeature } from "zhin.js";
|
|
8
|
-
import {
|
|
8
|
+
import { createSceneManagementTools } from "zhin.js";
|
|
9
9
|
import type { IcqqAdapter } from "../adapter.js";
|
|
10
10
|
import { Actions } from "../protocol.js";
|
|
11
11
|
|
|
@@ -17,8 +17,8 @@ export function registerTools(
|
|
|
17
17
|
const disposers: (() => void)[] = [];
|
|
18
18
|
|
|
19
19
|
// ── 通用群管工具 ───────────────────────────────────────────────────
|
|
20
|
-
const
|
|
21
|
-
disposers.push(...
|
|
20
|
+
const sceneTools = createSceneManagementTools(icqq, "icqq");
|
|
21
|
+
disposers.push(...sceneTools.map((t) => toolService.addTool(t, pluginName)));
|
|
22
22
|
|
|
23
23
|
// ── 设置头衔 ───────────────────────────────────────────────────────
|
|
24
24
|
disposers.push(
|
|
@@ -39,7 +39,7 @@ export function registerTools(
|
|
|
39
39
|
},
|
|
40
40
|
platforms: ["icqq"],
|
|
41
41
|
tags: ["icqq"],
|
|
42
|
-
permissions: ["platform(icqq,
|
|
42
|
+
permissions: ["platform(icqq,scene_owner)"],
|
|
43
43
|
execute: async (args: Record<string, any>) => {
|
|
44
44
|
const endpoint = icqq.endpoints.get(args.endpoint_id);
|
|
45
45
|
if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
@@ -73,7 +73,7 @@ export function registerTools(
|
|
|
73
73
|
},
|
|
74
74
|
platforms: ["icqq"],
|
|
75
75
|
tags: ["icqq"],
|
|
76
|
-
permissions: ["platform(icqq,
|
|
76
|
+
permissions: ["platform(icqq,scene_admin)"],
|
|
77
77
|
execute: async (args: Record<string, any>) => {
|
|
78
78
|
const endpoint = icqq.endpoints.get(args.endpoint_id);
|
|
79
79
|
if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
@@ -135,7 +135,7 @@ export function registerTools(
|
|
|
135
135
|
},
|
|
136
136
|
platforms: ["icqq"],
|
|
137
137
|
tags: ["icqq"],
|
|
138
|
-
permissions: ["platform(icqq,
|
|
138
|
+
permissions: ["platform(icqq,scene_admin)"],
|
|
139
139
|
execute: async (args: Record<string, any>) => {
|
|
140
140
|
const endpoint = icqq.endpoints.get(args.endpoint_id);
|
|
141
141
|
if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
@@ -199,7 +199,7 @@ export function registerTools(
|
|
|
199
199
|
},
|
|
200
200
|
platforms: ["icqq"],
|
|
201
201
|
tags: ["icqq"],
|
|
202
|
-
permissions: ["platform(icqq,
|
|
202
|
+
permissions: ["platform(icqq,scene_owner)"],
|
|
203
203
|
execute: async (args: Record<string, any>) => {
|
|
204
204
|
const endpoint = icqq.endpoints.get(args.endpoint_id);
|
|
205
205
|
if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
|
@@ -393,7 +393,7 @@ export function registerTools(
|
|
|
393
393
|
},
|
|
394
394
|
platforms: ["icqq"],
|
|
395
395
|
tags: ["icqq"],
|
|
396
|
-
permissions: ["platform(icqq,
|
|
396
|
+
permissions: ["platform(icqq,scene_admin)"],
|
|
397
397
|
execute: async (args: Record<string, any>) => {
|
|
398
398
|
const endpoint = icqq.endpoints.get(args.endpoint_id);
|
|
399
399
|
if (!endpoint) throw new Error(`Endpoint ${args.endpoint_id} 不存在`);
|
package/src/types.ts
CHANGED
|
@@ -17,46 +17,6 @@ export interface IcqqSenderInfo {
|
|
|
17
17
|
title?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Typing Indicator 配置
|
|
22
|
-
*/
|
|
23
|
-
export interface TypingIndicatorConfig {
|
|
24
|
-
/** 是否启用(默认 true) */
|
|
25
|
-
enabled?: boolean;
|
|
26
|
-
/** 默认表情(默认 '⏳') */
|
|
27
|
-
defaultEmoji?: string;
|
|
28
|
-
/** 是否在完成后自动移除(默认 true) */
|
|
29
|
-
autoRemove?: boolean;
|
|
30
|
-
/** 自动移除延迟,毫秒(默认 5000) */
|
|
31
|
-
removeDelay?: number;
|
|
32
|
-
/** 私聊配置 */
|
|
33
|
-
privateConfig?: {
|
|
34
|
-
/** 提示类型 */
|
|
35
|
-
type: 'message' | 'reaction';
|
|
36
|
-
/** 消息内容(type='message' 时使用) */
|
|
37
|
-
message?: string;
|
|
38
|
-
/** 表情符号(type='reaction' 时使用) */
|
|
39
|
-
emoji?: string;
|
|
40
|
-
/** 是否自动移除 */
|
|
41
|
-
autoRemove?: boolean;
|
|
42
|
-
/** 自动移除延迟 */
|
|
43
|
-
removeDelay?: number;
|
|
44
|
-
};
|
|
45
|
-
/** 群聊配置 */
|
|
46
|
-
groupConfig?: {
|
|
47
|
-
/** 提示类型 */
|
|
48
|
-
type: 'message' | 'reaction';
|
|
49
|
-
/** 消息内容(type='message' 时使用) */
|
|
50
|
-
message?: string;
|
|
51
|
-
/** 表情符号(type='reaction' 时使用) */
|
|
52
|
-
emoji?: string;
|
|
53
|
-
/** 是否自动移除 */
|
|
54
|
-
autoRemove?: boolean;
|
|
55
|
-
/** 自动移除延迟 */
|
|
56
|
-
removeDelay?: number;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
20
|
/**
|
|
61
21
|
* Endpoint 配置:支持本地 IPC 和远程 RPC 两种连接模式。
|
|
62
22
|
*
|
|
@@ -81,11 +41,6 @@ export interface IcqqEndpointConfig {
|
|
|
81
41
|
* 默认 true;设为 false 则断开后仅将 `$connected` 置为 false,需手动重连。
|
|
82
42
|
*/
|
|
83
43
|
autoReconnect?: boolean;
|
|
84
|
-
/**
|
|
85
|
-
* Typing Indicator 配置
|
|
86
|
-
* 用于在 AI 处理消息时提示用户正在处理中
|
|
87
|
-
*/
|
|
88
|
-
typingIndicator?: TypingIndicatorConfig;
|
|
89
44
|
/**
|
|
90
45
|
* 出站图片/语音/视频:file=本机临时文件路径(需与 icqq 守护进程同机可读);
|
|
91
46
|
* base64=经 CQ `base64://` 交给守护进程解码(Zhin 与 icqq 异进程/异机时用,配置 rpc 时默认 base64)。
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ICQQ Typing Indicator 使用示例
|
|
3
|
-
*
|
|
4
|
-
* 展示如何在 ICQQ 适配器中集成消息处理状态提示
|
|
5
|
-
*/
|
|
6
|
-
import type { IcqqEndpoint } from './endpoint.js';
|
|
7
|
-
import type { IcqqAdapter } from './adapter.js';
|
|
8
|
-
import { ICQQTypingIndicatorManager, type ICQQTypingIndicatorConfig } from './typing-indicator.js';
|
|
9
|
-
/**
|
|
10
|
-
* 在 ICQQ 适配器初始化时启用 Typing Indicator
|
|
11
|
-
*/
|
|
12
|
-
export declare function enableTypingIndicatorForAdapter(adapter: IcqqAdapter): void;
|
|
13
|
-
/**
|
|
14
|
-
* 在消息处理流程中使用 Typing Indicator
|
|
15
|
-
*
|
|
16
|
-
* 使用示例:
|
|
17
|
-
* ```typescript
|
|
18
|
-
* // 在消息处理开始时
|
|
19
|
-
* const indicator = await startTypingIndicator(endpoint, {
|
|
20
|
-
* messageId: message.$id,
|
|
21
|
-
* sessionId: `${message.$channel.type}:${message.$channel.id}`,
|
|
22
|
-
* userId: message.$sender.id,
|
|
23
|
-
* groupId: message.$channel.type === 'group' ? message.$channel.id : undefined,
|
|
24
|
-
* sceneType: message.$channel.type as 'private' | 'group',
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* try {
|
|
28
|
-
* // 执行 AI 处理
|
|
29
|
-
* const result = await processMessage(message);
|
|
30
|
-
* return result;
|
|
31
|
-
* } finally {
|
|
32
|
-
* // 处理完成后停止提示
|
|
33
|
-
* await indicator.stop();
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export declare function withTypingIndicator<T>(endpoint: IcqqEndpoint, options: {
|
|
38
|
-
messageId?: string;
|
|
39
|
-
sessionId: string;
|
|
40
|
-
userId?: string;
|
|
41
|
-
groupId?: string;
|
|
42
|
-
sceneType: 'private' | 'group';
|
|
43
|
-
}, fn: () => Promise<T>): Promise<T>;
|
|
44
|
-
/**
|
|
45
|
-
* 自定义 Typing Indicator 配置
|
|
46
|
-
*/
|
|
47
|
-
export declare const customConfigs: Record<string, Partial<ICQQTypingIndicatorConfig>>;
|
|
48
|
-
/**
|
|
49
|
-
* 在 ICQQ 插件中集成 Typing Indicator
|
|
50
|
-
*
|
|
51
|
-
* 在 `index.ts` 中添加:
|
|
52
|
-
* ```typescript
|
|
53
|
-
* import { enableTypingIndicatorForAdapter } from './typing-indicator-example.js';
|
|
54
|
-
*
|
|
55
|
-
* // 在适配器 mounted 后启用
|
|
56
|
-
* provide({
|
|
57
|
-
* name: "icqq",
|
|
58
|
-
* mounted: async (p: Plugin) => {
|
|
59
|
-
* const adapter = new IcqqAdapter(p);
|
|
60
|
-
* await adapter.start();
|
|
61
|
-
*
|
|
62
|
-
* // 启用 Typing Indicator
|
|
63
|
-
* enableTypingIndicatorForAdapter(adapter);
|
|
64
|
-
*
|
|
65
|
-
* return adapter;
|
|
66
|
-
* },
|
|
67
|
-
* });
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
/**
|
|
71
|
-
* 监听消息事件并自动添加 Typing Indicator
|
|
72
|
-
*/
|
|
73
|
-
export declare function setupTypingIndicatorHooks(adapter: IcqqAdapter): void;
|
|
74
|
-
/**
|
|
75
|
-
* 批量管理多个 Endpoint 的 Typing Indicator
|
|
76
|
-
*/
|
|
77
|
-
export declare class BatchTypingIndicatorManager {
|
|
78
|
-
private managers;
|
|
79
|
-
/**
|
|
80
|
-
* 注册 Endpoint
|
|
81
|
-
*/
|
|
82
|
-
registerEndpoint(endpoint: IcqqEndpoint, config?: Partial<ICQQTypingIndicatorConfig>): void;
|
|
83
|
-
/**
|
|
84
|
-
* 开始提示
|
|
85
|
-
*/
|
|
86
|
-
start(endpointId: string, options: {
|
|
87
|
-
messageId?: string;
|
|
88
|
-
sessionId: string;
|
|
89
|
-
userId?: string;
|
|
90
|
-
groupId?: string;
|
|
91
|
-
sceneType: 'private' | 'group';
|
|
92
|
-
}): Promise<void>;
|
|
93
|
-
/**
|
|
94
|
-
* 停止提示
|
|
95
|
-
*/
|
|
96
|
-
stop(endpointId: string, options: {
|
|
97
|
-
sessionId: string;
|
|
98
|
-
}): Promise<void>;
|
|
99
|
-
/**
|
|
100
|
-
* 停止所有提示
|
|
101
|
-
*/
|
|
102
|
-
stopAll(): Promise<void>;
|
|
103
|
-
/**
|
|
104
|
-
* 获取 Endpoint 的管理器
|
|
105
|
-
*/
|
|
106
|
-
getManager(endpointId: string): ICQQTypingIndicatorManager | undefined;
|
|
107
|
-
}
|
|
108
|
-
//# sourceMappingURL=typing-indicator-example.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"typing-indicator-example.d.ts","sourceRoot":"","sources":["../src/typing-indicator-example.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,0BAA0B,EAG1B,KAAK,yBAAyB,EAC/B,MAAM,uBAAuB,CAAC;AAI/B;;GAEG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CA0B1E;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;CAChC,EACD,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAkBZ;AAID;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAyC5E,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAwBpE;AAID;;GAEG;AACH,qBAAa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAsD;IAEtE;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,GAAG,IAAI;IAK3F;;OAEG;IACG,KAAK,CACT,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;KAChC,GACA,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7E;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS;CAGvE"}
|