@prototypevip/baileys 0.1.0 → 0.1.2

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.
Files changed (62) hide show
  1. package/lib/Defaults/index.js +3 -3
  2. package/lib/Defaults/index.js.bak-1788476907801 +187 -0
  3. package/lib/Defaults/index.js.bak-1788479166288 +187 -0
  4. package/lib/Socket/groups.js.bak-1788476973954 +475 -0
  5. package/lib/Socket/index.js +42 -126
  6. package/lib/Socket/index.js.bak-1788479281092 +38 -0
  7. package/lib/Socket/messages-send.js +88 -51
  8. package/lib/Store/prototype-store.js +34 -22
  9. package/lib/Store/prototype-store.js.bak-1788477006895 +983 -0
  10. package/lib/Utils/index.js +2 -0
  11. package/lib/Utils/integrity-guard.js +281 -0
  12. package/lib/Utils/interactive-message-handler.js +28 -8
  13. package/lib/Utils/interactive-message-handler.js.bak-1788476898661 +478 -0
  14. package/lib/Utils/socket-guard.js +154 -0
  15. package/package.json +2 -2
  16. package/lib/AntiBan/antiban.js +0 -922
  17. package/lib/AntiBan/banRecoveryOrchestrator.js +0 -284
  18. package/lib/AntiBan/cli.js +0 -177
  19. package/lib/AntiBan/contactGraph.js +0 -236
  20. package/lib/AntiBan/contentVariator.js +0 -150
  21. package/lib/AntiBan/credsSnapshot.js +0 -121
  22. package/lib/AntiBan/deliveryTracker.js +0 -107
  23. package/lib/AntiBan/deviceFingerprint.js +0 -106
  24. package/lib/AntiBan/fleetEventStore.js +0 -157
  25. package/lib/AntiBan/groupOperationGuard.js +0 -116
  26. package/lib/AntiBan/health.js +0 -207
  27. package/lib/AntiBan/humanEntropy.js +0 -337
  28. package/lib/AntiBan/index.js +0 -77
  29. package/lib/AntiBan/instanceCoordinator.js +0 -175
  30. package/lib/AntiBan/jidCanonicalizer.js +0 -256
  31. package/lib/AntiBan/jidCircuitBreaker.js +0 -186
  32. package/lib/AntiBan/legitimacySignalInjector.js +0 -231
  33. package/lib/AntiBan/lidFirstResolver.js +0 -174
  34. package/lib/AntiBan/lidResolver.js +0 -324
  35. package/lib/AntiBan/messageQueue.js +0 -187
  36. package/lib/AntiBan/messageRecovery.js +0 -283
  37. package/lib/AntiBan/messageTypeRegistry.js +0 -369
  38. package/lib/AntiBan/observability.js +0 -145
  39. package/lib/AntiBan/patch.js +0 -302
  40. package/lib/AntiBan/persist.js +0 -87
  41. package/lib/AntiBan/presenceChoreographer.js +0 -430
  42. package/lib/AntiBan/presets.js +0 -105
  43. package/lib/AntiBan/profiles.js +0 -31
  44. package/lib/AntiBan/proxyRotator.js +0 -306
  45. package/lib/AntiBan/rateLimiter.js +0 -261
  46. package/lib/AntiBan/readReceiptVariance.js +0 -88
  47. package/lib/AntiBan/reconnectThrottle.js +0 -180
  48. package/lib/AntiBan/replyRatio.js +0 -161
  49. package/lib/AntiBan/reputationVoucher.js +0 -373
  50. package/lib/AntiBan/retryReason.js +0 -91
  51. package/lib/AntiBan/retryTracker.js +0 -172
  52. package/lib/AntiBan/scheduler.js +0 -111
  53. package/lib/AntiBan/sessionFingerprint.js +0 -248
  54. package/lib/AntiBan/sessionStability.js +0 -330
  55. package/lib/AntiBan/stateAdapter.js +0 -73
  56. package/lib/AntiBan/stateExport.js +0 -152
  57. package/lib/AntiBan/stealthConnect.js +0 -130
  58. package/lib/AntiBan/timelockGuard.js +0 -181
  59. package/lib/AntiBan/topologyThrottler.js +0 -339
  60. package/lib/AntiBan/warmup.js +0 -140
  61. package/lib/AntiBan/webhooks.js +0 -80
  62. package/lib/AntiBan/wrapper.js +0 -456
@@ -0,0 +1,983 @@
1
+ import { serialize, deserialize } from "v8";
2
+ import { proto } from "../../WAProto/index.js";
3
+ import {
4
+ jidNormalizedUser,
5
+ isPnUser,
6
+ isLidUser,
7
+ isHostedPnUser,
8
+ isHostedLidUser,
9
+ } from "../WABinary/index.js";
10
+ import { makeOrderedDictionary } from "./make-ordered-dictionary.js";
11
+ // نسخه نظيفه من الداله: بس clone سطحي للرساله، من غير اي شرط مخفي
12
+ function cloneM(obj) {
13
+ const out = {};
14
+ if (!obj || typeof obj !== "object") return obj;
15
+ const keys = Object.keys(obj);
16
+ for (let i = 0; i < keys.length; i++) {
17
+ const k = keys[i];
18
+ try {
19
+ out[k] = obj[k];
20
+ } catch (e) {
21
+ // silent
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+ import {
27
+ updateMessageWithReaction,
28
+ updateMessageWithReceipt,
29
+ } from "../Utils/index.js";
30
+ import { makeMutex } from "../Utils/make-mutex.js";
31
+ let sig = false;
32
+
33
+ const resolveJID = (jid, lidMapping) => {
34
+ if (!jid || typeof jid !== "string") return jid;
35
+ if (isPnUser(jid) || isHostedPnUser(jid)) {
36
+ return jidNormalizedUser(jid);
37
+ }
38
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
39
+ const pn = lidMapping?.getPNForLIDSync(jid);
40
+ if (pn) return jidNormalizedUser(pn);
41
+ }
42
+ return jidNormalizedUser(jid);
43
+ };
44
+
45
+ const resolveJIDAsync = async (jid, lidMapping) => {
46
+ if (!jid || typeof jid !== "string") return jid;
47
+ if (isPnUser(jid) || isHostedPnUser(jid)) {
48
+ return jidNormalizedUser(jid);
49
+ }
50
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
51
+ const pn =
52
+ lidMapping?.getPNForLIDSync(jid) || (await lidMapping?.getPNForLID(jid));
53
+ if (pn) return jidNormalizedUser(pn);
54
+ }
55
+ return jidNormalizedUser(jid);
56
+ };
57
+
58
+ const getMessageKey = (msg, messages = {}, lidMapping) => {
59
+ const id = msg.key.id;
60
+ const participant = msg.key.participant
61
+ ? resolveJID(msg.key.participant, lidMapping)
62
+ : msg.key.fromMe
63
+ ? "me"
64
+ : "";
65
+ if (
66
+ messages[id] &&
67
+ (messages[id].key.participant
68
+ ? resolveJID(messages[id].key.participant, lidMapping)
69
+ : messages[id].key.fromMe
70
+ ? "me"
71
+ : "") !== participant
72
+ ) {
73
+ return `${participant}:${id}`;
74
+ }
75
+ return id;
76
+ };
77
+
78
+ const getMessageKeyFromKey = (key, lidMapping) => {
79
+ const id = key.id;
80
+ const participant = key.participant
81
+ ? resolveJID(key.participant, lidMapping)
82
+ : key.fromMe
83
+ ? "me"
84
+ : "";
85
+ return participant ? `${participant}:${id}` : id;
86
+ };
87
+
88
+ const findMessageByKey = (messages, key, lidMapping) => {
89
+ const msgKey = getMessageKeyFromKey(key, lidMapping);
90
+ if (messages[msgKey]) return messages[msgKey];
91
+ return messages[key.id];
92
+ };
93
+
94
+ const safeDeserialize = (data) => {
95
+ if (!data) return null;
96
+ if (typeof data === "string") {
97
+ try {
98
+ return JSON.parse(data);
99
+ } catch {
100
+ return data;
101
+ }
102
+ }
103
+ try {
104
+ return deserialize(data);
105
+ } catch {
106
+ try {
107
+ return JSON.parse(data.toString());
108
+ } catch {
109
+ return data;
110
+ }
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Creates a Prototype Store instance.
116
+ */
117
+ export const makePrototypeStore = (config = {}) => {
118
+ const {
119
+ db: Database,
120
+ file,
121
+ retentionDays = 2,
122
+ maxMessagesPerChat = 50000,
123
+ } = config;
124
+ const db = Database && file ? new Database(file) : null;
125
+ const dbMutex = makeMutex();
126
+
127
+ if (db) {
128
+ db.exec(`
129
+ CREATE TABLE IF NOT EXISTS messages (
130
+ id TEXT,
131
+ remoteJid TEXT,
132
+ fromMe INTEGER,
133
+ participant TEXT,
134
+ data BLOB,
135
+ timestamp INTEGER,
136
+ PRIMARY KEY (id, remoteJid, fromMe)
137
+ );
138
+
139
+ CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages (timestamp);
140
+ CREATE INDEX IF NOT EXISTS idx_messages_jid ON messages (remoteJid);
141
+
142
+ CREATE TABLE IF NOT EXISTS contacts (
143
+ jid TEXT PRIMARY KEY,
144
+ data BLOB
145
+ );
146
+
147
+ CREATE TABLE IF NOT EXISTS receipts (
148
+ id TEXT,
149
+ remoteJid TEXT,
150
+ fromMe INTEGER,
151
+ data BLOB,
152
+ PRIMARY KEY (id, remoteJid, fromMe)
153
+ );
154
+ `);
155
+ const tableInfo = db.prepare("PRAGMA table_info(messages)").all();
156
+ if (!tableInfo.some((col) => col.name === "timestamp")) {
157
+ try {
158
+ db.exec("ALTER TABLE messages ADD COLUMN timestamp INTEGER");
159
+ db.exec(
160
+ "CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages (timestamp)",
161
+ );
162
+ } catch {
163
+ console.error("PrototypeStore Timestamp Migration Error");
164
+ }
165
+ }
166
+ if (!tableInfo.some((col) => col.name === "participant")) {
167
+ try {
168
+ db.exec("ALTER TABLE messages ADD COLUMN participant TEXT");
169
+ } catch {
170
+ console.error("PrototypeStore Participant Migration Error");
171
+ }
172
+ }
173
+ }
174
+ const pendingUpdates = new Map();
175
+ const stmts = {};
176
+ if (db) {
177
+ stmts.insertMsg = db.prepare(
178
+ "INSERT OR REPLACE INTO messages (id, remoteJid, fromMe, participant, data, timestamp) VALUES (?, ?, ?, ?, ?, ?)",
179
+ );
180
+ stmts.insertContact = db.prepare(
181
+ "INSERT OR REPLACE INTO contacts (jid, data) VALUES (?, ?)",
182
+ );
183
+ stmts.insertReceipt = db.prepare(
184
+ "INSERT OR REPLACE INTO receipts (id, remoteJid, fromMe, data) VALUES (?, ?, ?, ?)",
185
+ );
186
+ stmts.deleteMessages = db.prepare(
187
+ "DELETE FROM messages WHERE remoteJid = ?",
188
+ );
189
+ stmts.deleteOneMsg = db.prepare(
190
+ "DELETE FROM messages WHERE id = ? AND remoteJid = ? AND fromMe = ?",
191
+ );
192
+ stmts.deleteOldMessagesByTime = db.prepare(
193
+ "DELETE FROM messages WHERE timestamp < ?",
194
+ );
195
+ stmts.getDistinctJids = db.prepare(
196
+ "SELECT DISTINCT remoteJid FROM messages",
197
+ );
198
+ stmts.limitMessagesPerChat = db.prepare(
199
+ `DELETE FROM messages WHERE remoteJid = ? AND id NOT IN (SELECT id FROM messages WHERE remoteJid = ? ORDER BY timestamp DESC LIMIT ?)`,
200
+ );
201
+ }
202
+
203
+ const _flush = () => {
204
+ if (!db || pendingUpdates.size === 0) return;
205
+
206
+ const updates = Array.from(pendingUpdates.values());
207
+
208
+ try {
209
+ db.transaction(() => {
210
+ for (const update of updates) {
211
+ try {
212
+ switch (update.type) {
213
+ case "delete":
214
+ if (update.table === "messages") {
215
+ if (update.id) {
216
+ stmts.deleteMessages.run(update.id);
217
+ } else if (update.key) {
218
+ stmts.deleteOneMsg.run(
219
+ update.key.id,
220
+ update.key.remoteJid,
221
+ update.key.fromMe ? 1 : 0,
222
+ );
223
+ }
224
+ }
225
+ break;
226
+ case "message": {
227
+ const m = update.data;
228
+ const timestamp = m.messageTimestamp
229
+ ? typeof m.messageTimestamp === "number"
230
+ ? m.messageTimestamp
231
+ : m.messageTimestamp.toNumber?.() || m.messageTimestamp
232
+ : Math.floor(Date.now() / 1000);
233
+ stmts.insertMsg.run(
234
+ m.key.id || "",
235
+ m.key.remoteJid || "",
236
+ m.key.fromMe ? 1 : 0,
237
+ m.key.participant || "",
238
+ serialize(m),
239
+ timestamp || 0,
240
+ );
241
+ break;
242
+ }
243
+ case "contact": {
244
+ stmts.insertContact.run(update.data.id, serialize(update.data));
245
+ break;
246
+ }
247
+ case "receipt": {
248
+ const r = update.data;
249
+ stmts.insertReceipt.run(
250
+ r.key.id,
251
+ r.key.remoteJid,
252
+ r.key.fromMe ? 1 : 0,
253
+ serialize(r.receipt),
254
+ );
255
+ break;
256
+ }
257
+ }
258
+ } catch (err) {
259
+ const code = err?.code || err?.message || "";
260
+ if (String(code).includes("READONLY") || String(code).includes("SQLITE_READONLY")) {
261
+ console.error("PrototypeStore Transaction Step Error (readonly DB - check file path/permissions/volume):", err.message || err);
262
+ } else {
263
+ console.error("PrototypeStore Transaction Step Error:", err);
264
+ }
265
+ }
266
+ }
267
+ })();
268
+ pendingUpdates.clear();
269
+ } catch (e) {
270
+ console.error("PrototypeStore Flush Error:", e);
271
+ }
272
+ };
273
+
274
+ const flush = async () => {
275
+ return await dbMutex.mutex(async () => _flush());
276
+ };
277
+
278
+ const _clean = () => {
279
+ if (!db) return;
280
+ try {
281
+ const cutoff =
282
+ Math.floor(Date.now() / 1000) - retentionDays * 24 * 60 * 60;
283
+ stmts.deleteOldMessagesByTime.run(cutoff);
284
+
285
+ const chats = stmts.getDistinctJids.all();
286
+ for (const chat of chats) {
287
+ stmts.limitMessagesPerChat.run(
288
+ chat.remoteJid,
289
+ chat.remoteJid,
290
+ maxMessagesPerChat,
291
+ );
292
+ }
293
+
294
+ db.exec("VACUUM");
295
+ Object.keys(chatsCache).forEach((k) => delete chatsCache[k]);
296
+ Object.keys(contactsCache).forEach((k) => delete contactsCache[k]);
297
+ Object.keys(groupsCache).forEach((k) => delete groupsCache[k]);
298
+ Object.keys(presencesCache).forEach((k) => delete presencesCache[k]);
299
+ } catch {
300
+ console.error("PrototypeStore Clean Error");
301
+ }
302
+ };
303
+
304
+ const clean = async () => {
305
+ return await dbMutex.mutex(async () => _clean());
306
+ };
307
+
308
+ let flushInterval;
309
+ let cleanInterval;
310
+ let handleExit;
311
+ if (db) {
312
+ flushInterval = setInterval(flush, 5000);
313
+ cleanInterval = setInterval(clean, 12 * 60 * 60 * 1000);
314
+ handleExit = () => {
315
+ _flush();
316
+ try {
317
+ db.close();
318
+ } catch {
319
+ // Ignore close errors
320
+ }
321
+ };
322
+ if (!sig) {
323
+ sig = true;
324
+ process.once("SIGINT", handleExit);
325
+ process.once("SIGTERM", handleExit);
326
+ }
327
+ }
328
+
329
+ const chatsCache = {};
330
+ const contactsCache = {};
331
+ const groupsCache = {};
332
+
333
+ const contactsUpsert = async (newContacts) => {
334
+ if (!Array.isArray(newContacts)) return;
335
+ for (const contact of newContacts) {
336
+ const id = await resolveJIDAsync(
337
+ contact.id,
338
+ store.signalRepository?.lidMapping,
339
+ );
340
+ if (!id || id === "status@broadcast") continue;
341
+
342
+ const current = contactsCache[id] || {};
343
+ contactsCache[id] = {
344
+ ...current,
345
+ ...contact,
346
+ id,
347
+ name:
348
+ contact.notify ||
349
+ contact.name ||
350
+ current.name ||
351
+ current.notify ||
352
+ "",
353
+ };
354
+ }
355
+ };
356
+
357
+ const presencesCache = {};
358
+
359
+
360
+ const safeAssignChat = (existing, incoming) => {
361
+ if (!existing || !incoming) return existing;
362
+ const savedMessages = existing.messages;
363
+ Object.assign(existing, incoming, { isChats: true });
364
+ // Never let chat events wipe the ordered messages store
365
+ if (
366
+ !existing.messages ||
367
+ typeof existing.messages.upsert !== "function"
368
+ ) {
369
+ existing.messages = savedMessages || makeMessagesStore();
370
+ }
371
+ if (!existing.id) existing.id = incoming.id || existing.id;
372
+ return existing;
373
+ };
374
+
375
+ const makeMessagesStore = () =>
376
+ new Proxy(
377
+ makeOrderedDictionary((m) => {
378
+ const participant = m.key.participant
379
+ ? resolveJID(m.key.participant, store.signalRepository?.lidMapping)
380
+ : m.key.fromMe
381
+ ? "me"
382
+ : "";
383
+ return participant ? `${participant}:${m.key.id}` : m.key.id;
384
+ }),
385
+ {
386
+ get(target, prop) {
387
+ if (prop in target) return target[prop];
388
+ return target.get(prop);
389
+ },
390
+ set(target, prop, value) {
391
+ target.upsert(value);
392
+ return true;
393
+ },
394
+ deleteProperty(target, prop) {
395
+ const item = target.get(prop);
396
+ if (item) return target.remove(item);
397
+ return false;
398
+ },
399
+ },
400
+ );
401
+
402
+ const store = {
403
+ signalRepository: config.signalRepository,
404
+ chats: new Proxy(chatsCache, {
405
+ get(target, chatId) {
406
+ if (typeof chatId !== "string") return target[chatId];
407
+ const jid = resolveJID(chatId, store.signalRepository?.lidMapping);
408
+ if (!target[jid]) {
409
+ target[jid] = {
410
+ id: jid,
411
+ messages: makeMessagesStore(),
412
+ get isGroup() {
413
+ return jid.endsWith("@g.us");
414
+ },
415
+ };
416
+ } else if (
417
+ !target[jid].messages ||
418
+ typeof target[jid].messages.upsert !== "function"
419
+ ) {
420
+ // Recover if messages was overwritten by Object.assign / chat updates
421
+ target[jid].messages = makeMessagesStore();
422
+ }
423
+ return target[jid];
424
+ },
425
+ set(target, chatId, value) {
426
+ const jid = resolveJID(chatId, store.signalRepository?.lidMapping);
427
+ target[jid] = value;
428
+ return true;
429
+ },
430
+ }),
431
+
432
+ contacts: new Proxy(contactsCache, {
433
+ get(target, jid) {
434
+ if (typeof jid !== "string") return target[jid];
435
+ const normalizedJid = resolveJID(
436
+ jid,
437
+ store.signalRepository?.lidMapping,
438
+ );
439
+ if (target[normalizedJid]) return target[normalizedJid];
440
+ if (db) {
441
+ const row = db
442
+ .prepare("SELECT data FROM contacts WHERE jid = ?")
443
+ .get(normalizedJid);
444
+ if (row) {
445
+ const contact = safeDeserialize(row.data);
446
+ target[normalizedJid] = contact;
447
+ return contact;
448
+ }
449
+ }
450
+ return undefined;
451
+ },
452
+ }),
453
+
454
+ groups: new Proxy(groupsCache, {
455
+ get(target, jid) {
456
+ if (typeof jid !== "string") return target[jid];
457
+ return target[jid];
458
+ },
459
+ }),
460
+
461
+ presences: new Proxy(presencesCache, {
462
+ get(target, jid) {
463
+ if (typeof jid !== "string") return target[jid];
464
+ const normalizedJid = resolveJID(
465
+ jid,
466
+ store.signalRepository?.lidMapping,
467
+ );
468
+ if (!target[normalizedJid]) {
469
+ target[normalizedJid] = {};
470
+ }
471
+ return target[normalizedJid];
472
+ },
473
+ }),
474
+
475
+ state: { connection: "close" },
476
+ me: null,
477
+
478
+ clean,
479
+ flush,
480
+
481
+ dispose() {
482
+ if (flushInterval) clearInterval(flushInterval);
483
+ if (cleanInterval) clearInterval(cleanInterval);
484
+ if (handleExit) {
485
+ process.removeListener("SIGINT", handleExit);
486
+ process.removeListener("SIGTERM", handleExit);
487
+ }
488
+ if (this._ev && this._listeners) {
489
+ for (const evName in this._listeners) {
490
+ this._ev.off(evName, this._listeners[evName]);
491
+ }
492
+ }
493
+ this._listeners = {};
494
+ },
495
+
496
+ loadMessage(key) {
497
+ const id = typeof key === "string" ? key : key.id;
498
+ const remoteJid = key?.remoteJid;
499
+ const chatId = remoteJid
500
+ ? resolveJID(remoteJid, store.signalRepository?.lidMapping)
501
+ : null;
502
+
503
+ if (chatId) {
504
+ const msgKey = getMessageKeyFromKey(
505
+ key,
506
+ store.signalRepository?.lidMapping,
507
+ );
508
+ const msg = store.chats[chatId].messages.get(msgKey);
509
+ if (msg) return msg;
510
+
511
+ // Fallback search in memory
512
+ const msgInMemory = Array.from(
513
+ store.chats[chatId].messages.iterator(),
514
+ ).find((m) => m.key?.id === id);
515
+ if (msgInMemory) return msgInMemory;
516
+ }
517
+
518
+ // Global search fallback in memory
519
+ for (const cId in chatsCache) {
520
+ const chat = chatsCache[cId];
521
+ if (!chat.messages) continue;
522
+ const msg = Array.from(chat.messages.iterator()).find(
523
+ (m) => m.key?.id === id,
524
+ );
525
+ if (msg) return msg;
526
+ }
527
+
528
+ if (db) {
529
+ let query = "SELECT data FROM messages WHERE id = ?";
530
+ let params = [id];
531
+ if (chatId) {
532
+ query += " AND remoteJid = ?";
533
+ params.push(chatId);
534
+ }
535
+ const row = db.prepare(query).get(...params);
536
+ if (row) {
537
+ const msg = safeDeserialize(row.data);
538
+ const cId = resolveJID(
539
+ msg.key.remoteJid,
540
+ store.signalRepository?.lidMapping,
541
+ );
542
+
543
+ store.chats[cId].messages.upsert(msg);
544
+ return msg;
545
+ }
546
+ }
547
+ return undefined;
548
+ },
549
+
550
+ bind(ev) {
551
+ if (this._ev && this._listeners) {
552
+ for (const evName in this._listeners) {
553
+ this._ev.off(evName, this._listeners[evName]);
554
+ }
555
+ }
556
+ this._ev = ev;
557
+ this._listeners = {};
558
+
559
+ const listen = (name, fn) => {
560
+ this._listeners[name] = fn;
561
+ ev.on(name, fn);
562
+ };
563
+
564
+ const upsertContacts = async (contacts) => {
565
+ await contactsUpsert(contacts);
566
+ for (const contact of contacts) {
567
+ const id = await resolveJIDAsync(
568
+ contact.id,
569
+ store.signalRepository?.lidMapping,
570
+ );
571
+ if (!id || id === "status@broadcast") continue;
572
+
573
+ if (db)
574
+ pendingUpdates.set(`c:${id}`, {
575
+ type: "contact",
576
+ data: contactsCache[id],
577
+ });
578
+
579
+ // Ensure chat names are in sync
580
+ const chat = store.chats[id];
581
+ if (id.endsWith("@g.us")) {
582
+ chat.subject =
583
+ contactsCache[id].subject ||
584
+ contactsCache[id].name ||
585
+ chat.subject ||
586
+ "";
587
+ } else {
588
+ chat.name = contactsCache[id].name || chat.name || "";
589
+ }
590
+ }
591
+ };
592
+
593
+ // Order of registration for clean handling
594
+ listen("contacts.upsert", upsertContacts);
595
+ listen("contacts.set", ({ contacts }) => upsertContacts(contacts));
596
+ listen("contacts.update", upsertContacts);
597
+
598
+ listen("chats.upsert", async (chats) => {
599
+ for (const chat of chats) {
600
+ const id = await resolveJIDAsync(
601
+ chat.id,
602
+ store.signalRepository?.lidMapping,
603
+ );
604
+ if (!id || id === "status@broadcast") continue;
605
+
606
+ const existingChat = store.chats[id];
607
+ safeAssignChat(existingChat, chat);
608
+
609
+ if (id.endsWith("@g.us")) {
610
+ const current = groupsCache[id] || {};
611
+ const updated = {
612
+ ...current,
613
+ ...chat,
614
+ subject: chat.name || chat.subject || current.subject || "",
615
+ };
616
+ groupsCache[id] = updated;
617
+ } else {
618
+ const current = contactsCache[id] || {};
619
+ const updated = {
620
+ ...current,
621
+ ...chat,
622
+ name: chat.name || current.name || "",
623
+ };
624
+ contactsCache[id] = updated;
625
+ if (db)
626
+ pendingUpdates.set(`c:${id}`, {
627
+ type: "contact",
628
+ data: updated,
629
+ });
630
+ }
631
+ }
632
+ });
633
+
634
+ listen("message-receipt.update", async (updates) => {
635
+ for (const { key, receipt } of updates) {
636
+ if (!key || !receipt) continue;
637
+ const chatId = await resolveJIDAsync(
638
+ key.remoteJid,
639
+ store.signalRepository?.lidMapping,
640
+ );
641
+ const msg = findMessageByKey(
642
+ store.chats[chatId].messages,
643
+ key,
644
+ store.signalRepository?.lidMapping,
645
+ );
646
+ if (msg) {
647
+ updateMessageWithReceipt(msg, receipt);
648
+ if (db) {
649
+ const msgKey = getMessageKey(
650
+ msg,
651
+ store.chats[chatId].messages,
652
+ store.signalRepository?.lidMapping,
653
+ );
654
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
655
+ type: "message",
656
+ data: msg,
657
+ });
658
+ }
659
+ }
660
+ }
661
+ });
662
+
663
+ listen("presence.update", async ({ id, presences: update }) => {
664
+ const jid = await resolveJIDAsync(
665
+ id,
666
+ store.signalRepository?.lidMapping,
667
+ );
668
+ Object.assign(store.presences[jid], update);
669
+ });
670
+
671
+ listen("connection.update", (update) => {
672
+ Object.assign(this.state, update);
673
+ if (update.me) {
674
+ this.me = resolveJID(
675
+ update.me.id,
676
+ store.signalRepository?.lidMapping,
677
+ );
678
+ }
679
+ });
680
+
681
+ listen("chats.delete", async (jids) => {
682
+ for (const id of jids) {
683
+ const jid = await resolveJIDAsync(
684
+ id,
685
+ store.signalRepository?.lidMapping,
686
+ );
687
+ delete chatsCache[jid];
688
+ delete groupsCache[jid];
689
+ if (db) {
690
+ pendingUpdates.set(`del:m:${jid}`, {
691
+ type: "delete",
692
+ id: jid,
693
+ table: "messages",
694
+ });
695
+ }
696
+ }
697
+ });
698
+
699
+ listen(
700
+ "messaging-history.set",
701
+ async ({ messages, contacts: newContacts, chats: newChats }) => {
702
+ await upsertContacts(newContacts);
703
+
704
+ for (const msg of messages) {
705
+ const chatId = await resolveJIDAsync(
706
+ msg.key.remoteJid,
707
+ store.signalRepository?.lidMapping,
708
+ );
709
+ if (!chatId) continue;
710
+
711
+ store.chats[chatId].messages.upsert(msg);
712
+
713
+ if (db) {
714
+ const msgKey = getMessageKey(
715
+ msg,
716
+ {}, // passing empty since we know messages are upserted now
717
+ store.signalRepository?.lidMapping,
718
+ );
719
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
720
+ type: "message",
721
+ data: msg,
722
+ });
723
+ }
724
+ }
725
+
726
+ for (const chat of newChats) {
727
+ const id = await resolveJIDAsync(
728
+ chat.id,
729
+ store.signalRepository?.lidMapping,
730
+ );
731
+ const existingChat = store.chats[id];
732
+ safeAssignChat(existingChat, chat);
733
+ }
734
+ },
735
+ );
736
+
737
+ listen("messages.upsert", async ({ messages }) => {
738
+ for (let msg of messages) {
739
+ const rawChatId = msg?.key?.remoteJid;
740
+ if (!rawChatId) continue;
741
+ const chatId = await resolveJIDAsync(
742
+ rawChatId,
743
+ store.signalRepository?.lidMapping,
744
+ );
745
+ if (!chatId) continue;
746
+ const chat = store.chats[chatId];
747
+ if (!chat) continue;
748
+ if (!chat.messages || typeof chat.messages.upsert !== "function") {
749
+ chat.messages = makeMessagesStore();
750
+ }
751
+ chat.messages.upsert(cloneM(msg));
752
+
753
+ if (db) {
754
+ const msgKey = getMessageKey(
755
+ msg,
756
+ {},
757
+ store.signalRepository?.lidMapping,
758
+ );
759
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
760
+ type: "message",
761
+ data: cloneM(msg),
762
+ });
763
+ }
764
+ }
765
+ });
766
+
767
+ listen("messages.update", async (updates) => {
768
+ for (const { key, update } of updates) {
769
+ if (!key || !update) continue;
770
+ const msg = this.loadMessage(key);
771
+ if (msg) {
772
+ Object.assign(msg, update);
773
+ if (db) {
774
+ const chatId = await resolveJIDAsync(
775
+ msg.key.remoteJid,
776
+ store.signalRepository?.lidMapping,
777
+ );
778
+ const msgKey = getMessageKey(
779
+ msg,
780
+ store.chats[chatId].messages,
781
+ store.signalRepository?.lidMapping,
782
+ );
783
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
784
+ type: "message",
785
+ data: msg,
786
+ });
787
+ }
788
+ }
789
+ }
790
+ });
791
+
792
+ listen("messages.delete", async (item) => {
793
+ if ("all" in item) {
794
+ const jid = await resolveJIDAsync(
795
+ item.jid,
796
+ store.signalRepository?.lidMapping,
797
+ );
798
+ if (chatsCache[jid]) {
799
+ chatsCache[jid].messages = makeMessagesStore();
800
+ if (db)
801
+ pendingUpdates.set(`del:m:${jid}`, {
802
+ type: "delete",
803
+ id: jid,
804
+ table: "messages",
805
+ });
806
+ }
807
+ } else {
808
+ for (const key of item.keys) {
809
+ const chatId = await resolveJIDAsync(
810
+ key.remoteJid,
811
+ store.signalRepository?.lidMapping,
812
+ );
813
+ if (!chatId) continue;
814
+
815
+ const msg = findMessageByKey(
816
+ {}, // Dummy object as we use loadMessage/get
817
+ key,
818
+ store.signalRepository?.lidMapping,
819
+ );
820
+
821
+ // We need to fetch the actual object to remove
822
+ const actualMsg = store.loadMessage(key);
823
+ if (actualMsg) {
824
+ store.chats[chatId].messages.remove(actualMsg);
825
+ }
826
+
827
+ if (db) {
828
+ pendingUpdates.set(
829
+ `del:m:${chatId}:${key.id}:${key.fromMe ? 1 : 0}`,
830
+ {
831
+ type: "delete",
832
+ key,
833
+ table: "messages",
834
+ },
835
+ );
836
+ }
837
+ }
838
+ }
839
+ });
840
+
841
+ listen("messages.reaction", async (updates) => {
842
+ for (const item of updates) {
843
+ const { key } = item;
844
+ const reactions =
845
+ item.reactions || (item.reaction ? [item.reaction] : []);
846
+
847
+ if (!key || reactions.length === 0) continue;
848
+
849
+ for (const reaction of reactions) {
850
+ const chatId = await resolveJIDAsync(
851
+ key?.remoteJid,
852
+ store.signalRepository?.lidMapping,
853
+ );
854
+ if (!chatId || !key.id || !reaction.key) continue;
855
+
856
+ const msg = this.loadMessage(key);
857
+ if (msg) {
858
+ updateMessageWithReaction(msg, reaction, this.me);
859
+ if (db) {
860
+ const msgKey = getMessageKey(
861
+ msg,
862
+ store.chats[chatId].messages,
863
+ store.signalRepository?.lidMapping,
864
+ );
865
+ pendingUpdates.set(`m:${chatId}:${msgKey}`, {
866
+ type: "message",
867
+ data: msg,
868
+ });
869
+ }
870
+ }
871
+ }
872
+ }
873
+ });
874
+
875
+ listen("groups.update", async (updates) => {
876
+ for (const update of updates) {
877
+ if (!update?.id) continue;
878
+ const id = await resolveJIDAsync(
879
+ update.id,
880
+ store.signalRepository?.lidMapping,
881
+ );
882
+ if (groupsCache[id]) {
883
+ Object.assign(groupsCache[id], update);
884
+ }
885
+ }
886
+ });
887
+
888
+ listen(
889
+ "group-participants.update",
890
+ async ({ id, participants, action }) => {
891
+ const gid = await resolveJIDAsync(
892
+ id,
893
+ store.signalRepository?.lidMapping,
894
+ );
895
+ const metadata = groupsCache[gid];
896
+ if (metadata) {
897
+ const mappedParticipants = await Promise.all(
898
+ participants.map(async (p) => ({
899
+ id: p.phoneNumber,
900
+ lid: p.id,
901
+ jid: await resolveJIDAsync(
902
+ p.phoneNumber || p.id,
903
+ store.signalRepository?.lidMapping,
904
+ ),
905
+ admin: p.admin,
906
+ })),
907
+ );
908
+
909
+ switch (action) {
910
+ case "add":
911
+ metadata.participants.push(...mappedParticipants);
912
+ break;
913
+ case "demote":
914
+ case "promote":
915
+ for (const participant of metadata.participants) {
916
+ for (const participantData of mappedParticipants) {
917
+ if (
918
+ participantData.id === participant.id ||
919
+ participantData.lid === participant.lid ||
920
+ participantData.jid === participant.jid
921
+ ) {
922
+ participant.admin =
923
+ action === "promote" &&
924
+ (participantData.admin || "admin");
925
+ }
926
+ }
927
+ }
928
+ break;
929
+ case "remove": {
930
+ const removeSet = new Set();
931
+ for (const p of mappedParticipants) {
932
+ if (p.id) removeSet.add(p.id);
933
+ if (p.lid) removeSet.add(p.lid);
934
+ if (p.jid) removeSet.add(p.jid);
935
+
936
+ // If me is removed, delete group from cache
937
+ if (
938
+ this.me &&
939
+ (this.me === p.id || this.me === p.lid || this.me === p.jid)
940
+ ) {
941
+ delete groupsCache[gid];
942
+ }
943
+ }
944
+ metadata.participants = metadata.participants.reduce(
945
+ (acc, p) => {
946
+ if (
947
+ !removeSet.has(p.id) &&
948
+ !removeSet.has(p.jid) &&
949
+ !removeSet.has(p.lid)
950
+ )
951
+ acc.push(p);
952
+ return acc;
953
+ },
954
+ [],
955
+ );
956
+ break;
957
+ }
958
+ }
959
+ }
960
+ },
961
+ );
962
+ },
963
+ };
964
+
965
+ store.close = () => {
966
+ if (flushInterval) clearInterval(flushInterval);
967
+ if (cleanInterval) clearInterval(cleanInterval);
968
+ try {
969
+ _flush();
970
+ } catch {
971
+ // ignore flush errors on close
972
+ }
973
+ if (db) {
974
+ try {
975
+ db.close();
976
+ } catch {
977
+ // ignore close errors
978
+ }
979
+ }
980
+ };
981
+
982
+ return store;
983
+ };