@zhin.js/adapter-icqq 8.0.13 → 8.0.14

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.
@@ -1,25 +1,6 @@
1
- import { type MessageSegment, type QuotedMessagePayload } from 'zhin.js';
2
1
  import { type IcqqMessageElement } from "./icqq-inbound.js";
2
+ import type { ForwardEntry } from '@zhin.js/im-contract';
3
3
  /** 从 QQ 合并转发 json 元素提取 resid */
4
4
  export declare function extractForwardResidFromJsonElement(el: IcqqMessageElement): string | undefined;
5
- /** 从 get_msg 的 raw_message 解析 multimsg resid(XML / JSON / CQ:json) */
6
- export declare function extractForwardResidFromRawMessage(raw: string): string | undefined;
7
- /** 从 get_msg 的 message 元素数组解析 multimsg resid */
8
- export declare function extractForwardResidFromGetMsgElements(elements: IcqqMessageElement[] | undefined): string | undefined;
9
- /**
10
- * 从 get_msg 响应解析合并转发 resid(优先 message 数组,其次 raw_message)。
11
- */
12
- export declare function extractForwardResidFromGetMsg(data: unknown): string | undefined;
13
- export declare function extractForwardResidFromSegments(content: MessageSegment[]): string | undefined;
14
- export declare function extractForwardResidFromPayload(payload: QuotedMessagePayload): string | undefined;
15
- /** 在 get_msg 原始对象里深度搜索 resid */
16
- export declare function extractForwardResidDeep(root: unknown, maxDepth?: number): string | undefined;
17
- export declare function isForwardPlaceholderPayload(payload: QuotedMessagePayload): boolean;
18
- /** 将 get_forward_msg 响应格式化为可读聊天记录 */
19
- export declare function formatForwardMsgResponse(data: unknown): string;
20
- export declare function fetchForwardMsgText(client: {
21
- getForwardMsg(resId: string, fileName?: string): Promise<unknown[]>;
22
- }, id: string): Promise<string>;
23
- export declare function enrichQuotedPayloadWithForward(client: {
24
- getForwardMsg(resId: string, fileName?: string): Promise<unknown[]>;
25
- } | null | undefined, payload: QuotedMessagePayload, rawData?: unknown): Promise<QuotedMessagePayload>;
5
+ /** Canonical merged-forward entries. Speakers are data actors, never LLM roles. */
6
+ export declare function normalizeForwardMsgResponse(data: unknown): readonly ForwardEntry[];
@@ -1,8 +1,6 @@
1
- import { segment } from 'zhin.js';
2
1
  import { icqqElementsToSegments, } from "./icqq-inbound.js";
3
- import { parseCqMessage } from "./cq-message.js";
2
+ import { toCanonicalSegments } from '@zhin.js/core';
4
3
  const RESID_IN_XML_RE = /(?:m_resid|resid|fileid|res_id)=["']?([A-Za-z0-9+/=_.-]{8,})/i;
5
- const FORWARD_PLACEHOLDER_RE = /\[聊天记录\]|合并转发|转发消息|聊天记录/i;
6
4
  function parseJsonPayload(raw) {
7
5
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
8
6
  return raw;
@@ -64,313 +62,46 @@ export function extractForwardResidFromJsonElement(el) {
64
62
  return String(direct).trim();
65
63
  return undefined;
66
64
  }
67
- /** 从 get_msg 的 raw_message 解析 multimsg resid(XML / JSON / CQ:json) */
68
- export function extractForwardResidFromRawMessage(raw) {
69
- const text = raw.trim();
70
- if (!text)
71
- return undefined;
72
- const xml = text.match(RESID_IN_XML_RE);
73
- if (xml?.[1])
74
- return xml[1].trim();
75
- const asJson = parseJsonPayload(text);
76
- if (asJson) {
77
- const fromJson = extractForwardResidFromJsonElement({
78
- type: "json",
79
- data: asJson,
80
- });
81
- if (fromJson)
82
- return fromJson;
83
- }
84
- for (const match of text.matchAll(/\[CQ:json(?:,([^\]]*))?\]/gi)) {
85
- const arg = match[1] ?? "";
86
- const dataMatch = arg.match(/(?:^|,)data=(.+)$/s);
87
- if (!dataMatch?.[1])
88
- continue;
89
- const record = parseJsonPayload(dataMatch[1].trim());
90
- if (!record)
91
- continue;
92
- const fromCq = extractForwardResidFromJsonElement({
93
- type: "json",
94
- data: record,
95
- });
96
- if (fromCq)
97
- return fromCq;
98
- }
99
- const segs = parseCqMessage(text);
100
- return extractForwardResidFromSegments(segs);
101
- }
102
- /** 从 get_msg 的 message 元素数组解析 multimsg resid */
103
- export function extractForwardResidFromGetMsgElements(elements) {
104
- if (!elements?.length)
105
- return undefined;
106
- for (const el of elements) {
107
- if (!el || typeof el !== "object")
108
- continue;
109
- if (el.type === "forward") {
110
- const id = String(el.id ?? el.resid ?? el.res_id ?? el.file ?? "").trim();
111
- if (id)
112
- return id;
113
- }
114
- const fromJson = extractForwardResidFromJsonElement(el);
115
- if (fromJson)
116
- return fromJson;
117
- }
118
- const segs = icqqElementsToSegments(elements);
119
- if (segs?.length) {
120
- return extractForwardResidFromSegments(segs);
121
- }
122
- return extractForwardResidDeep(elements);
123
- }
124
- /**
125
- * 从 get_msg 响应解析合并转发 resid(优先 message 数组,其次 raw_message)。
126
- */
127
- export function extractForwardResidFromGetMsg(data) {
128
- if (!data || typeof data !== "object")
129
- return undefined;
130
- const record = data;
131
- if (Array.isArray(record.message)) {
132
- const fromElements = extractForwardResidFromGetMsgElements(record.message);
133
- if (fromElements)
134
- return fromElements;
135
- }
136
- if (typeof record.raw_message === "string" && record.raw_message.trim()) {
137
- const fromRaw = extractForwardResidFromRawMessage(record.raw_message);
138
- if (fromRaw)
139
- return fromRaw;
140
- }
141
- if (typeof record.message === "string" && record.message.trim()) {
142
- return extractForwardResidFromRawMessage(record.message);
143
- }
144
- return undefined;
145
- }
146
- export function extractForwardResidFromSegments(content) {
147
- for (const seg of content) {
148
- if (seg.type === "forward") {
149
- const id = seg.data?.id ?? seg.data?.resid;
150
- if (id != null && String(id).trim())
151
- return String(id).trim();
152
- }
153
- if (seg.type === "json" && seg.data) {
154
- const resid = extractForwardResidFromJsonElement(seg.data);
155
- if (resid)
156
- return resid;
157
- const raw = seg.data.data ?? seg.data.text;
158
- const record = parseJsonPayload(raw);
159
- if (record) {
160
- const fromEl = extractForwardResidFromJsonElement({
161
- type: "json",
162
- data: record,
163
- });
164
- if (fromEl)
165
- return fromEl;
166
- }
167
- }
168
- }
169
- const raw = segment.raw(content);
170
- const m = raw.match(RESID_IN_XML_RE);
171
- return m?.[1]?.trim();
172
- }
173
- export function extractForwardResidFromPayload(payload) {
174
- if (Array.isArray(payload.content)) {
175
- const fromSeg = extractForwardResidFromSegments(payload.content);
176
- if (fromSeg)
177
- return fromSeg;
178
- }
179
- if (typeof payload.raw === "string") {
180
- const m = payload.raw.match(RESID_IN_XML_RE);
181
- if (m?.[1])
182
- return m[1].trim();
183
- const record = parseJsonPayload(payload.raw);
184
- if (record) {
185
- return extractForwardResidFromJsonElement({ type: "json", data: record });
186
- }
187
- }
188
- return undefined;
189
- }
190
- /** 在 get_msg 原始对象里深度搜索 resid */
191
- export function extractForwardResidDeep(root, maxDepth = 14) {
192
- const seen = new Set();
193
- function walk(node, depth) {
194
- if (depth > maxDepth || node == null)
195
- return undefined;
196
- if (typeof node === "string") {
197
- const m = node.match(RESID_IN_XML_RE);
198
- if (m?.[1])
199
- return m[1].trim();
200
- const record = parseJsonPayload(node);
201
- if (record) {
202
- const fromJson = extractForwardResidFromJsonElement({
203
- type: "json",
204
- data: record,
205
- });
206
- if (fromJson)
207
- return fromJson;
208
- }
209
- return undefined;
210
- }
211
- if (typeof node !== "object")
212
- return undefined;
213
- if (seen.has(node))
214
- return undefined;
215
- seen.add(node);
216
- if (Array.isArray(node)) {
217
- for (const item of node) {
218
- const hit = walk(item, depth + 1);
219
- if (hit)
220
- return hit;
221
- }
222
- return undefined;
223
- }
224
- const fromEl = extractForwardResidFromJsonElement({
225
- type: "json",
226
- data: node,
227
- });
228
- if (fromEl)
229
- return fromEl;
230
- for (const value of Object.values(node)) {
231
- const hit = walk(value, depth + 1);
232
- if (hit)
233
- return hit;
234
- }
235
- return undefined;
236
- }
237
- return walk(root, 0);
238
- }
239
- export function isForwardPlaceholderPayload(payload) {
240
- if (Array.isArray(payload.content) &&
241
- payload.content.some((s) => s.type === "forward")) {
242
- return true;
243
- }
244
- const raw = Array.isArray(payload.content)
245
- ? segment.raw(payload.content)
246
- : String(payload.content ?? payload.raw ?? "");
247
- return FORWARD_PLACEHOLDER_RE.test(raw);
248
- }
249
- function hasMergedForwardBlock(payload) {
250
- const raw = Array.isArray(payload.content)
251
- ? segment.raw(payload.content)
252
- : String(payload.content ?? "");
253
- return raw.includes("[Merged chat history");
254
- }
255
- function senderLabel(sender) {
256
- if (!sender || typeof sender !== "object")
257
- return "unknown";
258
- const s = sender;
259
- const name = s.nickname ?? s.name ?? s.card;
260
- const id = s.user_id ?? s.uin ?? s.uid ?? s.id;
261
- if (name && id)
262
- return `${name} (${id})`;
263
- if (name)
264
- return String(name);
265
- if (id != null)
266
- return String(id);
267
- return "unknown";
268
- }
269
- function messageBodyToText(body) {
270
- if (body == null)
271
- return "";
272
- if (typeof body === "string")
273
- return body.trim();
274
- if (Array.isArray(body)) {
275
- const segs = icqqElementsToSegments(body);
276
- return segs?.length ? segment.raw(segs).trim() : segment.raw(body).trim();
277
- }
278
- if (typeof body === "object") {
279
- const r = body;
280
- if (typeof r.raw_message === "string" && r.raw_message.trim()) {
281
- return r.raw_message.trim();
282
- }
283
- if (Array.isArray(r.message)) {
284
- return messageBodyToText(r.message);
285
- }
286
- if (Array.isArray(r.elements)) {
287
- return messageBodyToText(r.elements);
288
- }
289
- if (typeof r.content === "string")
290
- return r.content.trim();
291
- }
292
- return "";
293
- }
294
- /** 将 get_forward_msg 响应格式化为可读聊天记录 */
295
- export function formatForwardMsgResponse(data) {
296
- if (!data)
297
- return "";
298
- const root = data && typeof data === "object" && !Array.isArray(data)
65
+ /** Canonical merged-forward entries. Speakers are data actors, never LLM roles. */
66
+ export function normalizeForwardMsgResponse(data) {
67
+ const root = data && typeof data === 'object' && !Array.isArray(data)
299
68
  ? data
300
- : null;
69
+ : undefined;
301
70
  const list = Array.isArray(data)
302
71
  ? data
303
- : Array.isArray(root?.messages)
304
- ? root.messages
305
- : Array.isArray(root?.msgList)
306
- ? root.msgList
307
- : Array.isArray(root?.msg_list)
308
- ? root.msg_list
309
- : Array.isArray(root?.message)
310
- ? root.message
72
+ : Array.isArray(root?.messages) ? root.messages
73
+ : Array.isArray(root?.msgList) ? root.msgList
74
+ : Array.isArray(root?.msg_list) ? root.msg_list
75
+ : Array.isArray(root?.message) ? root.message
311
76
  : [];
312
- if (!list.length)
313
- return "";
314
- const lines = [];
315
- let index = 0;
316
- for (const item of list) {
317
- if (!item || typeof item !== "object")
318
- continue;
77
+ return Object.freeze(list.flatMap((item) => {
78
+ if (!item || typeof item !== 'object')
79
+ return [];
319
80
  const row = item;
320
- index += 1;
321
- const who = senderLabel(row.sender ?? row.user);
322
- const time = typeof row.time === "number"
323
- ? new Date(row.time * (row.time > 1e12 ? 1 : 1000)).toISOString()
324
- : "";
325
- const text = messageBodyToText(row.message ??
326
- row.content ??
327
- row.raw_message ??
328
- row.elements ??
329
- row);
330
- const head = time ? `${index}. ${who} @ ${time}` : `${index}. ${who}`;
331
- lines.push(text ? `${head}\n${text}` : `${head}\n(非文本或无法解析的内容)`);
332
- }
333
- return lines.join("\n\n");
334
- }
335
- export async function fetchForwardMsgText(client, id) {
336
- try {
337
- const messages = await client.getForwardMsg(id);
338
- const text = formatForwardMsgResponse(messages);
339
- if (text.trim())
340
- return text.trim();
341
- }
342
- catch { /* ignore */ }
343
- return "";
344
- }
345
- function appendForwardBlock(payload, block) {
346
- if (Array.isArray(payload.content)) {
347
- return {
348
- ...payload,
349
- content: [...payload.content, { type: "text", data: { text: block } }],
350
- };
351
- }
352
- const prev = typeof payload.content === "string" ? payload.content.trim() : "";
353
- return {
354
- ...payload,
355
- content: prev ? `${prev}\n\n${block}` : block,
356
- };
357
- }
358
- export async function enrichQuotedPayloadWithForward(client, payload, rawData) {
359
- if (!client || hasMergedForwardBlock(payload))
360
- return payload;
361
- const resid = extractForwardResidFromPayload(payload) ??
362
- (rawData ? extractForwardResidFromGetMsg(rawData) : undefined) ??
363
- (rawData ? extractForwardResidDeep(rawData) : undefined);
364
- const fetchIds = [
365
- resid,
366
- isForwardPlaceholderPayload(payload) ? payload.messageId : undefined,
367
- ].filter((v, i, a) => !!v && a.indexOf(v) === i);
368
- for (const fetchId of fetchIds) {
369
- const forwardText = await fetchForwardMsgText(client, fetchId);
370
- if (!forwardText.trim())
371
- continue;
372
- const block = `[Merged chat history — id ${fetchId}]\n${forwardText.trim()}`;
373
- return appendForwardBlock(payload, block);
374
- }
375
- return payload;
81
+ const sender = row.sender && typeof row.sender === 'object'
82
+ ? row.sender
83
+ : row.user && typeof row.user === 'object'
84
+ ? row.user
85
+ : undefined;
86
+ const id = sender?.user_id ?? sender?.uin ?? sender?.id;
87
+ const displayName = sender?.nickname ?? sender?.card ?? sender?.name;
88
+ const body = row.message ?? row.content ?? row.elements ?? row.raw_message;
89
+ let segments;
90
+ if (Array.isArray(body))
91
+ segments = toCanonicalSegments(icqqElementsToSegments(body) ?? body);
92
+ else if (typeof body === 'string' && body.trim())
93
+ segments = [{ type: 'text', data: { text: body.trim() } }];
94
+ else
95
+ segments = [];
96
+ if (segments.length === 0)
97
+ return [];
98
+ return [Object.freeze({
99
+ ...(id != null ? { actor: Object.freeze({
100
+ id: String(id),
101
+ ...(displayName != null && String(displayName).trim() ? { displayName: String(displayName) } : {}),
102
+ }) } : {}),
103
+ ...(typeof row.time === 'number' ? { timestamp: row.time < 1e12 ? row.time * 1000 : row.time } : {}),
104
+ segments: Object.freeze(segments),
105
+ })];
106
+ }));
376
107
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * icqq 入站事件归一化(post_type / message_type / message 段)
3
3
  */
4
- import { type MessageSegment, type QuotedMessagePayload } from "zhin.js";
4
+ import { type MessageSegment } from "zhin.js";
5
5
  import type { GroupRole } from "./types.js";
6
6
  /** 守护进程推送的通用事件壳 */
7
7
  export interface IcqqEventBase {
@@ -60,6 +60,16 @@ export interface IcqqMessageSource {
60
60
  };
61
61
  [key: string]: unknown;
62
62
  }
63
+ interface IcqqMessageLookup {
64
+ readonly messageId: string;
65
+ readonly sender?: Readonly<{
66
+ id?: string;
67
+ name?: string;
68
+ }>;
69
+ readonly content: readonly MessageSegment[] | string;
70
+ readonly raw?: string;
71
+ readonly time?: number;
72
+ }
63
73
  export type IcqqMessageElement = {
64
74
  type: string;
65
75
  text?: string;
@@ -68,8 +78,23 @@ export type IcqqMessageElement = {
68
78
  id?: string | number;
69
79
  url?: string;
70
80
  file?: string;
81
+ fid?: string;
82
+ name?: string;
83
+ size?: number;
84
+ seconds?: number;
71
85
  [key: string]: unknown;
72
86
  };
87
+ export interface IcqqInboundMediaResolver {
88
+ getPttUrl(element: IcqqMessageElement): Promise<string | null | undefined>;
89
+ getVideoUrl(element: IcqqMessageElement): Promise<string | null | undefined>;
90
+ }
91
+ /**
92
+ * Resolve ICQQ's opaque protobuf media references while the endpoint-owned
93
+ * native client is still available. Resolution failure preserves the opaque
94
+ * reference, so canonical ingress stays truthful and can report unsupported
95
+ * media instead of inventing a URL.
96
+ */
97
+ export declare function resolveIcqqInboundMedia(elements: IcqqMessageElement[] | undefined, resolver: IcqqInboundMediaResolver): Promise<void>;
73
98
  /** 归一化后供适配器使用的入站消息 */
74
99
  export interface NormalizedIcqqInbound {
75
100
  messageId: string;
@@ -125,8 +150,8 @@ export declare function icqqElementsToSegments(elements: IcqqMessageElement[] |
125
150
  export declare function findIcqqNestedMessageSource(root: unknown, maxDepth?: number): IcqqMessageSource | undefined;
126
151
  /** 从 oicq MessageEvent.source 解析被引用消息的 message_id */
127
152
  export declare function resolveQuoteIdFromIcqqSource(source: unknown): string | undefined;
128
- /** 将 source 转为 QuotedMessagePayload(供 $getMsg 缓存,避免重复拉取) */
129
- export declare function quotedPayloadFromIcqqSource(source: unknown): QuotedMessagePayload | null;
153
+ /** Convert an ICQQ source into the adapter-local lookup payload. */
154
+ export declare function messageLookupFromIcqqSource(source: unknown): IcqqMessageLookup | null;
130
155
  /** 从事件顶层字段解析引用 message_id(与 message 段互补) */
131
156
  export declare function resolveIcqqQuoteIdFromEvent(data: IcqqMessageEvent): string | undefined;
132
157
  export declare function resolveInboundContent(data: IcqqMessageEvent): MessageSegment[];
@@ -137,3 +162,4 @@ export declare class InboundMessageDeduper {
137
162
  shouldProcess(messageId: string): boolean;
138
163
  clear(): void;
139
164
  }
165
+ export {};
@@ -1,9 +1,37 @@
1
1
  /**
2
2
  * icqq 入站事件归一化(post_type / message_type / message 段)
3
3
  */
4
- import { Message, toCanonicalSegments } from "zhin.js";
4
+ import { toCanonicalSegments } from "zhin.js";
5
5
  import { parseCqMessage, icqqMediaRefFromString } from "./cq-message.js";
6
6
  import { extractForwardResidFromJsonElement } from "./forward-msg.js";
7
+ /**
8
+ * Resolve ICQQ's opaque protobuf media references while the endpoint-owned
9
+ * native client is still available. Resolution failure preserves the opaque
10
+ * reference, so canonical ingress stays truthful and can report unsupported
11
+ * media instead of inventing a URL.
12
+ */
13
+ export async function resolveIcqqInboundMedia(elements, resolver) {
14
+ if (!elements?.length)
15
+ return;
16
+ await Promise.all(elements.map(async (element) => {
17
+ if (!element || typeof element !== "object")
18
+ return;
19
+ if (typeof element.url === "string" && element.url)
20
+ return;
21
+ try {
22
+ const url = element.type === "record" || element.type === "audio"
23
+ ? await resolver.getPttUrl(element)
24
+ : element.type === "video"
25
+ ? await resolver.getVideoUrl(element)
26
+ : undefined;
27
+ if (typeof url === "string" && url)
28
+ element.url = url;
29
+ }
30
+ catch {
31
+ // The original platform reference remains the canonical fallback.
32
+ }
33
+ }));
34
+ }
7
35
  const DEDUPE_TTL_MS = 120_000;
8
36
  const ICQQ_GROUP_ROLES = new Set(["owner", "admin", "member"]);
9
37
  /** 从 sender.role 解析群成员角色(供 Message.$sender → resolveSenderRoles) */
@@ -212,9 +240,21 @@ export function icqqElementsToSegments(elements) {
212
240
  break;
213
241
  case "image": {
214
242
  const value = String(el.url ?? el.file ?? "");
243
+ if (!value)
244
+ break;
245
+ out.push({
246
+ type: "image",
247
+ data: { media: icqqMediaRefFromElement(value, el) },
248
+ });
249
+ break;
250
+ }
251
+ case "flash": {
252
+ const value = String(el.url ?? el.file ?? "");
253
+ if (!value)
254
+ break;
215
255
  out.push({
216
256
  type: "image",
217
- data: { media: icqqMediaRefFromString(value) },
257
+ data: { media: icqqMediaRefFromElement(value, el), flash: true },
218
258
  });
219
259
  break;
220
260
  }
@@ -281,18 +321,42 @@ export function icqqElementsToSegments(elements) {
281
321
  }
282
322
  case "record":
283
323
  case "audio": {
284
- const value = String(el.file ?? el.url ?? "");
324
+ const value = String(el.url ?? el.file ?? "");
325
+ if (!value)
326
+ break;
285
327
  out.push({
286
- type: "record",
287
- data: { media: icqqMediaRefFromString(value) },
328
+ type: "audio",
329
+ data: {
330
+ media: icqqMediaRefFromElement(value, el),
331
+ ...(typeof el.seconds === "number" ? { duration: el.seconds } : {}),
332
+ },
288
333
  });
289
334
  break;
290
335
  }
291
336
  case "video": {
292
- const value = String(el.file ?? el.url ?? "");
337
+ const value = String(el.url ?? el.file ?? "");
338
+ if (!value)
339
+ break;
293
340
  out.push({
294
341
  type: "video",
295
- data: { media: icqqMediaRefFromString(value) },
342
+ data: {
343
+ media: icqqMediaRefFromElement(value, el),
344
+ ...(typeof el.seconds === "number" ? { duration: el.seconds } : {}),
345
+ },
346
+ });
347
+ break;
348
+ }
349
+ case "file": {
350
+ const value = String(el.url ?? el.file ?? el.fid ?? "");
351
+ if (!value)
352
+ break;
353
+ const name = typeof el.name === "string" && el.name ? el.name : undefined;
354
+ out.push({
355
+ type: "file",
356
+ data: {
357
+ media: icqqMediaRefFromElement(value, el),
358
+ ...(name ? { name } : {}),
359
+ },
296
360
  });
297
361
  break;
298
362
  }
@@ -305,6 +369,20 @@ export function icqqElementsToSegments(elements) {
305
369
  }
306
370
  return out.length ? out : null;
307
371
  }
372
+ function icqqMediaRefFromElement(value, element) {
373
+ const media = icqqMediaRefFromString(value);
374
+ const fileName = typeof element.name === "string" && element.name
375
+ ? element.name
376
+ : undefined;
377
+ const size = typeof element.size === "number" && Number.isFinite(element.size)
378
+ ? element.size
379
+ : undefined;
380
+ return {
381
+ ...media,
382
+ ...(fileName ? { file_name: fileName } : {}),
383
+ ...(size != null ? { size } : {}),
384
+ };
385
+ }
308
386
  function resolveReplyIdFromJsonElement(el) {
309
387
  let payload = el.data ?? el;
310
388
  if (typeof payload === "string") {
@@ -422,8 +500,8 @@ function icqqSourceToContentSegments(s) {
422
500
  }
423
501
  return [];
424
502
  }
425
- /** 将 source 转为 QuotedMessagePayload(供 $getMsg 缓存,避免重复拉取) */
426
- export function quotedPayloadFromIcqqSource(source) {
503
+ /** Convert an ICQQ source into the adapter-local lookup payload. */
504
+ export function messageLookupFromIcqqSource(source) {
427
505
  if (!source || typeof source !== "object")
428
506
  return null;
429
507
  const s = source;
@@ -481,10 +559,10 @@ export function resolveIcqqQuoteIdFromEvent(data) {
481
559
  return undefined;
482
560
  }
483
561
  function mergeReplyFromRawMessage(content, rawMessage) {
484
- if (!rawMessage || Message.quoteIdFromContent(content))
562
+ if (!rawMessage || quoteIdFromSegments(content))
485
563
  return content;
486
564
  const fromRaw = parseCqMessage(rawMessage);
487
- const quoteFromRaw = Message.quoteIdFromContent(fromRaw);
565
+ const quoteFromRaw = quoteIdFromSegments(fromRaw);
488
566
  if (!quoteFromRaw)
489
567
  return content;
490
568
  return [
@@ -493,7 +571,7 @@ function mergeReplyFromRawMessage(content, rawMessage) {
493
571
  ];
494
572
  }
495
573
  function mergeReplyFromSource(content, data) {
496
- if (Message.quoteIdFromContent(content))
574
+ if (quoteIdFromSegments(content))
497
575
  return content;
498
576
  const quoteId = resolveQuoteIdFromIcqqSource(data.source) ??
499
577
  resolveQuoteIdFromIcqqSource(findIcqqNestedMessageSource(data));
@@ -504,6 +582,11 @@ function mergeReplyFromSource(content, data) {
504
582
  ...content.filter((s) => s.type !== "reply"),
505
583
  ];
506
584
  }
585
+ function quoteIdFromSegments(content) {
586
+ const reply = content.find((item) => item.type === 'reply');
587
+ const value = reply?.data?.message_id ?? reply?.data?.id;
588
+ return value == null || !String(value).trim() ? undefined : String(value).trim();
589
+ }
507
590
  export function resolveInboundContent(data) {
508
591
  const fromElements = icqqElementsToSegments(data.message);
509
592
  const raw = data.raw_message ?? "";
@@ -0,0 +1,27 @@
1
+ import type { LoginAssist, LoginAssistType } from '@zhin.js/core';
2
+ type IcqqLoginClient = {
3
+ login(password?: string): Promise<unknown>;
4
+ submitSlider(ticket: string): Promise<unknown>;
5
+ sendSmsCode(): Promise<unknown>;
6
+ submitSmsCode(code: string): Promise<unknown>;
7
+ };
8
+ type Logger = {
9
+ info(message: string): void;
10
+ warn(message: string): void;
11
+ };
12
+ /**
13
+ * Drive ICQQ login challenges through LoginAssist (refresh-safe pending tasks).
14
+ * Mirrors the classic stdin flow: qrcode/auth → login(); slider → submitSlider;
15
+ * device → sendSmsCode then submitSmsCode.
16
+ */
17
+ export declare function runIcqqLoginAssistStep(input: {
18
+ assist: LoginAssist;
19
+ client: IcqqLoginClient;
20
+ adapter: string;
21
+ endpointKey: string;
22
+ owner: object;
23
+ logger: Logger;
24
+ step: LoginAssistType;
25
+ event: unknown;
26
+ }): Promise<void>;
27
+ export {};