@diegoaltoworks/chatter 0.47.0 → 0.48.0
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 +3 -4
- package/dist/auth/apikeys.d.ts.map +1 -1
- package/dist/auth/secretStrength.d.ts +8 -0
- package/dist/auth/secretStrength.d.ts.map +1 -0
- package/dist/bin/create-apikey.js +22 -3
- package/dist/bin/wa-pair.js +59 -11
- package/dist/channels/gates.d.ts +9 -0
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +252 -8
- package/dist/channels/index.mjs +250 -8
- package/dist/channels/pipeline.d.ts +106 -0
- package/dist/channels/pipeline.d.ts.map +1 -0
- package/dist/channels/senders.d.ts +2 -1
- package/dist/channels/senders.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +4 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/crypto.d.ts +17 -3
- package/dist/channels/whatsapp/crypto.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +34 -4
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +2 -1
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +395 -159
- package/dist/channels/whatsapp/index.mjs +394 -160
- package/dist/channels/whatsapp/router.d.ts +107 -0
- package/dist/channels/whatsapp/router.d.ts.map +1 -0
- package/dist/core/answer.d.ts +5 -2
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/logger.d.ts +23 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/messages.d.ts +8 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +4 -0
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/flows/engine.d.ts +3 -0
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/index.d.ts +1 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +91 -25
- package/dist/flows/index.mjs +91 -26
- package/dist/flows/intent.d.ts +2 -1
- package/dist/flows/intent.d.ts.map +1 -1
- package/dist/flows/loader.d.ts +5 -3
- package/dist/flows/loader.d.ts.map +1 -1
- package/dist/flows/types.d.ts +9 -0
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/history/index.d.ts +17 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +100 -0
- package/dist/history/index.mjs +73 -0
- package/dist/history/tursoStore.d.ts +26 -0
- package/dist/history/tursoStore.d.ts.map +1 -0
- package/dist/history/types.d.ts +18 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1146 -32444
- package/dist/index.mjs +1082 -32410
- package/dist/mcp-server/cost-tracker.d.ts +11 -6
- package/dist/mcp-server/cost-tracker.d.ts.map +1 -1
- package/dist/mcp-server/logger.d.ts +5 -2
- package/dist/mcp-server/logger.d.ts.map +1 -1
- package/dist/mcp-server/sdk.d.ts +23 -0
- package/dist/mcp-server/sdk.d.ts.map +1 -0
- package/dist/mcp-server/tool-input.d.ts +12 -0
- package/dist/mcp-server/tool-input.d.ts.map +1 -0
- package/dist/mcp-server/types.d.ts +10 -3
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server.d.ts +1 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +192 -111
- package/dist/mcp-server.mjs +179 -98
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/bodyLimit.d.ts +11 -0
- package/dist/middleware/bodyLimit.d.ts.map +1 -0
- package/dist/middleware/clientKey.d.ts +15 -0
- package/dist/middleware/clientKey.d.ts.map +1 -0
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/jwt.d.ts +0 -3
- package/dist/middleware/jwt.d.ts.map +1 -1
- package/dist/middleware/originMatch.d.ts +27 -0
- package/dist/middleware/originMatch.d.ts.map +1 -0
- package/dist/middleware/ratelimit.d.ts +0 -6
- package/dist/middleware/ratelimit.d.ts.map +1 -1
- package/dist/middleware/referrer.d.ts +5 -1
- package/dist/middleware/referrer.d.ts.map +1 -1
- package/dist/middleware/securityHeaders.d.ts +20 -0
- package/dist/middleware/securityHeaders.d.ts.map +1 -0
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/middleware/windowBucket.d.ts +19 -0
- package/dist/middleware/windowBucket.d.ts.map +1 -0
- package/dist/personas/index.js +20 -1
- package/dist/personas/index.mjs +20 -1
- package/dist/personas/resolver.d.ts +3 -0
- package/dist/personas/resolver.d.ts.map +1 -1
- package/dist/routes/demo.d.ts.map +1 -1
- package/dist/routes/openai.d.ts.map +1 -1
- package/dist/routes/private.d.ts.map +1 -1
- package/dist/routes/public.d.ts.map +1 -1
- package/dist/scheduler/compose.d.ts +2 -1
- package/dist/scheduler/compose.d.ts.map +1 -1
- package/dist/scheduler/index.js +29 -9
- package/dist/scheduler/index.mjs +29 -9
- package/dist/scheduler/scheduler.d.ts +3 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/tick.d.ts +2 -0
- package/dist/scheduler/tick.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +678 -528
- package/dist/server.mjs +678 -528
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -7
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
createWhatsAppChannel: () => createWhatsAppChannel,
|
|
40
40
|
createWhatsAppImageHandler: () => createWhatsAppImageHandler,
|
|
41
41
|
createWhatsAppInboundHandler: () => createWhatsAppInboundHandler,
|
|
42
|
+
createWhatsAppMessageRouter: () => createWhatsAppMessageRouter,
|
|
42
43
|
decrypt: () => decrypt,
|
|
43
44
|
drawSubject: () => drawSubject,
|
|
44
45
|
encrypt: () => encrypt,
|
|
@@ -50,6 +51,7 @@ __export(index_exports, {
|
|
|
50
51
|
loadBaileys: () => loadBaileys,
|
|
51
52
|
messageContext: () => messageContext,
|
|
52
53
|
reconnectDelayMs: () => reconnectDelayMs,
|
|
54
|
+
resolveWaMessage: () => resolveWaMessage,
|
|
53
55
|
senderNameFor: () => senderNameFor,
|
|
54
56
|
senderPhoneFor: () => senderPhoneFor,
|
|
55
57
|
shutdownWaSessions: () => shutdownWaSessions,
|
|
@@ -60,26 +62,59 @@ module.exports = __toCommonJS(index_exports);
|
|
|
60
62
|
|
|
61
63
|
// src/channels/whatsapp/crypto.ts
|
|
62
64
|
var import_node_crypto = require("node:crypto");
|
|
65
|
+
var import_node_util = require("node:util");
|
|
66
|
+
var scryptAsync = (0, import_node_util.promisify)(import_node_crypto.scrypt);
|
|
63
67
|
var IV_LENGTH = 12;
|
|
64
68
|
var TAG_LENGTH = 16;
|
|
65
|
-
|
|
69
|
+
var SALT_LENGTH = 16;
|
|
70
|
+
var KEY_LENGTH = 32;
|
|
71
|
+
var VERSION = 1;
|
|
72
|
+
function deriveLegacyKey(secret) {
|
|
66
73
|
return (0, import_node_crypto.createHash)("sha256").update(secret, "utf-8").digest();
|
|
67
74
|
}
|
|
68
|
-
function
|
|
75
|
+
async function deriveKey(secret, salt) {
|
|
76
|
+
return await scryptAsync(secret, salt, KEY_LENGTH);
|
|
77
|
+
}
|
|
78
|
+
async function encrypt(plaintext, secret) {
|
|
79
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
69
80
|
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
70
|
-
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", deriveKey(secret), iv);
|
|
81
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
71
82
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
72
|
-
return Buffer.concat([
|
|
83
|
+
return Buffer.concat([
|
|
84
|
+
Buffer.from([VERSION]),
|
|
85
|
+
salt,
|
|
86
|
+
iv,
|
|
87
|
+
cipher.getAuthTag(),
|
|
88
|
+
ciphertext
|
|
89
|
+
]).toString("base64");
|
|
73
90
|
}
|
|
74
|
-
function
|
|
75
|
-
const
|
|
91
|
+
async function decryptVersioned(raw, secret) {
|
|
92
|
+
const salt = raw.subarray(1, 1 + SALT_LENGTH);
|
|
93
|
+
const iv = raw.subarray(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);
|
|
94
|
+
const tag = raw.subarray(1 + SALT_LENGTH + IV_LENGTH, 1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
95
|
+
const ciphertext = raw.subarray(1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
96
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
97
|
+
decipher.setAuthTag(tag);
|
|
98
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
99
|
+
}
|
|
100
|
+
function decryptLegacy(raw, secret) {
|
|
76
101
|
const iv = raw.subarray(0, IV_LENGTH);
|
|
77
102
|
const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
78
103
|
const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
|
|
79
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm",
|
|
104
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", deriveLegacyKey(secret), iv);
|
|
80
105
|
decipher.setAuthTag(tag);
|
|
81
106
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
82
107
|
}
|
|
108
|
+
async function decrypt(payload, secret) {
|
|
109
|
+
const raw = Buffer.from(payload, "base64");
|
|
110
|
+
if (raw[0] === VERSION) {
|
|
111
|
+
try {
|
|
112
|
+
return await decryptVersioned(raw, secret);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return decryptLegacy(raw, secret);
|
|
117
|
+
}
|
|
83
118
|
|
|
84
119
|
// src/channels/whatsapp/authState.ts
|
|
85
120
|
var schemaReady = /* @__PURE__ */ new WeakMap();
|
|
@@ -104,10 +139,10 @@ async function useTursoAuthState(db, secret, sessionId, runtime) {
|
|
|
104
139
|
});
|
|
105
140
|
const row = result.rows[0];
|
|
106
141
|
if (!row) return null;
|
|
107
|
-
return JSON.parse(decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
142
|
+
return JSON.parse(await decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
108
143
|
}
|
|
109
144
|
async function write(id, value) {
|
|
110
|
-
const payload = encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
145
|
+
const payload = await encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
111
146
|
await db.execute({
|
|
112
147
|
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
113
148
|
args: [rowId(id), payload]
|
|
@@ -196,6 +231,35 @@ function silentLogger() {
|
|
|
196
231
|
return logger;
|
|
197
232
|
}
|
|
198
233
|
|
|
234
|
+
// src/auth/secretStrength.ts
|
|
235
|
+
var MIN_SECRET_LENGTH = 16;
|
|
236
|
+
function assertStrongSecret(secret, label) {
|
|
237
|
+
if (!secret) {
|
|
238
|
+
throw new Error(`${label} is required and cannot be empty.`);
|
|
239
|
+
}
|
|
240
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`${label} is too weak: must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length}). Generate one with \`openssl rand -base64 32\`.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/core/logger.ts
|
|
248
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
249
|
+
function createConsoleLogger(level = "info") {
|
|
250
|
+
const threshold = LEVEL_ORDER[level];
|
|
251
|
+
const write = (at) => (...args) => {
|
|
252
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
253
|
+
console.error(`[${at}]`, ...args);
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
debug: write("debug"),
|
|
257
|
+
info: write("info"),
|
|
258
|
+
warn: write("warn"),
|
|
259
|
+
error: write("error")
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
199
263
|
// src/channels/whatsapp/lease.ts
|
|
200
264
|
var LEASE_STALE_MS = 9e4;
|
|
201
265
|
var LEASE_HEARTBEAT_MS = 3e4;
|
|
@@ -278,11 +342,12 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
278
342
|
const heartbeatMs = deps.heartbeatMs ?? LEASE_HEARTBEAT_MS;
|
|
279
343
|
const waitMs = deps.waitMs ?? LEASE_WAIT_MS;
|
|
280
344
|
const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
|
|
345
|
+
const logger = deps.logger ?? createConsoleLogger();
|
|
281
346
|
if (isStopped()) return;
|
|
282
347
|
const retry = () => {
|
|
283
348
|
schedule(
|
|
284
349
|
() => void acquireSessionLease(sessionId, deps).catch(
|
|
285
|
-
(error) =>
|
|
350
|
+
(error) => logger.error(`WhatsApp[${sessionId}]: lease re-check failed:`, error)
|
|
286
351
|
),
|
|
287
352
|
waitMs
|
|
288
353
|
);
|
|
@@ -291,21 +356,21 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
291
356
|
try {
|
|
292
357
|
acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
|
|
293
358
|
} catch (error) {
|
|
294
|
-
|
|
359
|
+
logger.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
|
|
295
360
|
retry();
|
|
296
361
|
return;
|
|
297
362
|
}
|
|
298
363
|
if (!acquired) {
|
|
299
|
-
|
|
364
|
+
logger.warn(`WhatsApp[${sessionId}]: lease held by another instance, re-checking later`);
|
|
300
365
|
retry();
|
|
301
366
|
return;
|
|
302
367
|
}
|
|
303
368
|
const heartbeat = setInterval(() => {
|
|
304
369
|
void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
|
|
305
370
|
if (stillHeld || isStopped()) return;
|
|
306
|
-
|
|
371
|
+
logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
|
|
307
372
|
void loseSession(sessionId, sessions).then(retry);
|
|
308
|
-
}).catch((error) =>
|
|
373
|
+
}).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
|
|
309
374
|
}, heartbeatMs);
|
|
310
375
|
if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
|
|
311
376
|
sessions.set(sessionId, {
|
|
@@ -316,7 +381,7 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
316
381
|
try {
|
|
317
382
|
await connect(sessionId);
|
|
318
383
|
} catch (error) {
|
|
319
|
-
|
|
384
|
+
logger.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
|
|
320
385
|
await loseSession(sessionId, sessions);
|
|
321
386
|
retry();
|
|
322
387
|
}
|
|
@@ -325,6 +390,7 @@ function senderNameFor(channelName, sessionId) {
|
|
|
325
390
|
return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
|
|
326
391
|
}
|
|
327
392
|
function createWhatsAppChannel(config) {
|
|
393
|
+
assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
|
|
328
394
|
const channelName = "whatsapp";
|
|
329
395
|
const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
|
|
330
396
|
const now = config.now ?? Date.now;
|
|
@@ -342,6 +408,7 @@ function createWhatsAppChannel(config) {
|
|
|
342
408
|
return {
|
|
343
409
|
name: channelName,
|
|
344
410
|
async start(deps) {
|
|
411
|
+
const log = config.logger ?? deps.logger ?? createConsoleLogger();
|
|
345
412
|
const db = deps.db;
|
|
346
413
|
const leaseStore = createTursoWaLeaseStore(db);
|
|
347
414
|
senders = deps.senders;
|
|
@@ -361,7 +428,7 @@ function createWhatsAppChannel(config) {
|
|
|
361
428
|
runtime
|
|
362
429
|
);
|
|
363
430
|
if (!state.creds.registered) {
|
|
364
|
-
|
|
431
|
+
log.warn(
|
|
365
432
|
`WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
|
|
366
433
|
);
|
|
367
434
|
schedule(() => void connect(sessionId).catch(() => void 0), 6e4);
|
|
@@ -386,14 +453,14 @@ function createWhatsAppChannel(config) {
|
|
|
386
453
|
sock.ev.on(
|
|
387
454
|
"creds.update",
|
|
388
455
|
() => void saveCreds().catch(
|
|
389
|
-
(error) =>
|
|
456
|
+
(error) => log.warn(`WhatsApp[${sessionId}]: creds save failed:`, error)
|
|
390
457
|
)
|
|
391
458
|
);
|
|
392
459
|
sock.ev.on("connection.update", (update) => {
|
|
393
460
|
const { connection, lastDisconnect } = update;
|
|
394
461
|
if (connection === "open") {
|
|
395
462
|
failureCounts.set(sessionId, 0);
|
|
396
|
-
|
|
463
|
+
log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
|
|
397
464
|
const senderName = senderNameFor(channelName, sessionId);
|
|
398
465
|
const sender = {
|
|
399
466
|
sendText: async (chatId, text) => {
|
|
@@ -411,7 +478,7 @@ function createWhatsAppChannel(config) {
|
|
|
411
478
|
}
|
|
412
479
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
413
480
|
if (statusCode === baileys.DisconnectReason.loggedOut) {
|
|
414
|
-
|
|
481
|
+
log.error(
|
|
415
482
|
`WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
|
|
416
483
|
);
|
|
417
484
|
void loseSession(sessionId, sessions);
|
|
@@ -420,12 +487,12 @@ function createWhatsAppChannel(config) {
|
|
|
420
487
|
if (!stopped && sessions.has(sessionId)) {
|
|
421
488
|
const delay = reconnectDelayMs(failureCounts.get(sessionId) ?? 0);
|
|
422
489
|
failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
|
|
423
|
-
|
|
490
|
+
log.warn(
|
|
424
491
|
`WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
|
|
425
492
|
);
|
|
426
493
|
schedule(
|
|
427
494
|
() => void connect(sessionId).catch(
|
|
428
|
-
(error) =>
|
|
495
|
+
(error) => log.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
|
|
429
496
|
),
|
|
430
497
|
delay
|
|
431
498
|
);
|
|
@@ -439,7 +506,7 @@ function createWhatsAppChannel(config) {
|
|
|
439
506
|
try {
|
|
440
507
|
await config.onMessage?.({ sessionId, sock, message });
|
|
441
508
|
} catch (error) {
|
|
442
|
-
|
|
509
|
+
log.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
|
|
443
510
|
}
|
|
444
511
|
}
|
|
445
512
|
});
|
|
@@ -456,8 +523,9 @@ function createWhatsAppChannel(config) {
|
|
|
456
523
|
staleMs,
|
|
457
524
|
heartbeatMs,
|
|
458
525
|
waitMs,
|
|
459
|
-
schedule
|
|
460
|
-
|
|
526
|
+
schedule,
|
|
527
|
+
logger: log
|
|
528
|
+
}).catch((error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error));
|
|
461
529
|
}
|
|
462
530
|
},
|
|
463
531
|
async stop() {
|
|
@@ -536,6 +604,53 @@ function createWhatsAppImageHandler(deps) {
|
|
|
536
604
|
};
|
|
537
605
|
}
|
|
538
606
|
|
|
607
|
+
// src/channels/gates.ts
|
|
608
|
+
function matches(re, text) {
|
|
609
|
+
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
610
|
+
}
|
|
611
|
+
function isBlockedByAllowlist(msg, config) {
|
|
612
|
+
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
613
|
+
}
|
|
614
|
+
function decideChannelAction(msg, config) {
|
|
615
|
+
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
616
|
+
if (!msg.isDirectMessage) {
|
|
617
|
+
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
618
|
+
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
619
|
+
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
620
|
+
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
621
|
+
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
622
|
+
}
|
|
623
|
+
return "reply";
|
|
624
|
+
}
|
|
625
|
+
function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
|
|
626
|
+
const log = /* @__PURE__ */ new Map();
|
|
627
|
+
return function allow(key) {
|
|
628
|
+
const cutoff = now() - windowMs;
|
|
629
|
+
const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
|
|
630
|
+
if (entries.length >= maxPerWindow) {
|
|
631
|
+
if (entries.length > 0) log.set(key, entries);
|
|
632
|
+
else log.delete(key);
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
entries.push(now());
|
|
636
|
+
log.set(key, entries);
|
|
637
|
+
return true;
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function underReplyRateLimit(msg, limiters) {
|
|
641
|
+
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
642
|
+
}
|
|
643
|
+
function isFromAnySession(id, registry) {
|
|
644
|
+
if (!id) return false;
|
|
645
|
+
for (const ids of registry.values()) {
|
|
646
|
+
if (ids.includes(id)) return true;
|
|
647
|
+
}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
function isEffectivelyFromSelf(msg, registry) {
|
|
651
|
+
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
652
|
+
}
|
|
653
|
+
|
|
539
654
|
// src/core/guardrails.ts
|
|
540
655
|
function detectLeakage(text) {
|
|
541
656
|
const flags = [
|
|
@@ -580,8 +695,8 @@ async function completeOnce({
|
|
|
580
695
|
|
|
581
696
|
// src/core/answer.ts
|
|
582
697
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
583
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
|
|
584
|
-
const result = await answerFn({ system, messages, mode, sender });
|
|
698
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
699
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
585
700
|
const raw = typeof result === "string" ? result : result?.content;
|
|
586
701
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
587
702
|
return {
|
|
@@ -596,11 +711,12 @@ async function answerOnce({
|
|
|
596
711
|
messages,
|
|
597
712
|
mode,
|
|
598
713
|
sender,
|
|
714
|
+
conversationId,
|
|
599
715
|
temperature,
|
|
600
716
|
model
|
|
601
717
|
}) {
|
|
602
718
|
if (answerFn) {
|
|
603
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender });
|
|
719
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
604
720
|
}
|
|
605
721
|
return completeOnce({ client, system, messages, temperature, model });
|
|
606
722
|
}
|
|
@@ -636,6 +752,11 @@ async function resolveBuckets({
|
|
|
636
752
|
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
637
753
|
}
|
|
638
754
|
|
|
755
|
+
// src/core/messages.ts
|
|
756
|
+
function lastUserMessage(messages) {
|
|
757
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
758
|
+
}
|
|
759
|
+
|
|
639
760
|
// src/core/pipeline.ts
|
|
640
761
|
var MODE_SETTINGS = {
|
|
641
762
|
public: { topK: 6, label: "Context" },
|
|
@@ -650,7 +771,7 @@ async function prepareChat({
|
|
|
650
771
|
channelHint,
|
|
651
772
|
buckets
|
|
652
773
|
}) {
|
|
653
|
-
const lastUserMsg =
|
|
774
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
654
775
|
if (!lastUserMsg) {
|
|
655
776
|
throw new Error("no user message found in conversation");
|
|
656
777
|
}
|
|
@@ -665,54 +786,93 @@ async function prepareChat({
|
|
|
665
786
|
`${label}:
|
|
666
787
|
${ctx.join("\n\n")}`
|
|
667
788
|
].join("\n\n");
|
|
668
|
-
return { system, messages };
|
|
789
|
+
return { system, messages, context: ctx };
|
|
669
790
|
}
|
|
670
791
|
|
|
671
|
-
// src/channels/
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
function
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
682
|
-
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
683
|
-
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
684
|
-
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
792
|
+
// src/channels/pipeline.ts
|
|
793
|
+
var DEFAULT_HISTORY_LIMIT = 20;
|
|
794
|
+
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
795
|
+
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
796
|
+
async function resolvePersonaLayer(config, ctx) {
|
|
797
|
+
if (!config.personaResolver) return void 0;
|
|
798
|
+
try {
|
|
799
|
+
return await config.personaResolver(ctx);
|
|
800
|
+
} catch {
|
|
801
|
+
return void 0;
|
|
685
802
|
}
|
|
686
|
-
return "reply";
|
|
687
803
|
}
|
|
688
|
-
function
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
804
|
+
function createInboundPipeline(deps, config) {
|
|
805
|
+
const mutedChats = /* @__PURE__ */ new Set();
|
|
806
|
+
const now = config.now ?? Date.now;
|
|
807
|
+
const group = config.groupRateLimit ?? DEFAULT_GROUP_RATE_LIMIT;
|
|
808
|
+
const dm = config.dmRateLimit ?? DEFAULT_DM_RATE_LIMIT;
|
|
809
|
+
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
810
|
+
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
811
|
+
const mode = "public";
|
|
812
|
+
return async function handleInboundMessage(msg, turn) {
|
|
813
|
+
const allowedChats = config.allowedChats ?? [];
|
|
814
|
+
const action = decideChannelAction(msg, {
|
|
815
|
+
allowedChats,
|
|
816
|
+
mutedChats,
|
|
817
|
+
muteRegex: config.muteRegex,
|
|
818
|
+
unmuteRegex: config.unmuteRegex
|
|
819
|
+
});
|
|
820
|
+
if (action === "mute") {
|
|
821
|
+
mutedChats.add(msg.chatId);
|
|
822
|
+
if (config.muteReply) await turn.reply.sendGateReply(msg.chatId, config.muteReply);
|
|
823
|
+
return { action: "mute" };
|
|
697
824
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
825
|
+
if (action === "unmute") {
|
|
826
|
+
mutedChats.delete(msg.chatId);
|
|
827
|
+
if (config.unmuteReply) await turn.reply.sendGateReply(msg.chatId, config.unmuteReply);
|
|
828
|
+
return { action: "unmute" };
|
|
829
|
+
}
|
|
830
|
+
if (action === "ignore") {
|
|
831
|
+
return { action: "ignore" };
|
|
832
|
+
}
|
|
833
|
+
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
834
|
+
return { action: "rate-limited" };
|
|
835
|
+
}
|
|
836
|
+
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
837
|
+
const conversationId = turn.conversationId ?? msg.chatId;
|
|
838
|
+
if (turn.intercept && await turn.intercept(sender)) {
|
|
839
|
+
return { action: "intercepted" };
|
|
840
|
+
}
|
|
841
|
+
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
842
|
+
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
843
|
+
const priorTurns = config.history ? await config.history.store.load(
|
|
844
|
+
conversationId,
|
|
845
|
+
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
846
|
+
) : [];
|
|
847
|
+
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
848
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
849
|
+
const { system } = await prepareChat({
|
|
850
|
+
store: deps.store,
|
|
851
|
+
prompts: deps.prompts,
|
|
852
|
+
mode,
|
|
853
|
+
messages,
|
|
854
|
+
personaLayer,
|
|
855
|
+
channelHint: config.channelHint,
|
|
856
|
+
buckets
|
|
857
|
+
});
|
|
858
|
+
const { content } = await answerOnce({
|
|
859
|
+
answerFn: config.answerFn,
|
|
860
|
+
client: deps.client,
|
|
861
|
+
system,
|
|
862
|
+
messages,
|
|
863
|
+
mode,
|
|
864
|
+
sender,
|
|
865
|
+
conversationId,
|
|
866
|
+
model: config.model
|
|
867
|
+
});
|
|
868
|
+
if (!content) {
|
|
869
|
+
return { action: "ignore" };
|
|
870
|
+
}
|
|
871
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
872
|
+
await turn.reply.sendAnswer(msg.chatId, content);
|
|
873
|
+
return { action: "reply", content };
|
|
701
874
|
};
|
|
702
875
|
}
|
|
703
|
-
function underReplyRateLimit(msg, limiters) {
|
|
704
|
-
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
705
|
-
}
|
|
706
|
-
function isFromAnySession(id, registry) {
|
|
707
|
-
if (!id) return false;
|
|
708
|
-
for (const ids of registry.values()) {
|
|
709
|
-
if (ids.includes(id)) return true;
|
|
710
|
-
}
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
function isEffectivelyFromSelf(msg, registry) {
|
|
714
|
-
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
715
|
-
}
|
|
716
876
|
|
|
717
877
|
// src/channels/whatsapp/inbound.ts
|
|
718
878
|
function isGroupJid(jid) {
|
|
@@ -729,11 +889,6 @@ function jidToPhoneNumber(jid) {
|
|
|
729
889
|
const bare = bareUser(jid);
|
|
730
890
|
return bare.startsWith("+") ? bare : `+${bare}`;
|
|
731
891
|
}
|
|
732
|
-
function normalizeJid(jid) {
|
|
733
|
-
const [user = "", domain] = jid.split("@");
|
|
734
|
-
const withoutDevice = user.split(":")[0] ?? user;
|
|
735
|
-
return domain ? `${withoutDevice}@${domain}` : withoutDevice;
|
|
736
|
-
}
|
|
737
892
|
async function senderPhoneFor(sock, message, chatJid) {
|
|
738
893
|
const key = message.key;
|
|
739
894
|
let jid = [key.participantPn, key.senderPn, key.participant, key.remoteJidAlt, chatJid].find(
|
|
@@ -761,6 +916,11 @@ function extractText(message) {
|
|
|
761
916
|
if (!content) return "";
|
|
762
917
|
return content.conversation ?? content.extendedTextMessage?.text ?? content.imageMessage?.caption ?? content.videoMessage?.caption ?? content.documentMessage?.caption ?? "";
|
|
763
918
|
}
|
|
919
|
+
function normalizeJid(jid) {
|
|
920
|
+
const [user = "", domain] = jid.split("@");
|
|
921
|
+
const withoutDevice = user.split(":")[0] ?? user;
|
|
922
|
+
return domain ? `${withoutDevice}@${domain}` : withoutDevice;
|
|
923
|
+
}
|
|
764
924
|
function stripOwnMentions(text, ownIds) {
|
|
765
925
|
const ownUsers = new Set(ownIds.map(bareUser).filter(Boolean));
|
|
766
926
|
if (!text || ownUsers.size === 0) return text;
|
|
@@ -774,109 +934,183 @@ function stripOwnMentions(text, ownIds) {
|
|
|
774
934
|
const cleaned = stripped.trim();
|
|
775
935
|
return cleaned ? cleaned : text;
|
|
776
936
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
937
|
+
function resolveWaMessage(event, registry) {
|
|
938
|
+
const { sessionId, sock, message } = event;
|
|
939
|
+
const chatId = message.key.remoteJid ?? "";
|
|
940
|
+
const rawText = extractText(message);
|
|
941
|
+
const context = messageContext(message);
|
|
942
|
+
const senderId = message.key.participant ?? chatId;
|
|
943
|
+
const ownIds = [sock.user?.id, sock.user?.lid].filter((id) => Boolean(id)).map(normalizeJid);
|
|
944
|
+
registry.set(sessionId, ownIds);
|
|
945
|
+
const text = stripOwnMentions(rawText, ownIds);
|
|
946
|
+
const fromBot = isEffectivelyFromSelf(
|
|
947
|
+
{ fromBot: message.key.fromMe ?? false, senderId: normalizeJid(senderId) },
|
|
948
|
+
registry
|
|
949
|
+
);
|
|
950
|
+
const msg = {
|
|
951
|
+
chatId,
|
|
952
|
+
senderId,
|
|
953
|
+
text,
|
|
954
|
+
isDirectMessage: !isGroupJid(chatId),
|
|
955
|
+
mentionsBot: context.mentionedJids.some((jid) => ownIds.some((own) => jidsMatch(jid, own))),
|
|
956
|
+
isReplyToBot: ownIds.some((own) => jidsMatch(context.quotedParticipantJid, own)),
|
|
957
|
+
fromBot
|
|
958
|
+
};
|
|
959
|
+
return { msg, ownIds };
|
|
786
960
|
}
|
|
787
961
|
function createWhatsAppInboundHandler(config) {
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
const
|
|
793
|
-
const
|
|
962
|
+
const loggedUnallowedChats = /* @__PURE__ */ new Set();
|
|
963
|
+
const allowedChats = config.allowedChats ?? [];
|
|
964
|
+
const personaResolver = config.personaResolver;
|
|
965
|
+
const images = config.images;
|
|
966
|
+
const logger = config.logger ?? createConsoleLogger();
|
|
967
|
+
const pipeline = createInboundPipeline(
|
|
968
|
+
{ client: config.client, store: config.store, prompts: config.prompts },
|
|
969
|
+
{
|
|
970
|
+
answerFn: config.answerFn,
|
|
971
|
+
bucketsFor: config.bucketsFor,
|
|
972
|
+
model: config.model,
|
|
973
|
+
channelHint: config.channelHint,
|
|
974
|
+
personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
|
|
975
|
+
history: config.history,
|
|
976
|
+
allowedChats,
|
|
977
|
+
muteRegex: config.muteRegex,
|
|
978
|
+
unmuteRegex: config.unmuteRegex,
|
|
979
|
+
muteReply: config.muteReply,
|
|
980
|
+
unmuteReply: config.unmuteReply,
|
|
981
|
+
dmRateLimit: config.dmRateLimit,
|
|
982
|
+
groupRateLimit: config.groupRateLimit,
|
|
983
|
+
now: config.now
|
|
984
|
+
}
|
|
985
|
+
);
|
|
794
986
|
return async function handleWhatsAppMessage(event) {
|
|
795
987
|
const { sessionId, sock, message } = event;
|
|
796
988
|
try {
|
|
797
989
|
const chatId = message.key.remoteJid ?? "";
|
|
798
990
|
if (!chatId || chatId === "status@broadcast") return;
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
config.registry
|
|
808
|
-
);
|
|
809
|
-
const msg = {
|
|
810
|
-
chatId,
|
|
811
|
-
senderId,
|
|
812
|
-
text,
|
|
813
|
-
isDirectMessage: !isGroupJid(chatId),
|
|
814
|
-
mentionsBot: context.mentionedJids.some((jid) => ownIds.some((own) => jidsMatch(jid, own))),
|
|
815
|
-
isReplyToBot: ownIds.some((own) => jidsMatch(context.quotedParticipantJid, own)),
|
|
816
|
-
fromBot
|
|
817
|
-
};
|
|
818
|
-
const action = decideChannelAction(msg, {
|
|
819
|
-
allowedChats: config.allowedChats ?? [],
|
|
820
|
-
mutedChats,
|
|
821
|
-
muteRegex: config.muteRegex,
|
|
822
|
-
unmuteRegex: config.unmuteRegex
|
|
823
|
-
});
|
|
824
|
-
if (action === "mute") {
|
|
825
|
-
mutedChats.add(chatId);
|
|
826
|
-
if (config.muteReply) await sock.sendMessage(chatId, { text: config.muteReply });
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
if (action === "unmute") {
|
|
830
|
-
mutedChats.delete(chatId);
|
|
831
|
-
if (config.unmuteReply) await sock.sendMessage(chatId, { text: config.unmuteReply });
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
if (action !== "reply") return;
|
|
835
|
-
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
836
|
-
return;
|
|
991
|
+
const { msg } = resolveWaMessage(event, config.registry);
|
|
992
|
+
const text = msg.text;
|
|
993
|
+
if (isBlockedByAllowlist(msg, { allowedChats })) {
|
|
994
|
+
const dedupKey = `${sessionId}:${chatId}`;
|
|
995
|
+
if (!loggedUnallowedChats.has(dedupKey)) {
|
|
996
|
+
loggedUnallowedChats.add(dedupKey);
|
|
997
|
+
logger.warn(`WhatsApp[${sessionId}]: skipped group ${chatId} - not in allowedChats`);
|
|
998
|
+
}
|
|
837
999
|
}
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
1000
|
+
const reply = {
|
|
1001
|
+
sendAnswer: async (targetChatId, replyText2) => {
|
|
1002
|
+
await sock.sendMessage(targetChatId, { text: replyText2 }, { quoted: message });
|
|
1003
|
+
},
|
|
1004
|
+
sendGateReply: async (targetChatId, replyText2) => {
|
|
1005
|
+
await sock.sendMessage(targetChatId, { text: replyText2 });
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
await pipeline(msg, {
|
|
1009
|
+
reply,
|
|
1010
|
+
conversationId: chatId,
|
|
1011
|
+
sender: () => senderPhoneFor(sock, message, chatId),
|
|
1012
|
+
intercept: images ? (senderPhone) => images({
|
|
841
1013
|
sock,
|
|
842
1014
|
message,
|
|
843
1015
|
chatId,
|
|
844
1016
|
senderId: senderPhone,
|
|
845
1017
|
text,
|
|
846
1018
|
hasPhoto: Boolean(message.message?.imageMessage)
|
|
847
|
-
})
|
|
848
|
-
if (handled) return;
|
|
849
|
-
}
|
|
850
|
-
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
851
|
-
const buckets = await resolveBuckets({
|
|
852
|
-
mode: "public",
|
|
853
|
-
sender: senderPhone,
|
|
854
|
-
bucketsFor: config.bucketsFor
|
|
855
|
-
});
|
|
856
|
-
const messages = [{ role: "user", content: text }];
|
|
857
|
-
const { system } = await prepareChat({
|
|
858
|
-
store: config.store,
|
|
859
|
-
prompts: config.prompts,
|
|
860
|
-
mode: "public",
|
|
861
|
-
messages,
|
|
862
|
-
personaLayer,
|
|
863
|
-
channelHint: config.channelHint,
|
|
864
|
-
buckets
|
|
1019
|
+
}) : void 0
|
|
865
1020
|
});
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
logger.warn(`WhatsApp[${sessionId}]: inbound message handling failed:`, error);
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/channels/whatsapp/router.ts
|
|
1028
|
+
async function logDetectorError(config, logger, name, error) {
|
|
1029
|
+
if (!config.onDetectorError) {
|
|
1030
|
+
logger.warn(`WhatsApp router: detector "${name}" failed:`, error);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
await config.onDetectorError(name, error);
|
|
1035
|
+
} catch (callbackError) {
|
|
1036
|
+
logger.warn(`WhatsApp router: onDetectorError itself failed:`, callbackError);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function isChatIneligible(msg, allowedChats) {
|
|
1040
|
+
return !msg.isDirectMessage && allowedChats.length > 0 && !allowedChats.includes(msg.chatId);
|
|
1041
|
+
}
|
|
1042
|
+
function fireParallelDetector(config, logger, detector, ctx) {
|
|
1043
|
+
void (async () => {
|
|
1044
|
+
try {
|
|
1045
|
+
if (await detector.test(ctx)) {
|
|
1046
|
+
await detector.handle(ctx);
|
|
1047
|
+
}
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
1050
|
+
}
|
|
1051
|
+
})();
|
|
1052
|
+
}
|
|
1053
|
+
function createWhatsAppMessageRouter(config) {
|
|
1054
|
+
const allowedChats = config.allowedChats ?? [];
|
|
1055
|
+
const mutedChats = config.mutedChats ?? /* @__PURE__ */ new Set();
|
|
1056
|
+
const logger = config.logger ?? createConsoleLogger();
|
|
1057
|
+
const replaceDetectors = config.detectors.filter(
|
|
1058
|
+
(d) => d.mode === "replace"
|
|
1059
|
+
);
|
|
1060
|
+
const parallelDetectors = config.detectors.filter(
|
|
1061
|
+
(d) => d.mode === "parallel"
|
|
1062
|
+
);
|
|
1063
|
+
return async function routeWhatsAppMessage(event) {
|
|
1064
|
+
const { sock, message } = event;
|
|
1065
|
+
let ctx;
|
|
1066
|
+
try {
|
|
1067
|
+
const chatId = message.key.remoteJid ?? "";
|
|
1068
|
+
if (!chatId || chatId === "status@broadcast") return;
|
|
1069
|
+
const { msg, ownIds } = resolveWaMessage(event, config.registry);
|
|
1070
|
+
if (msg.fromBot || isChatIneligible(msg, allowedChats)) return;
|
|
1071
|
+
const action = decideChannelAction(msg, {
|
|
1072
|
+
allowedChats,
|
|
1073
|
+
mutedChats,
|
|
1074
|
+
muteRegex: config.muteRegex,
|
|
1075
|
+
unmuteRegex: config.unmuteRegex
|
|
874
1076
|
});
|
|
875
|
-
|
|
876
|
-
|
|
1077
|
+
ctx = {
|
|
1078
|
+
event,
|
|
1079
|
+
sock,
|
|
1080
|
+
message,
|
|
1081
|
+
msg,
|
|
1082
|
+
ownIds,
|
|
1083
|
+
senderId: msg.senderId,
|
|
1084
|
+
text: msg.text,
|
|
1085
|
+
action
|
|
1086
|
+
};
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
await logDetectorError(config, logger, "resolve", error);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
for (const detector of parallelDetectors) {
|
|
1092
|
+
fireParallelDetector(config, logger, detector, ctx);
|
|
1093
|
+
}
|
|
1094
|
+
for (const detector of replaceDetectors) {
|
|
1095
|
+
let matched;
|
|
1096
|
+
try {
|
|
1097
|
+
matched = await detector.test(ctx);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
1100
|
+
continue;
|
|
877
1101
|
}
|
|
1102
|
+
if (!matched) continue;
|
|
1103
|
+
try {
|
|
1104
|
+
await detector.handle(ctx);
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
1107
|
+
}
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
try {
|
|
1111
|
+
await config.fallback(event);
|
|
878
1112
|
} catch (error) {
|
|
879
|
-
|
|
1113
|
+
await logDetectorError(config, logger, "fallback", error);
|
|
880
1114
|
}
|
|
881
1115
|
};
|
|
882
1116
|
}
|
|
@@ -891,6 +1125,7 @@ function createWhatsAppInboundHandler(config) {
|
|
|
891
1125
|
createWhatsAppChannel,
|
|
892
1126
|
createWhatsAppImageHandler,
|
|
893
1127
|
createWhatsAppInboundHandler,
|
|
1128
|
+
createWhatsAppMessageRouter,
|
|
894
1129
|
decrypt,
|
|
895
1130
|
drawSubject,
|
|
896
1131
|
encrypt,
|
|
@@ -902,6 +1137,7 @@ function createWhatsAppInboundHandler(config) {
|
|
|
902
1137
|
loadBaileys,
|
|
903
1138
|
messageContext,
|
|
904
1139
|
reconnectDelayMs,
|
|
1140
|
+
resolveWaMessage,
|
|
905
1141
|
senderNameFor,
|
|
906
1142
|
senderPhoneFor,
|
|
907
1143
|
shutdownWaSessions,
|