@elizaos/plugin-wechat 2.0.0-alpha.537 → 2.0.0-beta.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/auto-enable.ts +21 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +259 -12
- package/package.json +16 -7
- package/src/channel.ts +13 -0
- package/src/connector-account-provider.ts +180 -0
- package/src/index.test.ts +89 -0
- package/src/index.ts +332 -2
- package/dist/bot.d.ts +0 -25
- package/dist/bot.js +0 -49
- package/dist/callback-server.js +0 -207
- package/dist/channel.d.ts +0 -28
- package/dist/channel.js +0 -194
- package/dist/proxy-client.d.ts +0 -35
- package/dist/proxy-client.js +0 -117
- package/dist/reply-dispatcher.d.ts +0 -17
- package/dist/reply-dispatcher.js +0 -47
- package/dist/runtime-bridge.d.ts +0 -12
- package/dist/runtime-bridge.js +0 -159
- package/dist/types.d.ts +0 -61
- package/dist/utils/qrcode.js +0 -20
- package/src/callback-server.test.ts +0 -190
- package/src/channel.test.ts +0 -121
- package/src/proxy-client-429.test.ts +0 -24
- package/src/proxy-client.test.ts +0 -46
- package/src/runtime-bridge.test.ts +0 -135
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Bot } from "./bot";
|
|
3
|
+
import { normalizePayload } from "./callback-server";
|
|
4
|
+
import type { ProxyClient } from "./proxy-client";
|
|
5
|
+
import { ReplyDispatcher } from "./reply-dispatcher";
|
|
6
|
+
import type { WechatMessageContext } from "./types";
|
|
7
|
+
|
|
8
|
+
describe("@elizaos/plugin-wechat", () => {
|
|
9
|
+
it("normalizes supported direct and group webhook payloads", () => {
|
|
10
|
+
expect(
|
|
11
|
+
normalizePayload({
|
|
12
|
+
data: {
|
|
13
|
+
type: 60001,
|
|
14
|
+
sender: "wxid_alice",
|
|
15
|
+
recipient: "wxid_bot",
|
|
16
|
+
content: "hello",
|
|
17
|
+
timestamp: 1_700_000_000,
|
|
18
|
+
msgId: "direct-1",
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
).toEqual(
|
|
22
|
+
expect.objectContaining({
|
|
23
|
+
id: "direct-1",
|
|
24
|
+
type: "text",
|
|
25
|
+
sender: "wxid_alice",
|
|
26
|
+
recipient: "wxid_bot",
|
|
27
|
+
content: "hello",
|
|
28
|
+
timestamp: 1_700_000_000,
|
|
29
|
+
threadId: undefined,
|
|
30
|
+
group: undefined,
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
normalizePayload({
|
|
36
|
+
data: {
|
|
37
|
+
type: 80002,
|
|
38
|
+
sender: "12345@chatroom",
|
|
39
|
+
recipient: "wxid_bot",
|
|
40
|
+
imageUrl: "https://example.com/image.jpg",
|
|
41
|
+
roomName: "Team Chat",
|
|
42
|
+
timestamp: 1_700_000_001,
|
|
43
|
+
msgId: "group-1",
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
).toEqual(
|
|
47
|
+
expect.objectContaining({
|
|
48
|
+
id: "group-1",
|
|
49
|
+
type: "image",
|
|
50
|
+
threadId: "12345@chatroom",
|
|
51
|
+
group: { subject: "Team Chat" },
|
|
52
|
+
imageUrl: "https://example.com/image.jpg",
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("deduplicates inbound messages before dispatching to runtime", () => {
|
|
58
|
+
const onMessage = vi.fn();
|
|
59
|
+
const bot = new Bot({ onMessage });
|
|
60
|
+
const message: WechatMessageContext = {
|
|
61
|
+
id: "msg-1",
|
|
62
|
+
type: "text",
|
|
63
|
+
sender: "wxid_alice",
|
|
64
|
+
recipient: "wxid_bot",
|
|
65
|
+
content: "hello",
|
|
66
|
+
timestamp: 1_700_000_000,
|
|
67
|
+
raw: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
bot.handleIncoming(message);
|
|
71
|
+
bot.handleIncoming(message);
|
|
72
|
+
bot.stop();
|
|
73
|
+
|
|
74
|
+
expect(onMessage).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(onMessage).toHaveBeenCalledWith(message);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("chunks long outgoing text through the proxy client", async () => {
|
|
79
|
+
const client = {
|
|
80
|
+
sendText: vi.fn(async () => undefined),
|
|
81
|
+
} as ProxyClient;
|
|
82
|
+
const dispatcher = new ReplyDispatcher({ client, chunkSize: 5 });
|
|
83
|
+
|
|
84
|
+
await dispatcher.sendText("wxid_alice", "hello world");
|
|
85
|
+
|
|
86
|
+
expect(client.sendText).toHaveBeenNthCalledWith(1, "wxid_alice", "hello");
|
|
87
|
+
expect(client.sendText).toHaveBeenNthCalledWith(2, "wxid_alice", "world");
|
|
88
|
+
});
|
|
89
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConnectorAccountManager,
|
|
3
|
+
stringToUuid,
|
|
4
|
+
type Content,
|
|
5
|
+
type IAgentRuntime,
|
|
6
|
+
type Memory,
|
|
7
|
+
type MessageConnectorTarget,
|
|
8
|
+
type TargetInfo,
|
|
9
|
+
type UUID,
|
|
10
|
+
} from "@elizaos/core";
|
|
1
11
|
import { WechatChannel } from "./channel";
|
|
12
|
+
import { createWechatConnectorAccountProvider } from "./connector-account-provider";
|
|
2
13
|
import { deliverIncomingWechatMessage } from "./runtime-bridge";
|
|
3
14
|
import type { WechatConfig, WechatMessageContext } from "./types";
|
|
4
15
|
|
|
@@ -37,17 +48,335 @@ export interface Plugin {
|
|
|
37
48
|
config: Record<string, unknown>,
|
|
38
49
|
runtime: unknown,
|
|
39
50
|
) => Promise<void | (() => Promise<void>)>;
|
|
51
|
+
/**
|
|
52
|
+
* Declarative auto-enable conditions consumed by the runtime's
|
|
53
|
+
* plugin-auto-enable engine. Mirrors the shape on `@elizaos/core` Plugin.
|
|
54
|
+
*/
|
|
55
|
+
autoEnable?: {
|
|
56
|
+
envKeys?: string[];
|
|
57
|
+
connectorKeys?: string[];
|
|
58
|
+
shouldEnable?: (
|
|
59
|
+
env: Record<string, string | undefined>,
|
|
60
|
+
config: Record<string, unknown>,
|
|
61
|
+
) => boolean;
|
|
62
|
+
};
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
let channel: WechatChannel | null = null;
|
|
43
66
|
|
|
67
|
+
type RuntimeWithWechatConnector = {
|
|
68
|
+
registerMessageConnector?: (registration: Record<string, unknown>) => void;
|
|
69
|
+
getMessageConnectors?: () => Array<{
|
|
70
|
+
source?: string;
|
|
71
|
+
fetchMessages?: (
|
|
72
|
+
context: { runtime: IAgentRuntime; target?: TargetInfo },
|
|
73
|
+
params?: WechatConnectorReadParams,
|
|
74
|
+
) => Promise<Memory[]>;
|
|
75
|
+
}>;
|
|
76
|
+
registerSendHandler?: (
|
|
77
|
+
source: string,
|
|
78
|
+
handler: (
|
|
79
|
+
runtime: IAgentRuntime,
|
|
80
|
+
target: TargetInfo,
|
|
81
|
+
content: Content,
|
|
82
|
+
) => Promise<void>,
|
|
83
|
+
) => void;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type WechatConnectorReadParams = {
|
|
87
|
+
target?: TargetInfo;
|
|
88
|
+
limit?: number;
|
|
89
|
+
query?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function readRuntimeSetting(runtime: unknown, key: string): string | undefined {
|
|
93
|
+
const value = (runtime as { getSetting?: (setting: string) => unknown })
|
|
94
|
+
.getSetting?.(key);
|
|
95
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveWechatConfig(
|
|
99
|
+
config: Record<string, unknown>,
|
|
100
|
+
runtime: unknown,
|
|
101
|
+
): WechatConfig | undefined {
|
|
102
|
+
const explicit = (config as { connectors?: { wechat?: WechatConfig } })
|
|
103
|
+
?.connectors?.wechat;
|
|
104
|
+
if (explicit) return explicit;
|
|
105
|
+
const apiKey = readRuntimeSetting(runtime, "WECHAT_API_KEY");
|
|
106
|
+
const proxyUrl = readRuntimeSetting(runtime, "WECHAT_PROXY_URL");
|
|
107
|
+
if (!apiKey && !proxyUrl) return undefined;
|
|
108
|
+
return {
|
|
109
|
+
apiKey,
|
|
110
|
+
proxyUrl,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeConnectorLimit(
|
|
115
|
+
limit: number | undefined,
|
|
116
|
+
fallback = 50,
|
|
117
|
+
): number {
|
|
118
|
+
if (!Number.isFinite(limit) || !limit || limit <= 0) {
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
return Math.min(Math.floor(limit), 200);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getConfiguredAccountIds(config: WechatConfig): string[] {
|
|
125
|
+
if (config.accounts && typeof config.accounts === "object") {
|
|
126
|
+
return Object.entries(config.accounts)
|
|
127
|
+
.filter(
|
|
128
|
+
([, account]) => account.enabled !== false && Boolean(account.apiKey),
|
|
129
|
+
)
|
|
130
|
+
.map(([id]) => id);
|
|
131
|
+
}
|
|
132
|
+
return config.apiKey ? ["default"] : [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveWechatAccountId(
|
|
136
|
+
config: WechatConfig,
|
|
137
|
+
target?: TargetInfo,
|
|
138
|
+
): string {
|
|
139
|
+
const metadata = (
|
|
140
|
+
target as (TargetInfo & { metadata?: Record<string, unknown> }) | undefined
|
|
141
|
+
)?.metadata;
|
|
142
|
+
const accountId =
|
|
143
|
+
typeof metadata?.accountId === "string" && metadata.accountId.trim()
|
|
144
|
+
? metadata.accountId.trim()
|
|
145
|
+
: undefined;
|
|
146
|
+
if (accountId) {
|
|
147
|
+
return accountId;
|
|
148
|
+
}
|
|
149
|
+
return (
|
|
150
|
+
channel?.getAccountIds()[0] ??
|
|
151
|
+
getConfiguredAccountIds(config)[0] ??
|
|
152
|
+
"default"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function wechatTarget(
|
|
157
|
+
accountId: string,
|
|
158
|
+
wxid: string,
|
|
159
|
+
name: string | undefined,
|
|
160
|
+
kind: "user" | "group",
|
|
161
|
+
score = 0.55,
|
|
162
|
+
): MessageConnectorTarget {
|
|
163
|
+
return {
|
|
164
|
+
target: {
|
|
165
|
+
source: "wechat",
|
|
166
|
+
channelId: wxid,
|
|
167
|
+
roomId: stringToUuid(`wechat:room:${accountId}:${wxid}`) as UUID,
|
|
168
|
+
metadata: { accountId },
|
|
169
|
+
} as TargetInfo,
|
|
170
|
+
label: name || wxid,
|
|
171
|
+
kind,
|
|
172
|
+
score,
|
|
173
|
+
contexts: ["social", "connectors"],
|
|
174
|
+
metadata: { accountId, wxid },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function listWechatTargets(
|
|
179
|
+
config: WechatConfig,
|
|
180
|
+
): Promise<MessageConnectorTarget[]> {
|
|
181
|
+
if (!channel) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const targets: MessageConnectorTarget[] = [];
|
|
185
|
+
for (const accountId of channel.getAccountIds()) {
|
|
186
|
+
const contacts = await channel.listContacts(accountId).catch(() => null);
|
|
187
|
+
if (!contacts) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
targets.push(
|
|
191
|
+
...contacts.friends.map((friend) =>
|
|
192
|
+
wechatTarget(accountId, friend.wxid, friend.name, "user"),
|
|
193
|
+
),
|
|
194
|
+
...contacts.chatrooms.map((chatroom) =>
|
|
195
|
+
wechatTarget(accountId, chatroom.wxid, chatroom.name, "group"),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (targets.length > 0) {
|
|
200
|
+
return targets;
|
|
201
|
+
}
|
|
202
|
+
return getConfiguredAccountIds(config).map((accountId) =>
|
|
203
|
+
wechatTarget(
|
|
204
|
+
accountId,
|
|
205
|
+
accountId,
|
|
206
|
+
`WeChat account ${accountId}`,
|
|
207
|
+
"user",
|
|
208
|
+
0.25,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function filterMemoriesByQuery(
|
|
214
|
+
memories: Memory[],
|
|
215
|
+
query: string,
|
|
216
|
+
limit: number,
|
|
217
|
+
): Memory[] {
|
|
218
|
+
const normalized = query.trim().toLowerCase();
|
|
219
|
+
if (!normalized) {
|
|
220
|
+
return memories.slice(0, limit);
|
|
221
|
+
}
|
|
222
|
+
return memories
|
|
223
|
+
.filter((memory) => {
|
|
224
|
+
const text =
|
|
225
|
+
typeof memory.content?.text === "string" ? memory.content.text : "";
|
|
226
|
+
return text.toLowerCase().includes(normalized);
|
|
227
|
+
})
|
|
228
|
+
.slice(0, limit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function registerWechatMessageConnector(
|
|
232
|
+
runtime: unknown,
|
|
233
|
+
config: WechatConfig,
|
|
234
|
+
): void {
|
|
235
|
+
const connectorRuntime = runtime as RuntimeWithWechatConnector;
|
|
236
|
+
const sendHandler = async (
|
|
237
|
+
_runtime: IAgentRuntime,
|
|
238
|
+
target: TargetInfo,
|
|
239
|
+
content: Content,
|
|
240
|
+
): Promise<void> => {
|
|
241
|
+
if (!channel) {
|
|
242
|
+
throw new Error("[wechat] Channel is not available");
|
|
243
|
+
}
|
|
244
|
+
const text = typeof content.text === "string" ? content.text.trim() : "";
|
|
245
|
+
if (!text) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const accountId = resolveWechatAccountId(config, target);
|
|
249
|
+
const to = String(target.channelId ?? target.entityId ?? "").trim();
|
|
250
|
+
if (!to) {
|
|
251
|
+
throw new Error("[wechat] target is missing channelId/entityId");
|
|
252
|
+
}
|
|
253
|
+
await channel.sendText(accountId, to, text);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
if (typeof connectorRuntime.registerMessageConnector === "function") {
|
|
257
|
+
connectorRuntime.registerMessageConnector({
|
|
258
|
+
source: "wechat",
|
|
259
|
+
label: "WeChat",
|
|
260
|
+
description:
|
|
261
|
+
"WeChat connector for sending and reading stored DM/group messages.",
|
|
262
|
+
capabilities: [
|
|
263
|
+
"send_message",
|
|
264
|
+
"resolve_targets",
|
|
265
|
+
"list_rooms",
|
|
266
|
+
"chat_context",
|
|
267
|
+
],
|
|
268
|
+
supportedTargetKinds: ["user", "group", "room"],
|
|
269
|
+
contexts: ["social", "connectors"],
|
|
270
|
+
resolveTargets: async (query: string) => {
|
|
271
|
+
const normalized = query.trim().toLowerCase();
|
|
272
|
+
return (await listWechatTargets(config))
|
|
273
|
+
.map((target) => {
|
|
274
|
+
const haystack =
|
|
275
|
+
`${target.label ?? ""} ${target.target.channelId ?? ""}`.toLowerCase();
|
|
276
|
+
return {
|
|
277
|
+
...target,
|
|
278
|
+
score:
|
|
279
|
+
normalized && haystack.includes(normalized)
|
|
280
|
+
? 0.8
|
|
281
|
+
: (target.score ?? 0.4),
|
|
282
|
+
};
|
|
283
|
+
})
|
|
284
|
+
.filter((target) => !normalized || (target.score ?? 0) >= 0.8)
|
|
285
|
+
.slice(0, 25);
|
|
286
|
+
},
|
|
287
|
+
listRecentTargets: async () =>
|
|
288
|
+
(await listWechatTargets(config)).slice(0, 10),
|
|
289
|
+
listRooms: async () => listWechatTargets(config),
|
|
290
|
+
fetchMessages: async (
|
|
291
|
+
context: { runtime: IAgentRuntime; target?: TargetInfo },
|
|
292
|
+
params?: WechatConnectorReadParams,
|
|
293
|
+
) => {
|
|
294
|
+
const limit = normalizeConnectorLimit(params?.limit);
|
|
295
|
+
const target = params?.target ?? context.target;
|
|
296
|
+
if (target?.roomId) {
|
|
297
|
+
return context.runtime.getMemories({
|
|
298
|
+
tableName: "messages",
|
|
299
|
+
roomId: target.roomId,
|
|
300
|
+
limit,
|
|
301
|
+
orderBy: "createdAt",
|
|
302
|
+
orderDirection: "desc",
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const targets = (await listWechatTargets(config)).slice(0, 10);
|
|
306
|
+
const chunks = await Promise.all(
|
|
307
|
+
targets
|
|
308
|
+
.map((candidate) => candidate.target.roomId)
|
|
309
|
+
.filter((roomId): roomId is UUID => Boolean(roomId))
|
|
310
|
+
.map((roomId) =>
|
|
311
|
+
context.runtime.getMemories({
|
|
312
|
+
tableName: "messages",
|
|
313
|
+
roomId,
|
|
314
|
+
limit,
|
|
315
|
+
orderBy: "createdAt",
|
|
316
|
+
orderDirection: "desc",
|
|
317
|
+
}),
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
return chunks
|
|
321
|
+
.flat()
|
|
322
|
+
.sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0))
|
|
323
|
+
.slice(0, limit);
|
|
324
|
+
},
|
|
325
|
+
searchMessages: async (
|
|
326
|
+
context: { runtime: IAgentRuntime; target?: TargetInfo },
|
|
327
|
+
params: WechatConnectorReadParams & { query: string },
|
|
328
|
+
) => {
|
|
329
|
+
const limit = normalizeConnectorLimit(params.limit);
|
|
330
|
+
const registration = connectorRuntime
|
|
331
|
+
.getMessageConnectors?.()
|
|
332
|
+
.find((connector) => connector.source === "wechat") as
|
|
333
|
+
| {
|
|
334
|
+
fetchMessages?: (
|
|
335
|
+
context: { runtime: IAgentRuntime; target?: TargetInfo },
|
|
336
|
+
params?: WechatConnectorReadParams,
|
|
337
|
+
) => Promise<Memory[]>;
|
|
338
|
+
}
|
|
339
|
+
| undefined;
|
|
340
|
+
const messages =
|
|
341
|
+
(await registration?.fetchMessages?.(context, {
|
|
342
|
+
target: params.target ?? context.target,
|
|
343
|
+
limit: Math.max(limit, 100),
|
|
344
|
+
})) ?? [];
|
|
345
|
+
return filterMemoriesByQuery(messages, params.query, limit);
|
|
346
|
+
},
|
|
347
|
+
sendHandler,
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
connectorRuntime.registerSendHandler?.("wechat", sendHandler);
|
|
353
|
+
}
|
|
354
|
+
|
|
44
355
|
const wechatPlugin: Plugin = {
|
|
45
356
|
name: "wechat",
|
|
46
357
|
description: "WeChat messaging via proxy API",
|
|
47
358
|
|
|
359
|
+
// Self-declared auto-enable: activate when the "wechat" connector is
|
|
360
|
+
// configured under config.connectors. The hardcoded CONNECTOR_PLUGINS map
|
|
361
|
+
// in plugin-auto-enable-engine.ts still serves as a fallback.
|
|
362
|
+
autoEnable: {
|
|
363
|
+
connectorKeys: ["wechat"],
|
|
364
|
+
},
|
|
365
|
+
|
|
48
366
|
async init(config: Record<string, unknown>, runtime: unknown) {
|
|
49
|
-
|
|
50
|
-
|
|
367
|
+
try {
|
|
368
|
+
const manager = getConnectorAccountManager(runtime as IAgentRuntime);
|
|
369
|
+
manager.registerProvider(
|
|
370
|
+
createWechatConnectorAccountProvider(runtime as IAgentRuntime),
|
|
371
|
+
);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
console.warn(
|
|
374
|
+
"[wechat] Failed to register provider with ConnectorAccountManager:",
|
|
375
|
+
err instanceof Error ? err.message : String(err),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const wechatConfig = resolveWechatConfig(config, runtime);
|
|
51
380
|
|
|
52
381
|
if (!wechatConfig) {
|
|
53
382
|
console.warn("[wechat] No wechat config found in connectors — skipping");
|
|
@@ -77,6 +406,7 @@ const wechatPlugin: Plugin = {
|
|
|
77
406
|
});
|
|
78
407
|
|
|
79
408
|
await channel.start();
|
|
409
|
+
registerWechatMessageConnector(runtime, wechatConfig);
|
|
80
410
|
console.log("[wechat] Plugin initialized");
|
|
81
411
|
|
|
82
412
|
// Return cleanup function
|
package/dist/bot.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { WechatMessageContext } from "./types.js";
|
|
2
|
-
|
|
3
|
-
//#region src/bot.d.ts
|
|
4
|
-
interface BotOptions {
|
|
5
|
-
onMessage: (msg: WechatMessageContext) => void | Promise<void>;
|
|
6
|
-
featuresGroups?: boolean;
|
|
7
|
-
featuresImages?: boolean;
|
|
8
|
-
/** Deduplication window in milliseconds. Defaults to 30 minutes. */
|
|
9
|
-
dedupWindowMs?: number;
|
|
10
|
-
}
|
|
11
|
-
declare class Bot {
|
|
12
|
-
private readonly seen;
|
|
13
|
-
private readonly onMessage;
|
|
14
|
-
private readonly featuresGroups;
|
|
15
|
-
private readonly featuresImages;
|
|
16
|
-
private readonly dedupWindowMs;
|
|
17
|
-
private cleanupTimer;
|
|
18
|
-
constructor(options: BotOptions);
|
|
19
|
-
handleIncoming(message: WechatMessageContext): void;
|
|
20
|
-
private isDuplicate;
|
|
21
|
-
private cleanup;
|
|
22
|
-
stop(): void;
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
|
-
export { Bot };
|
package/dist/bot.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
//#region src/bot.ts
|
|
2
|
-
const DEFAULT_DEDUP_WINDOW_MS = 1800 * 1e3;
|
|
3
|
-
const DEDUP_MAX_ENTRIES = 1e3;
|
|
4
|
-
const DEDUP_CLEANUP_INTERVAL_MS = 300 * 1e3;
|
|
5
|
-
var Bot = class {
|
|
6
|
-
seen = /* @__PURE__ */ new Map();
|
|
7
|
-
onMessage;
|
|
8
|
-
featuresGroups;
|
|
9
|
-
featuresImages;
|
|
10
|
-
dedupWindowMs;
|
|
11
|
-
cleanupTimer = null;
|
|
12
|
-
constructor(options) {
|
|
13
|
-
this.onMessage = options.onMessage;
|
|
14
|
-
this.featuresGroups = options.featuresGroups ?? true;
|
|
15
|
-
this.featuresImages = options.featuresImages ?? true;
|
|
16
|
-
this.dedupWindowMs = options.dedupWindowMs ?? DEFAULT_DEDUP_WINDOW_MS;
|
|
17
|
-
this.cleanupTimer = setInterval(() => this.cleanup(), DEDUP_CLEANUP_INTERVAL_MS);
|
|
18
|
-
}
|
|
19
|
-
handleIncoming(message) {
|
|
20
|
-
if (this.isDuplicate(message.id)) return;
|
|
21
|
-
if (message.group && !this.featuresGroups) return;
|
|
22
|
-
if (message.type === "image" && !this.featuresImages) return;
|
|
23
|
-
if (message.type === "unknown") return;
|
|
24
|
-
Promise.resolve(this.onMessage(message)).catch((error) => {
|
|
25
|
-
console.error("[wechat] Failed to process inbound message:", error);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
isDuplicate(messageId) {
|
|
29
|
-
const now = Date.now();
|
|
30
|
-
if (this.seen.has(messageId)) return true;
|
|
31
|
-
if (this.seen.size >= DEDUP_MAX_ENTRIES) this.cleanup();
|
|
32
|
-
this.seen.set(messageId, now);
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
cleanup() {
|
|
36
|
-
const cutoff = Date.now() - this.dedupWindowMs;
|
|
37
|
-
for (const [id, ts] of this.seen) if (ts < cutoff) this.seen.delete(id);
|
|
38
|
-
}
|
|
39
|
-
stop() {
|
|
40
|
-
if (this.cleanupTimer) {
|
|
41
|
-
clearInterval(this.cleanupTimer);
|
|
42
|
-
this.cleanupTimer = null;
|
|
43
|
-
}
|
|
44
|
-
this.seen.clear();
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
export { Bot };
|