@diegoaltoworks/chatter 0.47.0 → 0.49.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 +7 -6
- 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 +23 -8
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
// src/channels/whatsapp/crypto.ts
|
|
2
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, scrypt } from "node:crypto";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
var scryptAsync = promisify(scrypt);
|
|
3
5
|
var IV_LENGTH = 12;
|
|
4
6
|
var TAG_LENGTH = 16;
|
|
5
|
-
|
|
7
|
+
var SALT_LENGTH = 16;
|
|
8
|
+
var KEY_LENGTH = 32;
|
|
9
|
+
var VERSION = 1;
|
|
10
|
+
function deriveLegacyKey(secret) {
|
|
6
11
|
return createHash("sha256").update(secret, "utf-8").digest();
|
|
7
12
|
}
|
|
8
|
-
function
|
|
13
|
+
async function deriveKey(secret, salt) {
|
|
14
|
+
return await scryptAsync(secret, salt, KEY_LENGTH);
|
|
15
|
+
}
|
|
16
|
+
async function encrypt(plaintext, secret) {
|
|
17
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
9
18
|
const iv = randomBytes(IV_LENGTH);
|
|
10
|
-
const cipher = createCipheriv("aes-256-gcm", deriveKey(secret), iv);
|
|
19
|
+
const cipher = createCipheriv("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
11
20
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
12
|
-
return Buffer.concat([
|
|
21
|
+
return Buffer.concat([
|
|
22
|
+
Buffer.from([VERSION]),
|
|
23
|
+
salt,
|
|
24
|
+
iv,
|
|
25
|
+
cipher.getAuthTag(),
|
|
26
|
+
ciphertext
|
|
27
|
+
]).toString("base64");
|
|
13
28
|
}
|
|
14
|
-
function
|
|
15
|
-
const
|
|
29
|
+
async function decryptVersioned(raw, secret) {
|
|
30
|
+
const salt = raw.subarray(1, 1 + SALT_LENGTH);
|
|
31
|
+
const iv = raw.subarray(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);
|
|
32
|
+
const tag = raw.subarray(1 + SALT_LENGTH + IV_LENGTH, 1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
33
|
+
const ciphertext = raw.subarray(1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
34
|
+
const decipher = createDecipheriv("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
35
|
+
decipher.setAuthTag(tag);
|
|
36
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
37
|
+
}
|
|
38
|
+
function decryptLegacy(raw, secret) {
|
|
16
39
|
const iv = raw.subarray(0, IV_LENGTH);
|
|
17
40
|
const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
18
41
|
const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
|
|
19
|
-
const decipher = createDecipheriv("aes-256-gcm",
|
|
42
|
+
const decipher = createDecipheriv("aes-256-gcm", deriveLegacyKey(secret), iv);
|
|
20
43
|
decipher.setAuthTag(tag);
|
|
21
44
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
22
45
|
}
|
|
46
|
+
async function decrypt(payload, secret) {
|
|
47
|
+
const raw = Buffer.from(payload, "base64");
|
|
48
|
+
if (raw[0] === VERSION) {
|
|
49
|
+
try {
|
|
50
|
+
return await decryptVersioned(raw, secret);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return decryptLegacy(raw, secret);
|
|
55
|
+
}
|
|
23
56
|
|
|
24
57
|
// src/channels/whatsapp/authState.ts
|
|
25
58
|
var schemaReady = /* @__PURE__ */ new WeakMap();
|
|
@@ -44,10 +77,10 @@ async function useTursoAuthState(db, secret, sessionId, runtime) {
|
|
|
44
77
|
});
|
|
45
78
|
const row = result.rows[0];
|
|
46
79
|
if (!row) return null;
|
|
47
|
-
return JSON.parse(decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
80
|
+
return JSON.parse(await decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
48
81
|
}
|
|
49
82
|
async function write(id, value) {
|
|
50
|
-
const payload = encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
83
|
+
const payload = await encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
51
84
|
await db.execute({
|
|
52
85
|
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
53
86
|
args: [rowId(id), payload]
|
|
@@ -136,6 +169,35 @@ function silentLogger() {
|
|
|
136
169
|
return logger;
|
|
137
170
|
}
|
|
138
171
|
|
|
172
|
+
// src/auth/secretStrength.ts
|
|
173
|
+
var MIN_SECRET_LENGTH = 16;
|
|
174
|
+
function assertStrongSecret(secret, label) {
|
|
175
|
+
if (!secret) {
|
|
176
|
+
throw new Error(`${label} is required and cannot be empty.`);
|
|
177
|
+
}
|
|
178
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`${label} is too weak: must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length}). Generate one with \`openssl rand -base64 32\`.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/core/logger.ts
|
|
186
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
187
|
+
function createConsoleLogger(level = "info") {
|
|
188
|
+
const threshold = LEVEL_ORDER[level];
|
|
189
|
+
const write = (at) => (...args) => {
|
|
190
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
191
|
+
console.error(`[${at}]`, ...args);
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
debug: write("debug"),
|
|
195
|
+
info: write("info"),
|
|
196
|
+
warn: write("warn"),
|
|
197
|
+
error: write("error")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
139
201
|
// src/channels/whatsapp/lease.ts
|
|
140
202
|
var LEASE_STALE_MS = 9e4;
|
|
141
203
|
var LEASE_HEARTBEAT_MS = 3e4;
|
|
@@ -218,11 +280,12 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
218
280
|
const heartbeatMs = deps.heartbeatMs ?? LEASE_HEARTBEAT_MS;
|
|
219
281
|
const waitMs = deps.waitMs ?? LEASE_WAIT_MS;
|
|
220
282
|
const schedule = deps.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
|
|
283
|
+
const logger = deps.logger ?? createConsoleLogger();
|
|
221
284
|
if (isStopped()) return;
|
|
222
285
|
const retry = () => {
|
|
223
286
|
schedule(
|
|
224
287
|
() => void acquireSessionLease(sessionId, deps).catch(
|
|
225
|
-
(error) =>
|
|
288
|
+
(error) => logger.error(`WhatsApp[${sessionId}]: lease re-check failed:`, error)
|
|
226
289
|
),
|
|
227
290
|
waitMs
|
|
228
291
|
);
|
|
@@ -231,21 +294,21 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
231
294
|
try {
|
|
232
295
|
acquired = await leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs);
|
|
233
296
|
} catch (error) {
|
|
234
|
-
|
|
297
|
+
logger.warn(`WhatsApp[${sessionId}]: lease acquire failed, retrying:`, error);
|
|
235
298
|
retry();
|
|
236
299
|
return;
|
|
237
300
|
}
|
|
238
301
|
if (!acquired) {
|
|
239
|
-
|
|
302
|
+
logger.warn(`WhatsApp[${sessionId}]: lease held by another instance, re-checking later`);
|
|
240
303
|
retry();
|
|
241
304
|
return;
|
|
242
305
|
}
|
|
243
306
|
const heartbeat = setInterval(() => {
|
|
244
307
|
void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
|
|
245
308
|
if (stillHeld || isStopped()) return;
|
|
246
|
-
|
|
309
|
+
logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
|
|
247
310
|
void loseSession(sessionId, sessions).then(retry);
|
|
248
|
-
}).catch((error) =>
|
|
311
|
+
}).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
|
|
249
312
|
}, heartbeatMs);
|
|
250
313
|
if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
|
|
251
314
|
sessions.set(sessionId, {
|
|
@@ -256,7 +319,7 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
256
319
|
try {
|
|
257
320
|
await connect(sessionId);
|
|
258
321
|
} catch (error) {
|
|
259
|
-
|
|
322
|
+
logger.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
|
|
260
323
|
await loseSession(sessionId, sessions);
|
|
261
324
|
retry();
|
|
262
325
|
}
|
|
@@ -265,6 +328,7 @@ function senderNameFor(channelName, sessionId) {
|
|
|
265
328
|
return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
|
|
266
329
|
}
|
|
267
330
|
function createWhatsAppChannel(config) {
|
|
331
|
+
assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
|
|
268
332
|
const channelName = "whatsapp";
|
|
269
333
|
const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
|
|
270
334
|
const now = config.now ?? Date.now;
|
|
@@ -282,6 +346,7 @@ function createWhatsAppChannel(config) {
|
|
|
282
346
|
return {
|
|
283
347
|
name: channelName,
|
|
284
348
|
async start(deps) {
|
|
349
|
+
const log = config.logger ?? deps.logger ?? createConsoleLogger();
|
|
285
350
|
const db = deps.db;
|
|
286
351
|
const leaseStore = createTursoWaLeaseStore(db);
|
|
287
352
|
senders = deps.senders;
|
|
@@ -301,7 +366,7 @@ function createWhatsAppChannel(config) {
|
|
|
301
366
|
runtime
|
|
302
367
|
);
|
|
303
368
|
if (!state.creds.registered) {
|
|
304
|
-
|
|
369
|
+
log.warn(
|
|
305
370
|
`WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
|
|
306
371
|
);
|
|
307
372
|
schedule(() => void connect(sessionId).catch(() => void 0), 6e4);
|
|
@@ -326,14 +391,14 @@ function createWhatsAppChannel(config) {
|
|
|
326
391
|
sock.ev.on(
|
|
327
392
|
"creds.update",
|
|
328
393
|
() => void saveCreds().catch(
|
|
329
|
-
(error) =>
|
|
394
|
+
(error) => log.warn(`WhatsApp[${sessionId}]: creds save failed:`, error)
|
|
330
395
|
)
|
|
331
396
|
);
|
|
332
397
|
sock.ev.on("connection.update", (update) => {
|
|
333
398
|
const { connection, lastDisconnect } = update;
|
|
334
399
|
if (connection === "open") {
|
|
335
400
|
failureCounts.set(sessionId, 0);
|
|
336
|
-
|
|
401
|
+
log.info(`WhatsApp[${sessionId}]: connected as ${sock.user?.id ?? "unknown"}`);
|
|
337
402
|
const senderName = senderNameFor(channelName, sessionId);
|
|
338
403
|
const sender = {
|
|
339
404
|
sendText: async (chatId, text) => {
|
|
@@ -351,7 +416,7 @@ function createWhatsAppChannel(config) {
|
|
|
351
416
|
}
|
|
352
417
|
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
353
418
|
if (statusCode === baileys.DisconnectReason.loggedOut) {
|
|
354
|
-
|
|
419
|
+
log.error(
|
|
355
420
|
`WhatsApp[${sessionId}]: session logged out \u2014 re-pair to reconnect (see docs/channels.md).`
|
|
356
421
|
);
|
|
357
422
|
void loseSession(sessionId, sessions);
|
|
@@ -360,12 +425,12 @@ function createWhatsAppChannel(config) {
|
|
|
360
425
|
if (!stopped && sessions.has(sessionId)) {
|
|
361
426
|
const delay = reconnectDelayMs(failureCounts.get(sessionId) ?? 0);
|
|
362
427
|
failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
|
|
363
|
-
|
|
428
|
+
log.warn(
|
|
364
429
|
`WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
|
|
365
430
|
);
|
|
366
431
|
schedule(
|
|
367
432
|
() => void connect(sessionId).catch(
|
|
368
|
-
(error) =>
|
|
433
|
+
(error) => log.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
|
|
369
434
|
),
|
|
370
435
|
delay
|
|
371
436
|
);
|
|
@@ -379,7 +444,7 @@ function createWhatsAppChannel(config) {
|
|
|
379
444
|
try {
|
|
380
445
|
await config.onMessage?.({ sessionId, sock, message });
|
|
381
446
|
} catch (error) {
|
|
382
|
-
|
|
447
|
+
log.warn(`WhatsApp[${sessionId}]: onMessage handler failed:`, error);
|
|
383
448
|
}
|
|
384
449
|
}
|
|
385
450
|
});
|
|
@@ -396,8 +461,9 @@ function createWhatsAppChannel(config) {
|
|
|
396
461
|
staleMs,
|
|
397
462
|
heartbeatMs,
|
|
398
463
|
waitMs,
|
|
399
|
-
schedule
|
|
400
|
-
|
|
464
|
+
schedule,
|
|
465
|
+
logger: log
|
|
466
|
+
}).catch((error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error));
|
|
401
467
|
}
|
|
402
468
|
},
|
|
403
469
|
async stop() {
|
|
@@ -476,6 +542,53 @@ function createWhatsAppImageHandler(deps) {
|
|
|
476
542
|
};
|
|
477
543
|
}
|
|
478
544
|
|
|
545
|
+
// src/channels/gates.ts
|
|
546
|
+
function matches(re, text) {
|
|
547
|
+
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
548
|
+
}
|
|
549
|
+
function isBlockedByAllowlist(msg, config) {
|
|
550
|
+
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
551
|
+
}
|
|
552
|
+
function decideChannelAction(msg, config) {
|
|
553
|
+
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
554
|
+
if (!msg.isDirectMessage) {
|
|
555
|
+
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
556
|
+
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
557
|
+
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
558
|
+
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
559
|
+
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
560
|
+
}
|
|
561
|
+
return "reply";
|
|
562
|
+
}
|
|
563
|
+
function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
|
|
564
|
+
const log = /* @__PURE__ */ new Map();
|
|
565
|
+
return function allow(key) {
|
|
566
|
+
const cutoff = now() - windowMs;
|
|
567
|
+
const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
|
|
568
|
+
if (entries.length >= maxPerWindow) {
|
|
569
|
+
if (entries.length > 0) log.set(key, entries);
|
|
570
|
+
else log.delete(key);
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
entries.push(now());
|
|
574
|
+
log.set(key, entries);
|
|
575
|
+
return true;
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function underReplyRateLimit(msg, limiters) {
|
|
579
|
+
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
580
|
+
}
|
|
581
|
+
function isFromAnySession(id, registry) {
|
|
582
|
+
if (!id) return false;
|
|
583
|
+
for (const ids of registry.values()) {
|
|
584
|
+
if (ids.includes(id)) return true;
|
|
585
|
+
}
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
function isEffectivelyFromSelf(msg, registry) {
|
|
589
|
+
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
590
|
+
}
|
|
591
|
+
|
|
479
592
|
// src/core/guardrails.ts
|
|
480
593
|
function detectLeakage(text) {
|
|
481
594
|
const flags = [
|
|
@@ -520,8 +633,8 @@ async function completeOnce({
|
|
|
520
633
|
|
|
521
634
|
// src/core/answer.ts
|
|
522
635
|
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
523
|
-
async function runAnswerFn(answerFn, { system, messages, mode, sender }) {
|
|
524
|
-
const result = await answerFn({ system, messages, mode, sender });
|
|
636
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
637
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
525
638
|
const raw = typeof result === "string" ? result : result?.content;
|
|
526
639
|
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
527
640
|
return {
|
|
@@ -536,11 +649,12 @@ async function answerOnce({
|
|
|
536
649
|
messages,
|
|
537
650
|
mode,
|
|
538
651
|
sender,
|
|
652
|
+
conversationId,
|
|
539
653
|
temperature,
|
|
540
654
|
model
|
|
541
655
|
}) {
|
|
542
656
|
if (answerFn) {
|
|
543
|
-
return runAnswerFn(answerFn, { system, messages, mode, sender });
|
|
657
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
544
658
|
}
|
|
545
659
|
return completeOnce({ client, system, messages, temperature, model });
|
|
546
660
|
}
|
|
@@ -576,6 +690,11 @@ async function resolveBuckets({
|
|
|
576
690
|
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
577
691
|
}
|
|
578
692
|
|
|
693
|
+
// src/core/messages.ts
|
|
694
|
+
function lastUserMessage(messages) {
|
|
695
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
696
|
+
}
|
|
697
|
+
|
|
579
698
|
// src/core/pipeline.ts
|
|
580
699
|
var MODE_SETTINGS = {
|
|
581
700
|
public: { topK: 6, label: "Context" },
|
|
@@ -590,7 +709,7 @@ async function prepareChat({
|
|
|
590
709
|
channelHint,
|
|
591
710
|
buckets
|
|
592
711
|
}) {
|
|
593
|
-
const lastUserMsg =
|
|
712
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
594
713
|
if (!lastUserMsg) {
|
|
595
714
|
throw new Error("no user message found in conversation");
|
|
596
715
|
}
|
|
@@ -605,54 +724,93 @@ async function prepareChat({
|
|
|
605
724
|
`${label}:
|
|
606
725
|
${ctx.join("\n\n")}`
|
|
607
726
|
].join("\n\n");
|
|
608
|
-
return { system, messages };
|
|
727
|
+
return { system, messages, context: ctx };
|
|
609
728
|
}
|
|
610
729
|
|
|
611
|
-
// src/channels/
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
function
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
622
|
-
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
623
|
-
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
624
|
-
return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
|
|
730
|
+
// src/channels/pipeline.ts
|
|
731
|
+
var DEFAULT_HISTORY_LIMIT = 20;
|
|
732
|
+
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
733
|
+
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
734
|
+
async function resolvePersonaLayer(config, ctx) {
|
|
735
|
+
if (!config.personaResolver) return void 0;
|
|
736
|
+
try {
|
|
737
|
+
return await config.personaResolver(ctx);
|
|
738
|
+
} catch {
|
|
739
|
+
return void 0;
|
|
625
740
|
}
|
|
626
|
-
return "reply";
|
|
627
741
|
}
|
|
628
|
-
function
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
742
|
+
function createInboundPipeline(deps, config) {
|
|
743
|
+
const mutedChats = /* @__PURE__ */ new Set();
|
|
744
|
+
const now = config.now ?? Date.now;
|
|
745
|
+
const group = config.groupRateLimit ?? DEFAULT_GROUP_RATE_LIMIT;
|
|
746
|
+
const dm = config.dmRateLimit ?? DEFAULT_DM_RATE_LIMIT;
|
|
747
|
+
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
748
|
+
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
749
|
+
const mode = "public";
|
|
750
|
+
return async function handleInboundMessage(msg, turn) {
|
|
751
|
+
const allowedChats = config.allowedChats ?? [];
|
|
752
|
+
const action = decideChannelAction(msg, {
|
|
753
|
+
allowedChats,
|
|
754
|
+
mutedChats,
|
|
755
|
+
muteRegex: config.muteRegex,
|
|
756
|
+
unmuteRegex: config.unmuteRegex
|
|
757
|
+
});
|
|
758
|
+
if (action === "mute") {
|
|
759
|
+
mutedChats.add(msg.chatId);
|
|
760
|
+
if (config.muteReply) await turn.reply.sendGateReply(msg.chatId, config.muteReply);
|
|
761
|
+
return { action: "mute" };
|
|
637
762
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
763
|
+
if (action === "unmute") {
|
|
764
|
+
mutedChats.delete(msg.chatId);
|
|
765
|
+
if (config.unmuteReply) await turn.reply.sendGateReply(msg.chatId, config.unmuteReply);
|
|
766
|
+
return { action: "unmute" };
|
|
767
|
+
}
|
|
768
|
+
if (action === "ignore") {
|
|
769
|
+
return { action: "ignore" };
|
|
770
|
+
}
|
|
771
|
+
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
772
|
+
return { action: "rate-limited" };
|
|
773
|
+
}
|
|
774
|
+
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
775
|
+
const conversationId = turn.conversationId ?? msg.chatId;
|
|
776
|
+
if (turn.intercept && await turn.intercept(sender)) {
|
|
777
|
+
return { action: "intercepted" };
|
|
778
|
+
}
|
|
779
|
+
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
780
|
+
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
781
|
+
const priorTurns = config.history ? await config.history.store.load(
|
|
782
|
+
conversationId,
|
|
783
|
+
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
784
|
+
) : [];
|
|
785
|
+
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
786
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
787
|
+
const { system } = await prepareChat({
|
|
788
|
+
store: deps.store,
|
|
789
|
+
prompts: deps.prompts,
|
|
790
|
+
mode,
|
|
791
|
+
messages,
|
|
792
|
+
personaLayer,
|
|
793
|
+
channelHint: config.channelHint,
|
|
794
|
+
buckets
|
|
795
|
+
});
|
|
796
|
+
const { content } = await answerOnce({
|
|
797
|
+
answerFn: config.answerFn,
|
|
798
|
+
client: deps.client,
|
|
799
|
+
system,
|
|
800
|
+
messages,
|
|
801
|
+
mode,
|
|
802
|
+
sender,
|
|
803
|
+
conversationId,
|
|
804
|
+
model: config.model
|
|
805
|
+
});
|
|
806
|
+
if (!content) {
|
|
807
|
+
return { action: "ignore" };
|
|
808
|
+
}
|
|
809
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
810
|
+
await turn.reply.sendAnswer(msg.chatId, content);
|
|
811
|
+
return { action: "reply", content };
|
|
641
812
|
};
|
|
642
813
|
}
|
|
643
|
-
function underReplyRateLimit(msg, limiters) {
|
|
644
|
-
return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
|
|
645
|
-
}
|
|
646
|
-
function isFromAnySession(id, registry) {
|
|
647
|
-
if (!id) return false;
|
|
648
|
-
for (const ids of registry.values()) {
|
|
649
|
-
if (ids.includes(id)) return true;
|
|
650
|
-
}
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
function isEffectivelyFromSelf(msg, registry) {
|
|
654
|
-
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
655
|
-
}
|
|
656
814
|
|
|
657
815
|
// src/channels/whatsapp/inbound.ts
|
|
658
816
|
function isGroupJid(jid) {
|
|
@@ -669,11 +827,6 @@ function jidToPhoneNumber(jid) {
|
|
|
669
827
|
const bare = bareUser(jid);
|
|
670
828
|
return bare.startsWith("+") ? bare : `+${bare}`;
|
|
671
829
|
}
|
|
672
|
-
function normalizeJid(jid) {
|
|
673
|
-
const [user = "", domain] = jid.split("@");
|
|
674
|
-
const withoutDevice = user.split(":")[0] ?? user;
|
|
675
|
-
return domain ? `${withoutDevice}@${domain}` : withoutDevice;
|
|
676
|
-
}
|
|
677
830
|
async function senderPhoneFor(sock, message, chatJid) {
|
|
678
831
|
const key = message.key;
|
|
679
832
|
let jid = [key.participantPn, key.senderPn, key.participant, key.remoteJidAlt, chatJid].find(
|
|
@@ -701,6 +854,11 @@ function extractText(message) {
|
|
|
701
854
|
if (!content) return "";
|
|
702
855
|
return content.conversation ?? content.extendedTextMessage?.text ?? content.imageMessage?.caption ?? content.videoMessage?.caption ?? content.documentMessage?.caption ?? "";
|
|
703
856
|
}
|
|
857
|
+
function normalizeJid(jid) {
|
|
858
|
+
const [user = "", domain] = jid.split("@");
|
|
859
|
+
const withoutDevice = user.split(":")[0] ?? user;
|
|
860
|
+
return domain ? `${withoutDevice}@${domain}` : withoutDevice;
|
|
861
|
+
}
|
|
704
862
|
function stripOwnMentions(text, ownIds) {
|
|
705
863
|
const ownUsers = new Set(ownIds.map(bareUser).filter(Boolean));
|
|
706
864
|
if (!text || ownUsers.size === 0) return text;
|
|
@@ -714,109 +872,183 @@ function stripOwnMentions(text, ownIds) {
|
|
|
714
872
|
const cleaned = stripped.trim();
|
|
715
873
|
return cleaned ? cleaned : text;
|
|
716
874
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
875
|
+
function resolveWaMessage(event, registry) {
|
|
876
|
+
const { sessionId, sock, message } = event;
|
|
877
|
+
const chatId = message.key.remoteJid ?? "";
|
|
878
|
+
const rawText = extractText(message);
|
|
879
|
+
const context = messageContext(message);
|
|
880
|
+
const senderId = message.key.participant ?? chatId;
|
|
881
|
+
const ownIds = [sock.user?.id, sock.user?.lid].filter((id) => Boolean(id)).map(normalizeJid);
|
|
882
|
+
registry.set(sessionId, ownIds);
|
|
883
|
+
const text = stripOwnMentions(rawText, ownIds);
|
|
884
|
+
const fromBot = isEffectivelyFromSelf(
|
|
885
|
+
{ fromBot: message.key.fromMe ?? false, senderId: normalizeJid(senderId) },
|
|
886
|
+
registry
|
|
887
|
+
);
|
|
888
|
+
const msg = {
|
|
889
|
+
chatId,
|
|
890
|
+
senderId,
|
|
891
|
+
text,
|
|
892
|
+
isDirectMessage: !isGroupJid(chatId),
|
|
893
|
+
mentionsBot: context.mentionedJids.some((jid) => ownIds.some((own) => jidsMatch(jid, own))),
|
|
894
|
+
isReplyToBot: ownIds.some((own) => jidsMatch(context.quotedParticipantJid, own)),
|
|
895
|
+
fromBot
|
|
896
|
+
};
|
|
897
|
+
return { msg, ownIds };
|
|
726
898
|
}
|
|
727
899
|
function createWhatsAppInboundHandler(config) {
|
|
728
|
-
const
|
|
729
|
-
const
|
|
730
|
-
const
|
|
731
|
-
const
|
|
732
|
-
const
|
|
733
|
-
const
|
|
900
|
+
const loggedUnallowedChats = /* @__PURE__ */ new Set();
|
|
901
|
+
const allowedChats = config.allowedChats ?? [];
|
|
902
|
+
const personaResolver = config.personaResolver;
|
|
903
|
+
const images = config.images;
|
|
904
|
+
const logger = config.logger ?? createConsoleLogger();
|
|
905
|
+
const pipeline = createInboundPipeline(
|
|
906
|
+
{ client: config.client, store: config.store, prompts: config.prompts },
|
|
907
|
+
{
|
|
908
|
+
answerFn: config.answerFn,
|
|
909
|
+
bucketsFor: config.bucketsFor,
|
|
910
|
+
model: config.model,
|
|
911
|
+
channelHint: config.channelHint,
|
|
912
|
+
personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
|
|
913
|
+
history: config.history,
|
|
914
|
+
allowedChats,
|
|
915
|
+
muteRegex: config.muteRegex,
|
|
916
|
+
unmuteRegex: config.unmuteRegex,
|
|
917
|
+
muteReply: config.muteReply,
|
|
918
|
+
unmuteReply: config.unmuteReply,
|
|
919
|
+
dmRateLimit: config.dmRateLimit,
|
|
920
|
+
groupRateLimit: config.groupRateLimit,
|
|
921
|
+
now: config.now
|
|
922
|
+
}
|
|
923
|
+
);
|
|
734
924
|
return async function handleWhatsAppMessage(event) {
|
|
735
925
|
const { sessionId, sock, message } = event;
|
|
736
926
|
try {
|
|
737
927
|
const chatId = message.key.remoteJid ?? "";
|
|
738
928
|
if (!chatId || chatId === "status@broadcast") return;
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
config.registry
|
|
748
|
-
);
|
|
749
|
-
const msg = {
|
|
750
|
-
chatId,
|
|
751
|
-
senderId,
|
|
752
|
-
text,
|
|
753
|
-
isDirectMessage: !isGroupJid(chatId),
|
|
754
|
-
mentionsBot: context.mentionedJids.some((jid) => ownIds.some((own) => jidsMatch(jid, own))),
|
|
755
|
-
isReplyToBot: ownIds.some((own) => jidsMatch(context.quotedParticipantJid, own)),
|
|
756
|
-
fromBot
|
|
757
|
-
};
|
|
758
|
-
const action = decideChannelAction(msg, {
|
|
759
|
-
allowedChats: config.allowedChats ?? [],
|
|
760
|
-
mutedChats,
|
|
761
|
-
muteRegex: config.muteRegex,
|
|
762
|
-
unmuteRegex: config.unmuteRegex
|
|
763
|
-
});
|
|
764
|
-
if (action === "mute") {
|
|
765
|
-
mutedChats.add(chatId);
|
|
766
|
-
if (config.muteReply) await sock.sendMessage(chatId, { text: config.muteReply });
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
if (action === "unmute") {
|
|
770
|
-
mutedChats.delete(chatId);
|
|
771
|
-
if (config.unmuteReply) await sock.sendMessage(chatId, { text: config.unmuteReply });
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
if (action !== "reply") return;
|
|
775
|
-
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
776
|
-
return;
|
|
929
|
+
const { msg } = resolveWaMessage(event, config.registry);
|
|
930
|
+
const text = msg.text;
|
|
931
|
+
if (isBlockedByAllowlist(msg, { allowedChats })) {
|
|
932
|
+
const dedupKey = `${sessionId}:${chatId}`;
|
|
933
|
+
if (!loggedUnallowedChats.has(dedupKey)) {
|
|
934
|
+
loggedUnallowedChats.add(dedupKey);
|
|
935
|
+
logger.warn(`WhatsApp[${sessionId}]: skipped group ${chatId} - not in allowedChats`);
|
|
936
|
+
}
|
|
777
937
|
}
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
938
|
+
const reply = {
|
|
939
|
+
sendAnswer: async (targetChatId, replyText2) => {
|
|
940
|
+
await sock.sendMessage(targetChatId, { text: replyText2 }, { quoted: message });
|
|
941
|
+
},
|
|
942
|
+
sendGateReply: async (targetChatId, replyText2) => {
|
|
943
|
+
await sock.sendMessage(targetChatId, { text: replyText2 });
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
await pipeline(msg, {
|
|
947
|
+
reply,
|
|
948
|
+
conversationId: chatId,
|
|
949
|
+
sender: () => senderPhoneFor(sock, message, chatId),
|
|
950
|
+
intercept: images ? (senderPhone) => images({
|
|
781
951
|
sock,
|
|
782
952
|
message,
|
|
783
953
|
chatId,
|
|
784
954
|
senderId: senderPhone,
|
|
785
955
|
text,
|
|
786
956
|
hasPhoto: Boolean(message.message?.imageMessage)
|
|
787
|
-
})
|
|
788
|
-
if (handled) return;
|
|
789
|
-
}
|
|
790
|
-
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
791
|
-
const buckets = await resolveBuckets({
|
|
792
|
-
mode: "public",
|
|
793
|
-
sender: senderPhone,
|
|
794
|
-
bucketsFor: config.bucketsFor
|
|
795
|
-
});
|
|
796
|
-
const messages = [{ role: "user", content: text }];
|
|
797
|
-
const { system } = await prepareChat({
|
|
798
|
-
store: config.store,
|
|
799
|
-
prompts: config.prompts,
|
|
800
|
-
mode: "public",
|
|
801
|
-
messages,
|
|
802
|
-
personaLayer,
|
|
803
|
-
channelHint: config.channelHint,
|
|
804
|
-
buckets
|
|
957
|
+
}) : void 0
|
|
805
958
|
});
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
959
|
+
} catch (error) {
|
|
960
|
+
logger.warn(`WhatsApp[${sessionId}]: inbound message handling failed:`, error);
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/channels/whatsapp/router.ts
|
|
966
|
+
async function logDetectorError(config, logger, name, error) {
|
|
967
|
+
if (!config.onDetectorError) {
|
|
968
|
+
logger.warn(`WhatsApp router: detector "${name}" failed:`, error);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
await config.onDetectorError(name, error);
|
|
973
|
+
} catch (callbackError) {
|
|
974
|
+
logger.warn(`WhatsApp router: onDetectorError itself failed:`, callbackError);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function isChatIneligible(msg, allowedChats) {
|
|
978
|
+
return !msg.isDirectMessage && allowedChats.length > 0 && !allowedChats.includes(msg.chatId);
|
|
979
|
+
}
|
|
980
|
+
function fireParallelDetector(config, logger, detector, ctx) {
|
|
981
|
+
void (async () => {
|
|
982
|
+
try {
|
|
983
|
+
if (await detector.test(ctx)) {
|
|
984
|
+
await detector.handle(ctx);
|
|
985
|
+
}
|
|
986
|
+
} catch (error) {
|
|
987
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
988
|
+
}
|
|
989
|
+
})();
|
|
990
|
+
}
|
|
991
|
+
function createWhatsAppMessageRouter(config) {
|
|
992
|
+
const allowedChats = config.allowedChats ?? [];
|
|
993
|
+
const mutedChats = config.mutedChats ?? /* @__PURE__ */ new Set();
|
|
994
|
+
const logger = config.logger ?? createConsoleLogger();
|
|
995
|
+
const replaceDetectors = config.detectors.filter(
|
|
996
|
+
(d) => d.mode === "replace"
|
|
997
|
+
);
|
|
998
|
+
const parallelDetectors = config.detectors.filter(
|
|
999
|
+
(d) => d.mode === "parallel"
|
|
1000
|
+
);
|
|
1001
|
+
return async function routeWhatsAppMessage(event) {
|
|
1002
|
+
const { sock, message } = event;
|
|
1003
|
+
let ctx;
|
|
1004
|
+
try {
|
|
1005
|
+
const chatId = message.key.remoteJid ?? "";
|
|
1006
|
+
if (!chatId || chatId === "status@broadcast") return;
|
|
1007
|
+
const { msg, ownIds } = resolveWaMessage(event, config.registry);
|
|
1008
|
+
if (msg.fromBot || isChatIneligible(msg, allowedChats)) return;
|
|
1009
|
+
const action = decideChannelAction(msg, {
|
|
1010
|
+
allowedChats,
|
|
1011
|
+
mutedChats,
|
|
1012
|
+
muteRegex: config.muteRegex,
|
|
1013
|
+
unmuteRegex: config.unmuteRegex
|
|
814
1014
|
});
|
|
815
|
-
|
|
816
|
-
|
|
1015
|
+
ctx = {
|
|
1016
|
+
event,
|
|
1017
|
+
sock,
|
|
1018
|
+
message,
|
|
1019
|
+
msg,
|
|
1020
|
+
ownIds,
|
|
1021
|
+
senderId: msg.senderId,
|
|
1022
|
+
text: msg.text,
|
|
1023
|
+
action
|
|
1024
|
+
};
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
await logDetectorError(config, logger, "resolve", error);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
for (const detector of parallelDetectors) {
|
|
1030
|
+
fireParallelDetector(config, logger, detector, ctx);
|
|
1031
|
+
}
|
|
1032
|
+
for (const detector of replaceDetectors) {
|
|
1033
|
+
let matched;
|
|
1034
|
+
try {
|
|
1035
|
+
matched = await detector.test(ctx);
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
1038
|
+
continue;
|
|
817
1039
|
}
|
|
1040
|
+
if (!matched) continue;
|
|
1041
|
+
try {
|
|
1042
|
+
await detector.handle(ctx);
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
await logDetectorError(config, logger, detector.name, error);
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
await config.fallback(event);
|
|
818
1050
|
} catch (error) {
|
|
819
|
-
|
|
1051
|
+
await logDetectorError(config, logger, "fallback", error);
|
|
820
1052
|
}
|
|
821
1053
|
};
|
|
822
1054
|
}
|
|
@@ -830,6 +1062,7 @@ export {
|
|
|
830
1062
|
createWhatsAppChannel,
|
|
831
1063
|
createWhatsAppImageHandler,
|
|
832
1064
|
createWhatsAppInboundHandler,
|
|
1065
|
+
createWhatsAppMessageRouter,
|
|
833
1066
|
decrypt,
|
|
834
1067
|
drawSubject,
|
|
835
1068
|
encrypt,
|
|
@@ -841,6 +1074,7 @@ export {
|
|
|
841
1074
|
loadBaileys,
|
|
842
1075
|
messageContext,
|
|
843
1076
|
reconnectDelayMs,
|
|
1077
|
+
resolveWaMessage,
|
|
844
1078
|
senderNameFor,
|
|
845
1079
|
senderPhoneFor,
|
|
846
1080
|
shutdownWaSessions,
|