@zhin.js/adapter-icqq 6.0.1 → 6.0.3
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 +59 -0
- package/README.md +32 -437
- package/adapters/icqq.ts +30 -0
- package/agent/tools/announce.ts +21 -0
- package/agent/tools/essence.ts +22 -0
- package/agent/tools/friend_list.ts +19 -0
- package/agent/tools/group_files.ts +27 -0
- package/agent/tools/group_invite.ts +20 -0
- package/agent/tools/group_list.ts +20 -0
- package/agent/tools/group_sign.ts +19 -0
- package/agent/tools/list_muted.ts +22 -0
- package/agent/tools/poke.ts +20 -0
- package/agent/tools/send_user_like.ts +22 -0
- package/agent/tools/set_anonymous.ts +22 -0
- package/agent/tools/set_title.ts +25 -0
- package/lib/agent-prompt.d.ts +0 -1
- package/lib/agent-prompt.js +0 -1
- package/lib/cq-message.d.ts +0 -1
- package/lib/cq-message.js +0 -1
- package/lib/endpoint.d.ts +45 -97
- package/lib/endpoint.js +357 -628
- package/lib/forward-msg.d.ts +1 -2
- package/lib/forward-msg.js +4 -2
- package/lib/icqq-agent-deps.d.ts +25 -0
- package/lib/icqq-agent-deps.js +32 -0
- package/lib/icqq-guild.d.ts +1 -2
- package/lib/icqq-guild.js +4 -2
- package/lib/icqq-inbound.d.ts +10 -1
- package/lib/icqq-inbound.js +27 -3
- package/lib/index.d.ts +6 -18
- package/lib/index.js +6 -50
- package/lib/ipc-client.d.ts +0 -1
- package/lib/ipc-client.js +0 -1
- package/lib/outbound-media.d.ts +0 -1
- package/lib/outbound-media.js +0 -1
- package/lib/protocol.d.ts +73 -2
- package/lib/protocol.js +124 -2
- package/lib/types.d.ts +0 -1
- package/lib/types.js +0 -1
- package/package.json +51 -43
- package/plugin.ts +32 -0
- package/schema.json +35 -0
- package/src/agent-prompt.ts +1 -2
- package/src/endpoint.ts +399 -800
- package/src/forward-msg.ts +2 -2
- package/src/icqq-agent-deps.ts +57 -0
- package/src/icqq-guild.ts +1 -2
- package/src/icqq-inbound.ts +31 -2
- package/src/index.ts +35 -69
- package/src/protocol.ts +191 -1
- package/client/ICQQManagement.tsx +0 -147
- package/client/LoginAssistPanel.tsx +0 -300
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -34
- package/lib/adapter.d.ts +0 -46
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -128
- package/lib/adapter.js.map +0 -1
- package/lib/agent-prompt.d.ts.map +0 -1
- package/lib/agent-prompt.js.map +0 -1
- package/lib/cq-message.d.ts.map +0 -1
- package/lib/cq-message.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/forward-msg.d.ts.map +0 -1
- package/lib/forward-msg.js.map +0 -1
- package/lib/get-msg.d.ts +0 -3
- package/lib/get-msg.d.ts.map +0 -1
- package/lib/get-msg.js +0 -46
- package/lib/get-msg.js.map +0 -1
- package/lib/icqq-guild.d.ts.map +0 -1
- package/lib/icqq-guild.js.map +0 -1
- package/lib/icqq-inbound.d.ts.map +0 -1
- package/lib/icqq-inbound.js.map +0 -1
- package/lib/icqq-login-assist-bridge.d.ts +0 -5
- package/lib/icqq-login-assist-bridge.d.ts.map +0 -1
- package/lib/icqq-login-assist-bridge.js +0 -148
- package/lib/icqq-login-assist-bridge.js.map +0 -1
- package/lib/icqq-side-events.d.ts +0 -34
- package/lib/icqq-side-events.d.ts.map +0 -1
- package/lib/icqq-side-events.js +0 -194
- package/lib/icqq-side-events.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/ipc-client.d.ts.map +0 -1
- package/lib/ipc-client.js.map +0 -1
- package/lib/login-assist-routes.d.ts +0 -7
- package/lib/login-assist-routes.d.ts.map +0 -1
- package/lib/login-assist-routes.js +0 -29
- package/lib/login-assist-routes.js.map +0 -1
- package/lib/login-ipc-contract.d.ts +0 -44
- package/lib/login-ipc-contract.d.ts.map +0 -1
- package/lib/login-ipc-contract.js +0 -35
- package/lib/login-ipc-contract.js.map +0 -1
- package/lib/outbound-media.d.ts.map +0 -1
- package/lib/outbound-media.js.map +0 -1
- package/lib/protocol.d.ts.map +0 -1
- package/lib/protocol.js.map +0 -1
- package/lib/routes.d.ts +0 -8
- package/lib/routes.d.ts.map +0 -1
- package/lib/routes.js +0 -67
- package/lib/routes.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/tools/index.d.ts +0 -10
- package/lib/tools/index.d.ts.map +0 -1
- package/lib/tools/index.js +0 -341
- package/lib/tools/index.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -151
- package/src/get-msg.ts +0 -56
- package/src/icqq-login-assist-bridge.ts +0 -196
- package/src/icqq-side-events.ts +0 -228
- package/src/login-assist-routes.ts +0 -39
- package/src/login-ipc-contract.ts +0 -52
- package/src/routes.ts +0 -83
- package/src/segment-mapper.ts +0 -1
- package/src/tools/index.ts +0 -412
- /package/{skills/icqq → agent}/PERMITS.md +0 -0
|
@@ -1,196 +0,0 @@
|
|
|
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/icqq-side-events.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* icqq IPC 非 message 入站:notice / request / meta(对齐 OneBot 与 @icqqjs/icqq EventMap)
|
|
3
|
-
* @see https://icqq.pages.dev/interfaces/EventMap.html
|
|
4
|
-
*/
|
|
5
|
-
import { Notice, Request, formatCompact } from "zhin.js";
|
|
6
|
-
import type { NoticeType } from "zhin.js";
|
|
7
|
-
import type { RequestType } from "zhin.js";
|
|
8
|
-
import { Actions } from "./protocol.js";
|
|
9
|
-
import type { IpcClient } from "./ipc-client.js";
|
|
10
|
-
|
|
11
|
-
/** OneBot / icqq 守护进程推送的通用事件字段 */
|
|
12
|
-
export interface IcqqIpcRawEvent {
|
|
13
|
-
post_type?: string;
|
|
14
|
-
time?: number;
|
|
15
|
-
self_id?: number;
|
|
16
|
-
notice_type?: string;
|
|
17
|
-
request_type?: string;
|
|
18
|
-
sub_type?: string;
|
|
19
|
-
meta_event_type?: string;
|
|
20
|
-
group_id?: number;
|
|
21
|
-
user_id?: number;
|
|
22
|
-
operator_id?: number;
|
|
23
|
-
flag?: string;
|
|
24
|
-
comment?: string;
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function resolveIcqqEventPostType(
|
|
29
|
-
data: Record<string, unknown>,
|
|
30
|
-
): string | undefined {
|
|
31
|
-
const pt = data.post_type;
|
|
32
|
-
if (typeof pt === "string" && pt !== "") return pt;
|
|
33
|
-
if (data.notice_type != null) return "notice";
|
|
34
|
-
if (data.request_type != null) return "request";
|
|
35
|
-
if (data.meta_event_type != null) return "meta_event";
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function isIcqqNoticePayload(data: unknown): data is IcqqIpcRawEvent {
|
|
40
|
-
if (!data || typeof data !== "object") return false;
|
|
41
|
-
const pt = resolveIcqqEventPostType(data as Record<string, unknown>);
|
|
42
|
-
return pt === "notice" || (typeof pt === "string" && pt.startsWith("notice."));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function isIcqqRequestPayload(data: unknown): data is IcqqIpcRawEvent {
|
|
46
|
-
if (!data || typeof data !== "object") return false;
|
|
47
|
-
const pt = resolveIcqqEventPostType(data as Record<string, unknown>);
|
|
48
|
-
return pt === "request" || (typeof pt === "string" && pt.startsWith("request."));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function isIcqqMetaPayload(data: unknown): data is IcqqIpcRawEvent {
|
|
52
|
-
if (!data || typeof data !== "object") return false;
|
|
53
|
-
const pt = resolveIcqqEventPostType(data as Record<string, unknown>);
|
|
54
|
-
return pt === "meta_event" || (typeof pt === "string" && pt.startsWith("system."));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 去重用:notice / request 无稳定 id 时组合键 */
|
|
58
|
-
export function resolveSideEventDedupeKey(
|
|
59
|
-
data: IcqqIpcRawEvent,
|
|
60
|
-
kind: "notice" | "request" | "meta",
|
|
61
|
-
): string {
|
|
62
|
-
if (data.flag != null && String(data.flag) !== "") {
|
|
63
|
-
return `${kind}:${data.flag}`;
|
|
64
|
-
}
|
|
65
|
-
const time = data.time ?? 0;
|
|
66
|
-
const type =
|
|
67
|
-
data.notice_type ?? data.request_type ?? data.meta_event_type ?? kind;
|
|
68
|
-
const scope = data.group_id ?? data.user_id ?? "";
|
|
69
|
-
const sub = data.sub_type ?? "";
|
|
70
|
-
return `${kind}:${time}_${type}_${scope}_${sub}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const NOTICE_TYPE_MAP: Record<string, NoticeType> = {
|
|
74
|
-
group_increase: "group_member_increase",
|
|
75
|
-
group_decrease: "group_member_decrease",
|
|
76
|
-
group_admin: "group_admin_change",
|
|
77
|
-
group_ban: "group_ban",
|
|
78
|
-
group_recall: "group_recall",
|
|
79
|
-
friend_recall: "friend_recall",
|
|
80
|
-
friend_add: "friend_add",
|
|
81
|
-
group_upload: "group_upload",
|
|
82
|
-
group_sign: "group_sign",
|
|
83
|
-
group_transfer: "group_transfer",
|
|
84
|
-
// icqq EventMap 命名 → Zhin NoticeType
|
|
85
|
-
"group.increase": "group_member_increase",
|
|
86
|
-
"group.decrease": "group_member_decrease",
|
|
87
|
-
"group.admin": "group_admin_change",
|
|
88
|
-
"group.ban": "group_ban",
|
|
89
|
-
"group.recall": "group_recall",
|
|
90
|
-
"friend.increase": "friend_add",
|
|
91
|
-
"friend.decrease": "friend_recall",
|
|
92
|
-
"friend.recall": "friend_recall",
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
function resolveNoticeZhinType(event: IcqqIpcRawEvent): NoticeType {
|
|
96
|
-
const raw = event.notice_type ?? "";
|
|
97
|
-
if (NOTICE_TYPE_MAP[raw]) return NOTICE_TYPE_MAP[raw];
|
|
98
|
-
if (raw === "notify") {
|
|
99
|
-
if (event.sub_type === "poke") {
|
|
100
|
-
return event.group_id != null ? "group_poke" : "friend_poke";
|
|
101
|
-
}
|
|
102
|
-
return `notify_${event.sub_type ?? "unknown"}` as NoticeType;
|
|
103
|
-
}
|
|
104
|
-
if (raw.startsWith("notice.")) {
|
|
105
|
-
const suffix = raw.slice("notice.".length).replace(/\./g, "_");
|
|
106
|
-
return (NOTICE_TYPE_MAP[suffix] ?? suffix) as NoticeType;
|
|
107
|
-
}
|
|
108
|
-
return raw as NoticeType;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function senderFromId(id: unknown, name?: string) {
|
|
112
|
-
if (id == null) return undefined;
|
|
113
|
-
const s = String(id);
|
|
114
|
-
return { id: s, name: name ?? s };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function formatIcqqNotice(
|
|
118
|
-
event: IcqqIpcRawEvent,
|
|
119
|
-
endpointName: string,
|
|
120
|
-
): ReturnType<typeof Notice.from<IcqqIpcRawEvent>> {
|
|
121
|
-
const $type = resolveNoticeZhinType(event);
|
|
122
|
-
const isGroup = event.group_id != null;
|
|
123
|
-
const channelId = String(
|
|
124
|
-
event.group_id ?? event.user_id ?? event.operator_id ?? "",
|
|
125
|
-
);
|
|
126
|
-
const tsSec = event.time ?? Math.floor(Date.now() / 1000);
|
|
127
|
-
|
|
128
|
-
return Notice.from(event, {
|
|
129
|
-
$id: resolveSideEventDedupeKey(event, "notice"),
|
|
130
|
-
$adapter: "icqq",
|
|
131
|
-
$endpoint: endpointName,
|
|
132
|
-
$type,
|
|
133
|
-
$subType: event.sub_type,
|
|
134
|
-
$channel: {
|
|
135
|
-
id: channelId,
|
|
136
|
-
type: isGroup ? "group" : "private",
|
|
137
|
-
},
|
|
138
|
-
$operator: senderFromId(event.operator_id),
|
|
139
|
-
$target: senderFromId(event.user_id),
|
|
140
|
-
$timestamp: tsSec * 1000,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function resolveRequestZhinType(event: IcqqIpcRawEvent): RequestType {
|
|
145
|
-
const rt = event.request_type ?? "";
|
|
146
|
-
if (rt === "friend" || rt.startsWith("friend")) return "friend_add";
|
|
147
|
-
if (rt === "group" || rt.startsWith("group")) {
|
|
148
|
-
return event.sub_type === "invite" ? "group_invite" : "group_add";
|
|
149
|
-
}
|
|
150
|
-
return rt as RequestType;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function formatIcqqRequest(
|
|
154
|
-
event: IcqqIpcRawEvent,
|
|
155
|
-
endpointName: string,
|
|
156
|
-
ipc: IpcClient,
|
|
157
|
-
): ReturnType<typeof Request.from<IcqqIpcRawEvent>> {
|
|
158
|
-
const $type = resolveRequestZhinType(event);
|
|
159
|
-
const isGroup = event.group_id != null;
|
|
160
|
-
const channelId = String(event.group_id ?? event.user_id ?? "");
|
|
161
|
-
const tsSec = event.time ?? Math.floor(Date.now() / 1000);
|
|
162
|
-
const flag = event.flag ?? resolveSideEventDedupeKey(event, "request");
|
|
163
|
-
|
|
164
|
-
const isFriend =
|
|
165
|
-
event.request_type === "friend" ||
|
|
166
|
-
(typeof event.request_type === "string" &&
|
|
167
|
-
event.request_type.startsWith("friend"));
|
|
168
|
-
|
|
169
|
-
return Request.from(event, {
|
|
170
|
-
$id: String(flag),
|
|
171
|
-
$adapter: "icqq",
|
|
172
|
-
$endpoint: endpointName,
|
|
173
|
-
$type,
|
|
174
|
-
$subType: event.sub_type,
|
|
175
|
-
$channel: {
|
|
176
|
-
id: channelId,
|
|
177
|
-
type: isGroup ? "group" : "private",
|
|
178
|
-
},
|
|
179
|
-
$sender: senderFromId(event.user_id) ?? { id: "", name: "" },
|
|
180
|
-
$comment: event.comment,
|
|
181
|
-
$timestamp: tsSec * 1000,
|
|
182
|
-
$approve: async (remark?: string) => {
|
|
183
|
-
const action = isFriend
|
|
184
|
-
? Actions.HANDLE_FRIEND_REQUEST
|
|
185
|
-
: Actions.HANDLE_GROUP_REQUEST;
|
|
186
|
-
const params: Record<string, unknown> = {
|
|
187
|
-
flag: event.flag ?? flag,
|
|
188
|
-
approve: true,
|
|
189
|
-
};
|
|
190
|
-
if (remark) params.remark = remark;
|
|
191
|
-
if (!isFriend && event.sub_type) params.sub_type = event.sub_type;
|
|
192
|
-
const resp = await ipc.request(action, params);
|
|
193
|
-
if (!resp.ok) throw new Error(resp.error ?? "同意请求失败");
|
|
194
|
-
},
|
|
195
|
-
$reject: async (reason?: string) => {
|
|
196
|
-
const action = isFriend
|
|
197
|
-
? Actions.HANDLE_FRIEND_REQUEST
|
|
198
|
-
: Actions.HANDLE_GROUP_REQUEST;
|
|
199
|
-
const params: Record<string, unknown> = {
|
|
200
|
-
flag: event.flag ?? flag,
|
|
201
|
-
approve: false,
|
|
202
|
-
};
|
|
203
|
-
if (reason) params.reason = reason;
|
|
204
|
-
if (!isFriend && event.sub_type) params.sub_type = event.sub_type;
|
|
205
|
-
const resp = await ipc.request(action, params);
|
|
206
|
-
if (!resp.ok) throw new Error(resp.error ?? "拒绝请求失败");
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** meta_event / system.*:返回是否建议在上线后刷新好友/群列表 */
|
|
212
|
-
export function shouldRefreshListsOnMeta(event: IcqqIpcRawEvent): boolean {
|
|
213
|
-
if (event.meta_event_type === "lifecycle") {
|
|
214
|
-
const sub = event.sub_type;
|
|
215
|
-
return sub === "connect" || sub === "enable";
|
|
216
|
-
}
|
|
217
|
-
const pt = event.post_type ?? "";
|
|
218
|
-
if (pt === "system.online" || pt.endsWith(".online")) return true;
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function formatIcqqMetaLog(event: IcqqIpcRawEvent): string {
|
|
223
|
-
return formatCompact({
|
|
224
|
-
meta: event.meta_event_type ?? event.post_type ?? "meta",
|
|
225
|
-
sub_type: event.sub_type,
|
|
226
|
-
time: event.time,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
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/routes.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ICQQ HTTP 路由注册 — 通过 IPC 查询守护进程数据
|
|
3
|
-
*/
|
|
4
|
-
import type { Router } from "@zhin.js/host-router";
|
|
5
|
-
import type { Plugin } from "zhin.js";
|
|
6
|
-
import type { IcqqAdapter } from "./adapter.js";
|
|
7
|
-
import { Actions } from "./protocol.js";
|
|
8
|
-
|
|
9
|
-
export function registerRoutes(
|
|
10
|
-
router: Router,
|
|
11
|
-
icqq: IcqqAdapter,
|
|
12
|
-
_root: Plugin,
|
|
13
|
-
) {
|
|
14
|
-
router.get("/api/icqq/endpoints", async (ctx) => {
|
|
15
|
-
try {
|
|
16
|
-
const endpoints = Array.from(icqq.endpoints.values());
|
|
17
|
-
if (endpoints.length === 0) {
|
|
18
|
-
ctx.body = { success: true, data: [], message: "暂无ICQQEndpoint 实例" };
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const result = await Promise.all(
|
|
22
|
-
endpoints.map(async (endpoint) => {
|
|
23
|
-
try {
|
|
24
|
-
const base: Record<string, unknown> = {
|
|
25
|
-
name: endpoint.$config.name,
|
|
26
|
-
connected: endpoint.$connected || false,
|
|
27
|
-
groupCount: endpoint.groups.size,
|
|
28
|
-
friendCount: endpoint.friends.size,
|
|
29
|
-
status: endpoint.$connected ? "online" : "offline",
|
|
30
|
-
lastActivity: new Date().toISOString(),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// 尝试从守护进程获取详细状态
|
|
34
|
-
if (endpoint.$connected && endpoint.ipc && !endpoint.ipc.closed) {
|
|
35
|
-
try {
|
|
36
|
-
const statusResp = await endpoint.ipc.request(
|
|
37
|
-
Actions.GET_STATUS,
|
|
38
|
-
{},
|
|
39
|
-
5000,
|
|
40
|
-
);
|
|
41
|
-
if (statusResp.ok && statusResp.data) {
|
|
42
|
-
const stat = statusResp.data as Record<string, unknown>;
|
|
43
|
-
base.receiveCount = stat.recv_msg_cnt ?? 0;
|
|
44
|
-
base.sendCount = stat.sent_msg_cnt ?? 0;
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// status 查询超时,忽略
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return base;
|
|
52
|
-
} catch {
|
|
53
|
-
return {
|
|
54
|
-
name: endpoint.$config.name,
|
|
55
|
-
connected: false,
|
|
56
|
-
groupCount: 0,
|
|
57
|
-
friendCount: 0,
|
|
58
|
-
status: "error",
|
|
59
|
-
error: "数据获取失败",
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
ctx.body = {
|
|
65
|
-
success: true,
|
|
66
|
-
data: result,
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
};
|
|
69
|
-
} catch (error) {
|
|
70
|
-
ctx.status = 500;
|
|
71
|
-
ctx.body = {
|
|
72
|
-
success: false,
|
|
73
|
-
error: "ICQQ_API_ERROR",
|
|
74
|
-
message: "获取 Endpoint 数据失败",
|
|
75
|
-
details:
|
|
76
|
-
process.env.NODE_ENV === "development"
|
|
77
|
-
? (error as Error).message
|
|
78
|
-
: undefined,
|
|
79
|
-
timestamp: new Date().toISOString(),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
package/src/segment-mapper.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
|