@chbo297/infoflow 2026.3.18 → 2026.5.4
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/README.md +24 -528
- package/dist/index.js +21 -0
- package/dist/src/accounts.js +110 -0
- package/dist/src/actions.js +386 -0
- package/dist/src/bot.js +1010 -0
- package/dist/src/channel.js +385 -0
- package/dist/src/infoflow-req-parse.js +394 -0
- package/dist/src/logging.js +102 -0
- package/dist/src/markdown-local-images.js +65 -0
- package/dist/src/media.js +318 -0
- package/dist/src/monitor.js +145 -0
- package/dist/src/reply-dispatcher.js +301 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/send.js +820 -0
- package/dist/src/sent-message-store.js +190 -0
- package/dist/src/targets.js +90 -0
- package/dist/src/types.js +4 -0
- package/dist/src/ws-receiver.js +378 -0
- package/openclaw.plugin.json +194 -0
- package/package.json +18 -3
- package/scripts/deploy.sh +215 -0
- package/src/accounts.ts +25 -3
- package/src/actions.ts +9 -3
- package/src/bot.ts +63 -20
- package/src/channel.ts +64 -45
- package/src/infoflow-req-parse.ts +2 -2
- package/src/infoflow-sdk.d.ts +12 -0
- package/src/monitor.ts +21 -2
- package/src/reply-dispatcher.ts +2 -5
- package/src/types.ts +11 -0
- package/src/ws-receiver.ts +482 -0
- package/tsconfig.build.json +6 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket inbound receiver: wraps SDK WSClient for long-lived message delivery.
|
|
3
|
+
* Normalizes events into the same msgData shape as the webhook path, then calls
|
|
4
|
+
* handleGroupChatMessage / handlePrivateChatMessage.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
8
|
+
import {
|
|
9
|
+
checkBotMentioned,
|
|
10
|
+
handleGroupChatMessage,
|
|
11
|
+
handlePrivateChatMessage,
|
|
12
|
+
} from "./bot.js";
|
|
13
|
+
import { formatInfoflowError, getInfoflowWebhookLog, logVerbose } from "./logging.js";
|
|
14
|
+
import { isDuplicateMessage } from "./infoflow-req-parse.js";
|
|
15
|
+
import { extractIdFromRawJson } from "./send.js";
|
|
16
|
+
import type { ResolvedInfoflowAccount } from "./types.js";
|
|
17
|
+
|
|
18
|
+
export type WSReceiverOptions = {
|
|
19
|
+
account: ResolvedInfoflowAccount;
|
|
20
|
+
config: OpenClawConfig;
|
|
21
|
+
abortSignal: AbortSignal;
|
|
22
|
+
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type GroupInboundSeenKind = "forward" | "mention";
|
|
26
|
+
|
|
27
|
+
const GROUP_INBOUND_TTL_MS = 5 * 60 * 1000;
|
|
28
|
+
const GROUP_INBOUND_MAX_SIZE = 2048;
|
|
29
|
+
const groupInboundSeen = new Map<string, { kind: GroupInboundSeenKind; seenAt: number }>();
|
|
30
|
+
|
|
31
|
+
function pruneGroupInboundSeen(now: number): void {
|
|
32
|
+
for (const [key, value] of groupInboundSeen) {
|
|
33
|
+
if (now - value.seenAt > GROUP_INBOUND_TTL_MS) {
|
|
34
|
+
groupInboundSeen.delete(key);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (groupInboundSeen.size <= GROUP_INBOUND_MAX_SIZE) return;
|
|
38
|
+
const overflow = groupInboundSeen.size - GROUP_INBOUND_MAX_SIZE;
|
|
39
|
+
const oldest = [...groupInboundSeen.entries()]
|
|
40
|
+
.sort((a, b) => a[1].seenAt - b[1].seenAt)
|
|
41
|
+
.slice(0, overflow);
|
|
42
|
+
for (const [key] of oldest) {
|
|
43
|
+
groupInboundSeen.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function shouldSkipDuplicateGroupEvent(
|
|
48
|
+
dedupKey: string,
|
|
49
|
+
incomingKind: GroupInboundSeenKind,
|
|
50
|
+
): boolean {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
pruneGroupInboundSeen(now);
|
|
53
|
+
const existing = groupInboundSeen.get(dedupKey);
|
|
54
|
+
if (!existing) {
|
|
55
|
+
groupInboundSeen.set(dedupKey, { kind: incomingKind, seenAt: now });
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (incomingKind === "forward") {
|
|
59
|
+
existing.seenAt = now;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (existing.kind === "mention") {
|
|
63
|
+
existing.seenAt = now;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
groupInboundSeen.set(dedupKey, { kind: "mention", seenAt: now });
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function botMentionIdentity(account: ResolvedInfoflowAccount) {
|
|
71
|
+
return {
|
|
72
|
+
robotName: account.config.robotName,
|
|
73
|
+
appAgentId: account.config.appAgentId,
|
|
74
|
+
robotId: account.config.robotId?.trim() || undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type WsClientInstance = {
|
|
79
|
+
on: (event: string, handler: (...args: unknown[]) => void | Promise<void>) => void;
|
|
80
|
+
off?: (event: string, handler: unknown) => void;
|
|
81
|
+
connect: () => Promise<void>;
|
|
82
|
+
disconnect: () => void;
|
|
83
|
+
eventDispatcher?: { normalizePrivateMessage?: (p: unknown) => unknown };
|
|
84
|
+
frameCodec?: { parsePayload?: (frame: unknown) => unknown };
|
|
85
|
+
handleDisconnect?: (...args: unknown[]) => void;
|
|
86
|
+
stopHeartbeat?: () => void;
|
|
87
|
+
state?: string;
|
|
88
|
+
serverConfig?: Record<string, unknown>;
|
|
89
|
+
maxReconnectAttempts?: number;
|
|
90
|
+
reconnectAttempts?: number;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export class InfoflowWSReceiver {
|
|
94
|
+
private wsClient: WsClientInstance | null = null;
|
|
95
|
+
private options: WSReceiverOptions;
|
|
96
|
+
private stopped = false;
|
|
97
|
+
private handleGroupEventRef: ((...args: unknown[]) => void | Promise<void>) | null = null;
|
|
98
|
+
private handlePrivateEventRef: ((...args: unknown[]) => void | Promise<void>) | null = null;
|
|
99
|
+
|
|
100
|
+
constructor(options: WSReceiverOptions) {
|
|
101
|
+
this.options = options;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async start(): Promise<void> {
|
|
105
|
+
let WSClient: new (opts: Record<string, unknown>) => WsClientInstance;
|
|
106
|
+
try {
|
|
107
|
+
({ WSClient } = await import("@baidu/infoflow-sdk-nodejs"));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Infoflow WebSocket mode requires @baidu/infoflow-sdk-nodejs (install from the Baidu npm registry). ${formatInfoflowError(err)}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { appKey, appSecret } = this.options.account.config;
|
|
115
|
+
const wsGateway = this.options.account.config.wsGateway;
|
|
116
|
+
const wsConnectDomain = this.options.account.config.wsConnectDomain;
|
|
117
|
+
|
|
118
|
+
this.wsClient = new WSClient({
|
|
119
|
+
appId: appKey,
|
|
120
|
+
appSecret: appSecret,
|
|
121
|
+
wsGateway,
|
|
122
|
+
...(wsConnectDomain ? { wsConnectDomain } : {}),
|
|
123
|
+
endpointTimeout: 15_000,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const client = this.wsClient;
|
|
127
|
+
const dispatcher = client.eventDispatcher;
|
|
128
|
+
if (dispatcher && typeof dispatcher.normalizePrivateMessage === "function") {
|
|
129
|
+
const original = dispatcher.normalizePrivateMessage.bind(dispatcher);
|
|
130
|
+
dispatcher.normalizePrivateMessage = (payload: unknown) => {
|
|
131
|
+
const normalized = original(payload) as Record<string, unknown>;
|
|
132
|
+
normalized.originalMessage = payload;
|
|
133
|
+
return normalized;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const frameCodec = client.frameCodec;
|
|
138
|
+
if (frameCodec && typeof frameCodec.parsePayload === "function") {
|
|
139
|
+
const originalParse = frameCodec.parsePayload.bind(frameCodec);
|
|
140
|
+
frameCodec.parsePayload = (frame: unknown) => {
|
|
141
|
+
const f = frame as { method?: string; payload?: Buffer };
|
|
142
|
+
const result = originalParse(frame) as Record<string, unknown> | null;
|
|
143
|
+
if (result && typeof result === "object" && f.payload) {
|
|
144
|
+
try {
|
|
145
|
+
result._rawJson = f.payload.toString("utf-8");
|
|
146
|
+
} catch {
|
|
147
|
+
/* ignore */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const method = f.method ?? "?";
|
|
151
|
+
getInfoflowWebhookLog().info(
|
|
152
|
+
`[ws:frame] method=${method}, payloadLen=${f.payload?.length ?? 0}`,
|
|
153
|
+
);
|
|
154
|
+
return result;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const rawHandleDisconnect = client.handleDisconnect;
|
|
159
|
+
if (typeof rawHandleDisconnect === "function") {
|
|
160
|
+
const originalHandleDisconnect = rawHandleDisconnect.bind(client);
|
|
161
|
+
client.handleDisconnect = (...args: unknown[]) => {
|
|
162
|
+
if (this.stopped) {
|
|
163
|
+
try {
|
|
164
|
+
client.stopHeartbeat?.();
|
|
165
|
+
} catch {
|
|
166
|
+
/* ignore */
|
|
167
|
+
}
|
|
168
|
+
client.state = "disconnected";
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
return originalHandleDisconnect(...args);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getInfoflowWebhookLog().info(
|
|
176
|
+
`[ws:init] WSClient created: appKey=${appKey.slice(0, 4)}***, gateway=${wsGateway}, endpointTimeout=15000ms`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const handleGroupEvent = async (...args: unknown[]) => {
|
|
180
|
+
const event = args[0] as { data?: unknown; type?: string };
|
|
181
|
+
getInfoflowWebhookLog().info(
|
|
182
|
+
`[ws:inbound] group event received, type=${event?.type ?? (event?.data as { msgType?: string })?.msgType ?? "?"}`,
|
|
183
|
+
);
|
|
184
|
+
if (this.stopped) {
|
|
185
|
+
getInfoflowWebhookLog().warn(`[ws:inbound] group event dropped (receiver stopped)`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const data = (event?.data ?? event) as Record<string, unknown>;
|
|
190
|
+
await this.handleGroupEvent(data);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
getInfoflowWebhookLog().error(`[ws:inbound] group handler error: ${formatInfoflowError(err)}`);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
this.wsClient.on("group.*", handleGroupEvent);
|
|
196
|
+
this.handleGroupEventRef = handleGroupEvent;
|
|
197
|
+
|
|
198
|
+
const handlePrivateEvent = async (...args: unknown[]) => {
|
|
199
|
+
const event = args[0] as { data?: unknown; type?: string };
|
|
200
|
+
getInfoflowWebhookLog().info(
|
|
201
|
+
`[ws:inbound] private event received, type=${event?.type ?? (event?.data as { msgType?: string })?.msgType ?? "?"}`,
|
|
202
|
+
);
|
|
203
|
+
if (this.stopped) {
|
|
204
|
+
getInfoflowWebhookLog().warn(`[ws:inbound] private event dropped (receiver stopped)`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const data = (event?.data ?? event) as Record<string, unknown>;
|
|
209
|
+
await this.handlePrivateEvent(data);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
getInfoflowWebhookLog().error(`[ws:inbound] private handler error: ${formatInfoflowError(err)}`);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
this.wsClient.on("private.*", handlePrivateEvent);
|
|
215
|
+
this.handlePrivateEventRef = handlePrivateEvent;
|
|
216
|
+
|
|
217
|
+
this.wsClient.on("connected" as never, (...args: unknown[]) => {
|
|
218
|
+
const event = args[0] as { connectionId?: string };
|
|
219
|
+
getInfoflowWebhookLog().info(
|
|
220
|
+
`[ws:connect] websocket connected, connection_id=${event?.connectionId ?? "?"}`,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
this.wsClient.on("disconnected" as never, (...args: unknown[]) => {
|
|
224
|
+
const event = args[0] as { connectionId?: string };
|
|
225
|
+
getInfoflowWebhookLog().warn(
|
|
226
|
+
`[ws:disconnect] websocket disconnected, connection_id=${event?.connectionId ?? "?"}`,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
this.wsClient.on("error" as never, (...args: unknown[]) => {
|
|
230
|
+
const event = args[0] as { error?: { message?: string } };
|
|
231
|
+
const msg = event?.error?.message ?? String(event?.error ?? event ?? "unknown");
|
|
232
|
+
getInfoflowWebhookLog().error(`[ws:error] websocket error: ${msg}`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
getInfoflowWebhookLog().info(`[ws:connect] connecting to ${wsGateway}`);
|
|
236
|
+
await this.wsClient.connect();
|
|
237
|
+
getInfoflowWebhookLog().info(`[ws:connect] initial connection established`);
|
|
238
|
+
|
|
239
|
+
this.options.abortSignal.addEventListener(
|
|
240
|
+
"abort",
|
|
241
|
+
() => {
|
|
242
|
+
this.stop();
|
|
243
|
+
},
|
|
244
|
+
{ once: true },
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
stop(): void {
|
|
249
|
+
if (this.stopped) return;
|
|
250
|
+
this.stopped = true;
|
|
251
|
+
const client = this.wsClient;
|
|
252
|
+
if (this.handleGroupEventRef && client?.off) {
|
|
253
|
+
try {
|
|
254
|
+
client.off("group.*", this.handleGroupEventRef);
|
|
255
|
+
} catch {
|
|
256
|
+
/* ignore */
|
|
257
|
+
}
|
|
258
|
+
this.handleGroupEventRef = null;
|
|
259
|
+
}
|
|
260
|
+
if (this.handlePrivateEventRef && client?.off) {
|
|
261
|
+
try {
|
|
262
|
+
client.off("private.*", this.handlePrivateEventRef);
|
|
263
|
+
} catch {
|
|
264
|
+
/* ignore */
|
|
265
|
+
}
|
|
266
|
+
this.handlePrivateEventRef = null;
|
|
267
|
+
}
|
|
268
|
+
if (!client) return;
|
|
269
|
+
|
|
270
|
+
getInfoflowWebhookLog().info(`[ws:disconnect] stopping ws receiver`);
|
|
271
|
+
try {
|
|
272
|
+
client.stopHeartbeat?.();
|
|
273
|
+
if (client.serverConfig && typeof client.serverConfig === "object") {
|
|
274
|
+
client.serverConfig = {
|
|
275
|
+
...client.serverConfig,
|
|
276
|
+
reconnect_count: 0,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
client.maxReconnectAttempts = 0;
|
|
280
|
+
client.reconnectAttempts = 0;
|
|
281
|
+
} catch {
|
|
282
|
+
/* ignore */
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
client.disconnect();
|
|
286
|
+
} catch {
|
|
287
|
+
/* ignore */
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async handleGroupEvent(data: Record<string, unknown>): Promise<void> {
|
|
292
|
+
if (!data) return;
|
|
293
|
+
|
|
294
|
+
this.options.statusSink?.({ lastInboundAt: Date.now() });
|
|
295
|
+
|
|
296
|
+
const payload = (data.raw ?? data) as Record<string, unknown>;
|
|
297
|
+
const originalMessage = (payload.originalMessage ?? payload) as Record<string, unknown>;
|
|
298
|
+
const message = (payload.message ?? originalMessage.message ?? {}) as Record<string, unknown>;
|
|
299
|
+
const header = (message.header ?? originalMessage.header ?? {}) as Record<string, unknown>;
|
|
300
|
+
|
|
301
|
+
const rawJson = payload._rawJson as string | undefined;
|
|
302
|
+
const preciseMessageId =
|
|
303
|
+
(rawJson && extractIdFromRawJson(rawJson, "messageid")) ??
|
|
304
|
+
(header.messageid != null ? String(header.messageid) : undefined);
|
|
305
|
+
const preciseClientMsgId =
|
|
306
|
+
(rawJson && extractIdFromRawJson(rawJson, "clientmsgid")) ??
|
|
307
|
+
(header.clientmsgid != null ? String(header.clientmsgid) : undefined);
|
|
308
|
+
|
|
309
|
+
const preciseMsgId2 =
|
|
310
|
+
(rawJson && extractIdFromRawJson(rawJson, "msgid2")) ??
|
|
311
|
+
(payload.msgid2 != null
|
|
312
|
+
? String(payload.msgid2)
|
|
313
|
+
: originalMessage.msgid2 != null
|
|
314
|
+
? String(originalMessage.msgid2)
|
|
315
|
+
: undefined);
|
|
316
|
+
|
|
317
|
+
const rawEventType: string = String(
|
|
318
|
+
payload.eventtype ??
|
|
319
|
+
payload.eventType ??
|
|
320
|
+
originalMessage.eventtype ??
|
|
321
|
+
originalMessage.eventType ??
|
|
322
|
+
"MESSAGE_RECEIVE",
|
|
323
|
+
);
|
|
324
|
+
const bodyItems = (message.body ?? payload.body ?? data.body ?? []) as Array<{
|
|
325
|
+
type?: string;
|
|
326
|
+
name?: string;
|
|
327
|
+
robotid?: number;
|
|
328
|
+
}>;
|
|
329
|
+
const isMentionEvent =
|
|
330
|
+
rawEventType === "MESSAGE_RECEIVE"
|
|
331
|
+
? true
|
|
332
|
+
: checkBotMentioned(bodyItems, botMentionIdentity(this.options.account));
|
|
333
|
+
|
|
334
|
+
getInfoflowWebhookLog().info(
|
|
335
|
+
`[ws:inbound] group eventtype=${rawEventType}, wasMentioned=${isMentionEvent}`,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const msgData: Record<string, unknown> = {
|
|
339
|
+
eventtype: rawEventType,
|
|
340
|
+
groupid: payload.groupid ?? payload.groupId ?? data.groupId,
|
|
341
|
+
fromid: payload.fromid,
|
|
342
|
+
msgid2: preciseMsgId2,
|
|
343
|
+
wasMentioned: isMentionEvent,
|
|
344
|
+
message: {
|
|
345
|
+
header: {
|
|
346
|
+
fromuserid: header.fromuserid ?? payload.fromUserId ?? data.fromUserId ?? "",
|
|
347
|
+
toid: payload.groupid ?? payload.groupId ?? data.groupId,
|
|
348
|
+
totype: "GROUP",
|
|
349
|
+
msgtype: header.msgtype ?? data.msgType ?? "text",
|
|
350
|
+
messageid: preciseMessageId,
|
|
351
|
+
clientmsgid: preciseClientMsgId,
|
|
352
|
+
servertime: header.servertime,
|
|
353
|
+
clienttime: header.clienttime,
|
|
354
|
+
at: header.at ?? { atrobotids: [] },
|
|
355
|
+
},
|
|
356
|
+
body:
|
|
357
|
+
bodyItems.length > 0
|
|
358
|
+
? bodyItems
|
|
359
|
+
: data.content != null && String(data.content).length > 0
|
|
360
|
+
? [{ type: "TEXT", content: String(data.content) }]
|
|
361
|
+
: [],
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const dedupKey = preciseClientMsgId ?? preciseMessageId;
|
|
366
|
+
const dedupKind: GroupInboundSeenKind = isMentionEvent ? "mention" : "forward";
|
|
367
|
+
if (dedupKey && shouldSkipDuplicateGroupEvent(dedupKey, dedupKind)) {
|
|
368
|
+
logVerbose(`[infoflow:ws] duplicate group message skipped: key=${dedupKey}, kind=${dedupKind}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
logVerbose(
|
|
372
|
+
`[infoflow:ws] group message: from=${header.fromuserid}, msgType=${header.msgtype}, groupId=${payload.groupid ?? payload.groupId}`,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
await handleGroupChatMessage({
|
|
376
|
+
cfg: this.options.config,
|
|
377
|
+
msgData,
|
|
378
|
+
accountId: this.options.account.accountId,
|
|
379
|
+
statusSink: this.options.statusSink,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private async handlePrivateEvent(data: Record<string, unknown>): Promise<void> {
|
|
384
|
+
if (!data) return;
|
|
385
|
+
|
|
386
|
+
this.options.statusSink?.({ lastInboundAt: Date.now() });
|
|
387
|
+
|
|
388
|
+
const payload = (data.raw ?? data) as Record<string, unknown>;
|
|
389
|
+
const originalMessage = (payload.originalMessage ?? payload) as Record<string, unknown>;
|
|
390
|
+
|
|
391
|
+
const rawJson = (payload._rawJson ?? originalMessage._rawJson) as string | undefined;
|
|
392
|
+
const preciseMsgId =
|
|
393
|
+
(rawJson &&
|
|
394
|
+
(extractIdFromRawJson(rawJson, "MsgId") ?? extractIdFromRawJson(rawJson, "msgId"))) ??
|
|
395
|
+
(() => {
|
|
396
|
+
const raw = payload.MsgId ?? payload.msgId ?? data.msgId ?? originalMessage.MsgId;
|
|
397
|
+
return raw != null ? String(raw) : undefined;
|
|
398
|
+
})();
|
|
399
|
+
|
|
400
|
+
const preciseMsgId2 =
|
|
401
|
+
(rawJson &&
|
|
402
|
+
(extractIdFromRawJson(rawJson, "MsgId2") ??
|
|
403
|
+
extractIdFromRawJson(rawJson, "msgid2") ??
|
|
404
|
+
extractIdFromRawJson(rawJson, "msgId2"))) ??
|
|
405
|
+
(() => {
|
|
406
|
+
const raw =
|
|
407
|
+
payload.MsgId2 ??
|
|
408
|
+
payload.msgId2 ??
|
|
409
|
+
payload.msgid2 ??
|
|
410
|
+
data.MsgId2 ??
|
|
411
|
+
data.msgid2 ??
|
|
412
|
+
originalMessage.MsgId2;
|
|
413
|
+
return raw != null ? String(raw) : undefined;
|
|
414
|
+
})();
|
|
415
|
+
|
|
416
|
+
const msgData: Record<string, unknown> = {
|
|
417
|
+
FromUserId:
|
|
418
|
+
payload.FromUserId ??
|
|
419
|
+
payload.fromUserId ??
|
|
420
|
+
data.fromUserId ??
|
|
421
|
+
originalMessage.FromUserId ??
|
|
422
|
+
"",
|
|
423
|
+
FromUserName:
|
|
424
|
+
payload.FromUserName ??
|
|
425
|
+
payload.fromUserName ??
|
|
426
|
+
data.fromUserName ??
|
|
427
|
+
originalMessage.FromUserName,
|
|
428
|
+
Content: payload.Content ?? payload.content ?? data.content ?? originalMessage.Content ?? "",
|
|
429
|
+
MsgType:
|
|
430
|
+
payload.MsgType ?? payload.msgType ?? data.msgType ?? originalMessage.MsgType ?? "text",
|
|
431
|
+
CreateTime:
|
|
432
|
+
payload.CreateTime ??
|
|
433
|
+
payload.createTime ??
|
|
434
|
+
data.createTime ??
|
|
435
|
+
originalMessage.CreateTime ??
|
|
436
|
+
String(Date.now()),
|
|
437
|
+
PicUrl: payload.PicUrl ?? payload.picUrl ?? data.picUrl ?? originalMessage.PicUrl ?? "",
|
|
438
|
+
VoiceUrl:
|
|
439
|
+
payload.VoiceUrl ?? payload.voiceUrl ?? data.voiceUrl ?? originalMessage.VoiceUrl ?? "",
|
|
440
|
+
FromPlatform:
|
|
441
|
+
payload.FromPlatform ??
|
|
442
|
+
payload.fromPlatform ??
|
|
443
|
+
data.fromPlatform ??
|
|
444
|
+
originalMessage.FromPlatform ??
|
|
445
|
+
"",
|
|
446
|
+
agentId: payload.agentId ?? data.agentId ?? originalMessage.agentId ?? "",
|
|
447
|
+
OpenCode:
|
|
448
|
+
payload.OpenCode ?? payload.openCode ?? data.openCode ?? originalMessage.OpenCode ?? "",
|
|
449
|
+
MsgId: preciseMsgId,
|
|
450
|
+
MsgId2: preciseMsgId2,
|
|
451
|
+
FromId:
|
|
452
|
+
payload.FromId ?? payload.fromid ?? data.FromId ?? data.fromid ?? originalMessage.FromId,
|
|
453
|
+
Reply:
|
|
454
|
+
payload.Reply ??
|
|
455
|
+
payload.reply ??
|
|
456
|
+
data.Reply ??
|
|
457
|
+
data.reply ??
|
|
458
|
+
originalMessage.Reply ??
|
|
459
|
+
undefined,
|
|
460
|
+
FileId: payload.FileId ?? payload.fileId ?? data.fileId ?? originalMessage.FileId,
|
|
461
|
+
Name: payload.Name ?? payload.name ?? data.name ?? originalMessage.Name,
|
|
462
|
+
FileSize: payload.FileSize ?? payload.fileSize ?? data.fileSize ?? originalMessage.FileSize,
|
|
463
|
+
FileType: payload.FileType ?? payload.fileType ?? data.fileType ?? originalMessage.FileType,
|
|
464
|
+
CardType: payload.CardType ?? payload.cardType ?? data.CardType ?? originalMessage.CardType,
|
|
465
|
+
Title: payload.Title ?? payload.title ?? data.Title ?? originalMessage.Title,
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
if (isDuplicateMessage(msgData)) {
|
|
469
|
+
logVerbose("[infoflow:ws] duplicate private message, skipping");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
logVerbose(`[infoflow:ws] private message: from=${msgData.FromUserId}, msgType=${msgData.MsgType}`);
|
|
474
|
+
|
|
475
|
+
await handlePrivateChatMessage({
|
|
476
|
+
cfg: this.options.config,
|
|
477
|
+
msgData,
|
|
478
|
+
accountId: this.options.account.accountId,
|
|
479
|
+
statusSink: this.options.statusSink,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|