@arcaelas/whatsapp 5.1.0 → 6.0.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.
Files changed (94) hide show
  1. package/README.md +282 -239
  2. package/build/cjs/decorators.js +0 -1
  3. package/build/cjs/index.js +0 -1
  4. package/build/cjs/lib/bot/decorator.js +0 -1
  5. package/build/cjs/lib/bot/decorators.d.ts +2 -2
  6. package/build/cjs/lib/bot/decorators.js +4 -4
  7. package/build/cjs/lib/bot/index.js +0 -1
  8. package/build/cjs/lib/chat/index.d.ts +41 -0
  9. package/build/cjs/lib/chat/index.js +27 -8
  10. package/build/cjs/lib/contact/index.js +0 -1
  11. package/build/cjs/lib/internal.js +0 -1
  12. package/build/cjs/lib/message/index.d.ts +6 -0
  13. package/build/cjs/lib/message/index.js +53 -9
  14. package/build/cjs/lib/status/index.js +0 -1
  15. package/build/cjs/lib/store/engine/index.d.ts +14 -0
  16. package/build/cjs/lib/store/engine/index.js +0 -1
  17. package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +10 -0
  18. package/build/cjs/lib/store/engine/lib/file_system/index.js +27 -1
  19. package/build/cjs/lib/store/engine/lib/index.js +0 -1
  20. package/build/cjs/lib/store/engine/lib/redis/index.d.ts +18 -4
  21. package/build/cjs/lib/store/engine/lib/redis/index.js +21 -3
  22. package/build/cjs/lib/store/engine/lib/s3/index.js +0 -1
  23. package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +14 -2
  24. package/build/cjs/lib/store/engine/lib/sqlite/index.js +22 -4
  25. package/build/cjs/lib/store/index.js +0 -1
  26. package/build/cjs/lib/whatsapp/index.js +20 -4
  27. package/build/esm/decorators.js +0 -1
  28. package/build/esm/index.js +0 -1
  29. package/build/esm/lib/bot/decorator.js +0 -1
  30. package/build/esm/lib/bot/decorators.d.ts +2 -2
  31. package/build/esm/lib/bot/decorators.js +4 -4
  32. package/build/esm/lib/bot/index.js +0 -1
  33. package/build/esm/lib/chat/index.d.ts +41 -0
  34. package/build/esm/lib/chat/index.js +27 -8
  35. package/build/esm/lib/contact/index.js +0 -1
  36. package/build/esm/lib/internal.js +0 -1
  37. package/build/esm/lib/message/index.d.ts +6 -0
  38. package/build/esm/lib/message/index.js +53 -9
  39. package/build/esm/lib/status/index.js +0 -1
  40. package/build/esm/lib/store/engine/index.d.ts +14 -0
  41. package/build/esm/lib/store/engine/index.js +0 -1
  42. package/build/esm/lib/store/engine/lib/file_system/index.d.ts +10 -0
  43. package/build/esm/lib/store/engine/lib/file_system/index.js +27 -1
  44. package/build/esm/lib/store/engine/lib/index.js +0 -1
  45. package/build/esm/lib/store/engine/lib/redis/index.d.ts +18 -4
  46. package/build/esm/lib/store/engine/lib/redis/index.js +21 -3
  47. package/build/esm/lib/store/engine/lib/s3/index.js +0 -1
  48. package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +14 -2
  49. package/build/esm/lib/store/engine/lib/sqlite/index.js +22 -4
  50. package/build/esm/lib/store/index.js +0 -1
  51. package/build/esm/lib/whatsapp/index.js +20 -4
  52. package/package.json +24 -5
  53. package/build/cjs/decorators.js.map +0 -1
  54. package/build/cjs/index.js.map +0 -1
  55. package/build/cjs/lib/bot/decorator.js.map +0 -1
  56. package/build/cjs/lib/bot/decorators.js.map +0 -1
  57. package/build/cjs/lib/bot/index.js.map +0 -1
  58. package/build/cjs/lib/chat/index.js.map +0 -1
  59. package/build/cjs/lib/contact/index.js.map +0 -1
  60. package/build/cjs/lib/internal.js.map +0 -1
  61. package/build/cjs/lib/message/index.js.map +0 -1
  62. package/build/cjs/lib/status/index.js.map +0 -1
  63. package/build/cjs/lib/store/engine/index.js.map +0 -1
  64. package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
  65. package/build/cjs/lib/store/engine/lib/index.js.map +0 -1
  66. package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
  67. package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
  68. package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +0 -1
  69. package/build/cjs/lib/store/index.js.map +0 -1
  70. package/build/cjs/lib/whatsapp/index.js.map +0 -1
  71. package/build/cjs/test.d.ts +0 -1
  72. package/build/cjs/test.js +0 -72
  73. package/build/cjs/test.js.map +0 -1
  74. package/build/esm/decorators.js.map +0 -1
  75. package/build/esm/index.js.map +0 -1
  76. package/build/esm/lib/bot/decorator.js.map +0 -1
  77. package/build/esm/lib/bot/decorators.js.map +0 -1
  78. package/build/esm/lib/bot/index.js.map +0 -1
  79. package/build/esm/lib/chat/index.js.map +0 -1
  80. package/build/esm/lib/contact/index.js.map +0 -1
  81. package/build/esm/lib/internal.js.map +0 -1
  82. package/build/esm/lib/message/index.js.map +0 -1
  83. package/build/esm/lib/status/index.js.map +0 -1
  84. package/build/esm/lib/store/engine/index.js.map +0 -1
  85. package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
  86. package/build/esm/lib/store/engine/lib/index.js.map +0 -1
  87. package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
  88. package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
  89. package/build/esm/lib/store/engine/lib/sqlite/index.js.map +0 -1
  90. package/build/esm/lib/store/index.js.map +0 -1
  91. package/build/esm/lib/whatsapp/index.js.map +0 -1
  92. package/build/esm/test.d.ts +0 -1
  93. package/build/esm/test.js +0 -67
  94. package/build/esm/test.js.map +0 -1
@@ -15,14 +15,14 @@ const lib_1 = require("../../../../../lib/store/engine/lib");
15
15
  * resuelven a mano: `list` es un `ORDER BY score DESC LIMIT/OFFSET` sobre el índice (sin
16
16
  * índice en memoria ni archivo de orden), `count` un `COUNT(*)` y el `unset` en cascada una
17
17
  * sola sentencia por rango de `path`. Medido sobre un chat real de 55.146 mensajes frente al
18
- * filesystem: 220 MB → 111 MB en disco, primer `list` 115 ms → 0,7 ms y dos inodes en total.
18
+ * filesystem: 220 MB → 64 MB en disco, primer `list` 115 ms → 0,6 ms y dos archivos en total.
19
19
  *
20
20
  * SQLite persistence driver: a single `(path, parent, score, value)` table with a
21
21
  * `(parent, score DESC)` index. The most efficient built-in driver, since it delegates to the
22
22
  * engine what the others hand-roll: `list` is an indexed `ORDER BY score DESC LIMIT/OFFSET`
23
23
  * (no in-memory index, no order file), `count` a `COUNT(*)` and cascading `unset` a single
24
24
  * range statement. Measured on a real 55,146-message chat against the filesystem: 220 MB →
25
- * 111 MB on disk, first `list` 115 ms → 0.7 ms and two inodes in total.
25
+ * 64 MB on disk, first `list` 115 ms → 0.6 ms and two files in total.
26
26
  *
27
27
  * @example
28
28
  * import Database from 'better-sqlite3';
@@ -49,7 +49,7 @@ class SQLiteEngine {
49
49
  catch {
50
50
  /* optional tuning */
51
51
  }
52
- _db.exec(`CREATE TABLE IF NOT EXISTS ${table} (path TEXT PRIMARY KEY, parent TEXT NOT NULL, score INTEGER NOT NULL, value TEXT NOT NULL);
52
+ _db.exec(`CREATE TABLE IF NOT EXISTS ${table} (path TEXT PRIMARY KEY, parent TEXT NOT NULL, score INTEGER NOT NULL, value TEXT NOT NULL DEFAULT '', binary BLOB);
53
53
  CREATE INDEX IF NOT EXISTS ${table}_order ON ${table} (parent, score DESC);`);
54
54
  this._read = _db.prepare(`SELECT value FROM ${table} WHERE path = ?`);
55
55
  this._write = _db.prepare(`INSERT INTO ${table} (path, parent, score, value) VALUES (?, ?, ?, ?)
@@ -62,6 +62,9 @@ class SQLiteEngine {
62
62
  this._page = _db.prepare(`SELECT value FROM ${table} WHERE parent = ? ORDER BY score DESC LIMIT ? OFFSET ?`);
63
63
  this._total = _db.prepare(`SELECT COUNT(*) AS total FROM ${table} WHERE parent = ?`);
64
64
  this._wipe = _db.prepare(`DELETE FROM ${table}`);
65
+ this._read_blob = _db.prepare(`SELECT binary FROM ${table} WHERE path = ?`);
66
+ this._write_blob = _db.prepare(`INSERT INTO ${table} (path, parent, score, value, binary) VALUES (?, ?, ?, '', ?)
67
+ ON CONFLICT(path) DO UPDATE SET binary = excluded.binary, score = excluded.score`);
65
68
  }
66
69
  /**
67
70
  * Lee el valor de un documento.
@@ -102,6 +105,22 @@ class SQLiteEngine {
102
105
  async count(path) {
103
106
  return Number(this._total.get((0, lib_1.normalize_path)(path)).total);
104
107
  }
108
+ /**
109
+ * Lee el binario del documento, o null si la fila no lo tiene.
110
+ * Reads the document binary, or null when the row has none.
111
+ */
112
+ async get_buffer(path) {
113
+ const row = this._read_blob.get((0, lib_1.normalize_path)(path));
114
+ return row?.binary ? Buffer.from(row.binary) : null;
115
+ }
116
+ /**
117
+ * Escribe el binario del documento como BLOB, sin JSON ni base64.
118
+ * Writes the document binary as a BLOB, with no JSON nor base64.
119
+ */
120
+ async set_buffer(path, data, score) {
121
+ const { parent } = (0, lib_1.split_path)(path);
122
+ this._write_blob.run((0, lib_1.normalize_path)(path), parent, score ?? Date.now(), data);
123
+ }
105
124
  /**
106
125
  * Vacía la tabla completa.
107
126
  * Clears the entire table.
@@ -111,4 +130,3 @@ class SQLiteEngine {
111
130
  }
112
131
  }
113
132
  exports.SQLiteEngine = SQLiteEngine;
114
- //# sourceMappingURL=index.js.map
@@ -46,4 +46,3 @@ function deserialize(raw) {
46
46
  return null;
47
47
  }
48
48
  }
49
- //# sourceMappingURL=index.js.map
@@ -154,6 +154,19 @@ class WhatsApp {
154
154
  }
155
155
  return null;
156
156
  }
157
+ /**
158
+ * @internal
159
+ * Persiste un binario: crudo cuando el driver lo soporta, JSON con base64 si no.
160
+ * Persists a binary: raw when the driver supports it, base64 JSON otherwise.
161
+ */
162
+ async #write_content(path, data) {
163
+ if (this.engine.set_buffer) {
164
+ await this.engine.set_buffer(path, data);
165
+ }
166
+ else {
167
+ await this.engine.set(path, (0, store_1.serialize)({ data: data.toString('base64') }));
168
+ }
169
+ }
157
170
  /** @internal Encola una tarea en la cadena serial de handlers. / Queues a task on the serial handler chain. */
158
171
  #enqueue(task) {
159
172
  this.#chain = this.#chain.then(task).catch(() => { });
@@ -462,7 +475,7 @@ class WhatsApp {
462
475
  };
463
476
  await this.engine.set(`/status/${doc.id}`, (0, store_1.serialize)(doc), created_at);
464
477
  if (post.content) {
465
- await this.engine.set(`/status/${doc.id}/content`, (0, store_1.serialize)({ data: post.content.toString('base64') }));
478
+ await this.#write_content(`/status/${doc.id}/content`, post.content);
466
479
  }
467
480
  result = new status_1.Feed(this, doc);
468
481
  this.emit('feed:created', result, this);
@@ -627,6 +640,7 @@ class WhatsApp {
627
640
  archived: ch.archived ?? null,
628
641
  pinned: ch.pinned ?? null,
629
642
  mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
643
+ unread_count: ch.unreadCount ?? null,
630
644
  };
631
645
  if (ch.name) {
632
646
  raw.name = ch.name;
@@ -662,6 +676,9 @@ class WhatsApp {
662
676
  if (mute_changed) {
663
677
  patch.mute_end_time = ch.muteEndTime != null ? Number(ch.muteEndTime) : null;
664
678
  }
679
+ if (ch.unreadCount != null) {
680
+ patch.unread_count = ch.unreadCount;
681
+ }
665
682
  if (Object.keys(patch).length > 0) {
666
683
  const merged = { ...current, ...patch };
667
684
  await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged));
@@ -811,7 +828,7 @@ class WhatsApp {
811
828
  }
812
829
  await this.engine.set(`/status/${mid}`, (0, store_1.serialize)(feed_raw));
813
830
  if (content_buf.length > 0) {
814
- await this.engine.set(`/status/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
831
+ await this.#write_content(`/status/${mid}/content`, content_buf);
815
832
  }
816
833
  this.emit('feed:created', new status_1.Feed(this, feed_raw), this);
817
834
  continue;
@@ -999,7 +1016,7 @@ class WhatsApp {
999
1016
  }
1000
1017
  }
1001
1018
  if (content_buf.length > 0) {
1002
- await this.engine.set(`/chat/${cid}/message/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
1019
+ await this.#write_content(`/chat/${cid}/message/${mid}/content`, content_buf);
1003
1020
  }
1004
1021
  const instance = (0, message_1.message)(this, doc);
1005
1022
  const chat_instance = await instance.chat();
@@ -1098,4 +1115,3 @@ class WhatsApp {
1098
1115
  }
1099
1116
  exports.WhatsApp = WhatsApp;
1100
1117
  exports.default = WhatsApp;
1101
- //# sourceMappingURL=index.js.map
@@ -6,4 +6,3 @@
6
6
  * API to build bots on top of the WhatsApp client.
7
7
  */
8
8
  export { WhatsAppBot, Bot, on, guard, command, from, once, connect, disconnect, every, delay, pipe, pair, HANDLERS, decorator, } from './lib/bot/index.js';
9
- //# sourceMappingURL=decorators.js.map
@@ -9,4 +9,3 @@ export { Contact, contact } from './lib/contact/index.js';
9
9
  export { Chat, chat } from './lib/chat/index.js';
10
10
  export { Message, message, Text, Image, Video, Audio, Sticker, Document, Location, Poll, VCard, Event } from './lib/message/index.js';
11
11
  export { Feed, TTL_MS as FEED_TTL_MS } from './lib/status/index.js';
12
- //# sourceMappingURL=index.js.map
@@ -243,4 +243,3 @@ export class WhatsAppBot extends WhatsApp {
243
243
  }
244
244
  }
245
245
  export default WhatsAppBot;
246
- //# sourceMappingURL=decorator.js.map
@@ -82,9 +82,9 @@ export declare function delay(ms: number): (_value: unknown, context: ClassMetho
82
82
  export declare const pair: () => (_value: unknown, context: ClassMethodDecoratorContext) => void;
83
83
  /**
84
84
  * Filtra por autor del mensaje. Acepta JID, LID o teléfono numérico; el string
85
- * se normaliza vía `wa._resolve_jid` con cache lazy. Un array produce match OR.
85
+ * se normaliza con la resolución interna del cliente y cache lazy. Un array produce match OR.
86
86
  * Filters by message author. Accepts JID, LID or numeric phone; the string is
87
- * normalized via `wa._resolve_jid` with lazy cache. An array yields OR matching.
87
+ * normalized through the client's internal resolution with lazy cache. An array yields OR matching.
88
88
  *
89
89
  * @param source - Identificador(es) permitidos o predicate sobre `msg.from` / Allowed identifier(s) or predicate on `msg.from`
90
90
  */
@@ -3,6 +3,7 @@
3
3
  * @description Decoradores públicos del bot.
4
4
  * Public bot decorators.
5
5
  */
6
+ import { internals } from '../../lib/internal.js';
6
7
  import { WhatsAppBot, decorator, register_workflow_step, resolve, } from './decorator.js';
7
8
  /**
8
9
  * Registra el método como listener del evento indicado. Apilable: varios `@on`
@@ -114,9 +115,9 @@ export const pair = decorator((_meta, h) => {
114
115
  });
115
116
  /**
116
117
  * Filtra por autor del mensaje. Acepta JID, LID o teléfono numérico; el string
117
- * se normaliza vía `wa._resolve_jid` con cache lazy. Un array produce match OR.
118
+ * se normaliza con la resolución interna del cliente y cache lazy. Un array produce match OR.
118
119
  * Filters by message author. Accepts JID, LID or numeric phone; the string is
119
- * normalized via `wa._resolve_jid` with lazy cache. An array yields OR matching.
120
+ * normalized through the client's internal resolution with lazy cache. An array yields OR matching.
120
121
  *
121
122
  * @param source - Identificador(es) permitidos o predicate sobre `msg.from` / Allowed identifier(s) or predicate on `msg.from`
122
123
  */
@@ -134,7 +135,7 @@ export function from(source) {
134
135
  if (resolved === null) {
135
136
  resolved = new Set();
136
137
  for (const raw of list) {
137
- const r = await wa._resolve_jid(raw);
138
+ const r = await internals(wa).resolve_jid(raw);
138
139
  if (r) {
139
140
  resolved.add(r);
140
141
  }
@@ -247,4 +248,3 @@ export function command(pattern) {
247
248
  });
248
249
  };
249
250
  }
250
- //# sourceMappingURL=decorators.js.map
@@ -1,3 +1,2 @@
1
1
  export { WhatsAppBot, HANDLERS, decorator } from './decorator.js';
2
2
  export { on, guard, command, from, once, connect, disconnect, every, delay, pipe, pair, Bot } from './decorators.js';
3
- //# sourceMappingURL=index.js.map
@@ -16,6 +16,7 @@ export declare class Chat {
16
16
  archived?: boolean | null;
17
17
  pinned?: number | null;
18
18
  mute_end_time?: number | null;
19
+ unread_count?: number | null;
19
20
  };
20
21
  /**
21
22
  * @internal Documento crudo del chat. `id` es el identificador del engine (JID, LID o
@@ -29,6 +30,7 @@ export declare class Chat {
29
30
  archived?: boolean | null;
30
31
  pinned?: number | null;
31
32
  mute_end_time?: number | null;
33
+ unread_count?: number | null;
32
34
  });
33
35
  /** Teléfono del contacto, o el identificador crudo en grupos y LIDs. / Contact phone, or the raw identifier for groups and LIDs. */
34
36
  get id(): string;
@@ -40,6 +42,8 @@ export declare class Chat {
40
42
  get archived(): boolean;
41
43
  /** true si el chat está fijado. / true when the chat is pinned. */
42
44
  get pinned(): boolean;
45
+ /** Mensajes sin leer del chat. / Chat's unread messages. */
46
+ get count(): number;
43
47
  /** Fecha ISO UTC hasta la que el chat está silenciado, o null. / ISO UTC date until the chat stays muted, or null. */
44
48
  get muted(): string | null;
45
49
  }
@@ -56,6 +60,7 @@ export declare function chat(wa: WhatsApp): {
56
60
  archived?: boolean | null;
57
61
  pinned?: number | null;
58
62
  mute_end_time?: number | null;
63
+ unread_count?: number | null;
59
64
  }): {
60
65
  /**
61
66
  * Participantes del chat: los integrantes en grupos, el contacto y yo en 1:1.
@@ -66,6 +71,15 @@ export declare function chat(wa: WhatsApp): {
66
71
  * @returns Página de contactos / Contact page
67
72
  */
68
73
  members(offset?: number, limit?: number): Promise<InstanceType<typeof wa.Contact>[]>;
74
+ /**
75
+ * Descripción del chat: el asunto del grupo o, en un 1:1, la bio del contacto.
76
+ * Es asíncrono porque ninguno de los dos vive en el documento del chat.
77
+ * Chat description: the group's subject or, on a 1:1, the contact's bio. It is async
78
+ * because neither of them lives in the chat document.
79
+ *
80
+ * @returns Descripción, o cadena vacía si no hay / Description, or an empty string when absent
81
+ */
82
+ content(): Promise<string>;
69
83
  /**
70
84
  * Mensajes del chat paginados desde el más reciente.
71
85
  * Chat messages paginated from the most recent one.
@@ -146,6 +160,7 @@ export declare function chat(wa: WhatsApp): {
146
160
  archived?: boolean | null;
147
161
  pinned?: number | null;
148
162
  mute_end_time?: number | null;
163
+ unread_count?: number | null;
149
164
  };
150
165
  /** Teléfono del contacto, o el identificador crudo en grupos y LIDs. / Contact phone, or the raw identifier for groups and LIDs. */
151
166
  get id(): string;
@@ -157,6 +172,8 @@ export declare function chat(wa: WhatsApp): {
157
172
  get archived(): boolean;
158
173
  /** true si el chat está fijado. / true when the chat is pinned. */
159
174
  get pinned(): boolean;
175
+ /** Mensajes sin leer del chat. / Chat's unread messages. */
176
+ get count(): number;
160
177
  /** Fecha ISO UTC hasta la que el chat está silenciado, o null. / ISO UTC date until the chat stays muted, or null. */
161
178
  get muted(): string | null;
162
179
  };
@@ -179,6 +196,15 @@ export declare function chat(wa: WhatsApp): {
179
196
  * @returns Página de contactos / Contact page
180
197
  */
181
198
  members(offset?: number, limit?: number): Promise<InstanceType<typeof wa.Contact>[]>;
199
+ /**
200
+ * Descripción del chat: el asunto del grupo o, en un 1:1, la bio del contacto.
201
+ * Es asíncrono porque ninguno de los dos vive en el documento del chat.
202
+ * Chat description: the group's subject or, on a 1:1, the contact's bio. It is async
203
+ * because neither of them lives in the chat document.
204
+ *
205
+ * @returns Descripción, o cadena vacía si no hay / Description, or an empty string when absent
206
+ */
207
+ content(): Promise<string>;
182
208
  /**
183
209
  * Mensajes del chat paginados desde el más reciente.
184
210
  * Chat messages paginated from the most recent one.
@@ -259,6 +285,7 @@ export declare function chat(wa: WhatsApp): {
259
285
  archived?: boolean | null;
260
286
  pinned?: number | null;
261
287
  mute_end_time?: number | null;
288
+ unread_count?: number | null;
262
289
  };
263
290
  /** Teléfono del contacto, o el identificador crudo en grupos y LIDs. / Contact phone, or the raw identifier for groups and LIDs. */
264
291
  get id(): string;
@@ -270,6 +297,8 @@ export declare function chat(wa: WhatsApp): {
270
297
  get archived(): boolean;
271
298
  /** true si el chat está fijado. / true when the chat is pinned. */
272
299
  get pinned(): boolean;
300
+ /** Mensajes sin leer del chat. / Chat's unread messages. */
301
+ get count(): number;
273
302
  /** Fecha ISO UTC hasta la que el chat está silenciado, o null. / ISO UTC date until the chat stays muted, or null. */
274
303
  get muted(): string | null;
275
304
  } | null>;
@@ -291,6 +320,15 @@ export declare function chat(wa: WhatsApp): {
291
320
  * @returns Página de contactos / Contact page
292
321
  */
293
322
  members(offset?: number, limit?: number): Promise<InstanceType<typeof wa.Contact>[]>;
323
+ /**
324
+ * Descripción del chat: el asunto del grupo o, en un 1:1, la bio del contacto.
325
+ * Es asíncrono porque ninguno de los dos vive en el documento del chat.
326
+ * Chat description: the group's subject or, on a 1:1, the contact's bio. It is async
327
+ * because neither of them lives in the chat document.
328
+ *
329
+ * @returns Descripción, o cadena vacía si no hay / Description, or an empty string when absent
330
+ */
331
+ content(): Promise<string>;
294
332
  /**
295
333
  * Mensajes del chat paginados desde el más reciente.
296
334
  * Chat messages paginated from the most recent one.
@@ -371,6 +409,7 @@ export declare function chat(wa: WhatsApp): {
371
409
  archived?: boolean | null;
372
410
  pinned?: number | null;
373
411
  mute_end_time?: number | null;
412
+ unread_count?: number | null;
374
413
  };
375
414
  /** Teléfono del contacto, o el identificador crudo en grupos y LIDs. / Contact phone, or the raw identifier for groups and LIDs. */
376
415
  get id(): string;
@@ -382,6 +421,8 @@ export declare function chat(wa: WhatsApp): {
382
421
  get archived(): boolean;
383
422
  /** true si el chat está fijado. / true when the chat is pinned. */
384
423
  get pinned(): boolean;
424
+ /** Mensajes sin leer del chat. / Chat's unread messages. */
425
+ get count(): number;
385
426
  /** Fecha ISO UTC hasta la que el chat está silenciado, o null. / ISO UTC date until the chat stays muted, or null. */
386
427
  get muted(): string | null;
387
428
  }[]>;
@@ -40,6 +40,10 @@ export class Chat {
40
40
  get pinned() {
41
41
  return this._raw.pinned != null;
42
42
  }
43
+ /** Mensajes sin leer del chat. / Chat's unread messages. */
44
+ get count() {
45
+ return this._raw.unread_count ?? 0;
46
+ }
43
47
  /** Fecha ISO UTC hasta la que el chat está silenciado, o null. / ISO UTC date until the chat stays muted, or null. */
44
48
  get muted() {
45
49
  const end = this._raw.mute_end_time;
@@ -90,14 +94,14 @@ export function chat(wa) {
90
94
  * Participantes del grupo memoizados 15s, para no repetir `groupMetadata` en cada página.
91
95
  * Group participants memoized for 15s, avoiding a `groupMetadata` round-trip per page.
92
96
  */
93
- const members_cache = new Map();
94
- function group_members(jid) {
95
- if (!members_cache.has(jid)) {
97
+ const groups_cache = new Map();
98
+ function group_meta(jid) {
99
+ if (!groups_cache.has(jid)) {
96
100
  const socket = internals(wa).socket;
97
- members_cache.set(jid, socket ? socket.groupMetadata(jid).then((meta) => meta.participants).catch(() => []) : Promise.resolve([]));
98
- setTimeout(() => members_cache.delete(jid), 15_000);
101
+ groups_cache.set(jid, socket ? socket.groupMetadata(jid).catch(() => ({ participants: [] })) : Promise.resolve({ participants: [] }));
102
+ setTimeout(() => groups_cache.delete(jid), 15_000);
99
103
  }
100
- return members_cache.get(jid);
104
+ return groups_cache.get(jid);
101
105
  }
102
106
  /**
103
107
  * Contacto desde el engine, o ficha mínima local cuando no está persistido (sin red).
@@ -118,10 +122,24 @@ export function chat(wa) {
118
122
  */
119
123
  async members(offset = 0, limit = 50) {
120
124
  const ids = this.type === 'group'
121
- ? (await group_members(this._raw.id)).map((participant) => participant.id)
125
+ ? (await group_meta(this._raw.id)).participants.map((participant) => participant.id)
122
126
  : [this._raw.id, internals(wa).socket?.user?.id].filter((id) => Boolean(id));
123
127
  return Promise.all(ids.slice(offset, offset + limit).map(load_contact));
124
128
  }
129
+ /**
130
+ * Descripción del chat: el asunto del grupo o, en un 1:1, la bio del contacto.
131
+ * Es asíncrono porque ninguno de los dos vive en el documento del chat.
132
+ * Chat description: the group's subject or, on a 1:1, the contact's bio. It is async
133
+ * because neither of them lives in the chat document.
134
+ *
135
+ * @returns Descripción, o cadena vacía si no hay / Description, or an empty string when absent
136
+ */
137
+ async content() {
138
+ if (this.type === 'group') {
139
+ return (await group_meta(this._raw.id)).desc ?? '';
140
+ }
141
+ return deserialize(await wa.engine.get(`/contact/${this._raw.id}`))?.status ?? '';
142
+ }
125
143
  /**
126
144
  * Mensajes del chat paginados desde el más reciente.
127
145
  * Chat messages paginated from the most recent one.
@@ -237,6 +255,8 @@ export function chat(wa) {
237
255
  const socket = internals(wa).socket;
238
256
  if (socket) {
239
257
  await socket.chatModify({ markRead: true, lastMessages: await last_messages(this._raw.id) }, this._raw.id);
258
+ this._raw.unread_count = 0;
259
+ await wa.engine.set(`/chat/${this._raw.id}`, serialize(this._raw));
240
260
  ok = true;
241
261
  }
242
262
  return ok;
@@ -318,4 +338,3 @@ export function chat(wa) {
318
338
  }
319
339
  };
320
340
  }
321
- //# sourceMappingURL=index.js.map
@@ -146,4 +146,3 @@ export function contact(wa) {
146
146
  }
147
147
  };
148
148
  }
149
- //# sourceMappingURL=index.js.map
@@ -32,4 +32,3 @@ export function bind(owner, state) {
32
32
  export function internals(owner) {
33
33
  return channel.get(owner);
34
34
  }
35
- //# sourceMappingURL=internal.js.map
@@ -451,6 +451,12 @@ export declare class Sticker extends Message {
451
451
  * Document message; the actual MIME lives in the base `mime`.
452
452
  */
453
453
  export declare class Document extends Message {
454
+ /** @internal */
455
+ private get _media();
456
+ /** Nombre del archivo. / File name. */
457
+ get name(): string;
458
+ /** Número de páginas (PDF); 0 cuando no aplica. / Page count (PDF); 0 when not applicable. */
459
+ get pages(): number;
454
460
  /** Peso en bytes. / Size in bytes. */
455
461
  get size(): number;
456
462
  content(): Promise<Buffer>;
@@ -88,21 +88,56 @@ async function send(wa, cid, content, binary, extra = {}, doc_overrides) {
88
88
  }
89
89
  await wa.engine.set(`/chat/${jid}/message/${sent_id}`, serialize(instance._raw), instance._raw.created_at);
90
90
  if (binary) {
91
- await wa.engine.set(`/chat/${jid}/message/${sent_id}/content`, serialize({ data: binary.toString('base64') }));
91
+ await write_content(wa, `/chat/${jid}/message/${sent_id}/content`, binary);
92
92
  }
93
93
  result = instance;
94
94
  }
95
95
  }
96
96
  return result;
97
97
  }
98
+ /**
99
+ * Binario persistido de un documento: primero el crudo del engine y, si el driver no soporta
100
+ * binarios o el dato viene de una sesión anterior, el documento JSON con base64.
101
+ * A document's persisted binary: the engine's raw one first and, when the driver lacks binary
102
+ * support or the data comes from an older session, the base64 JSON document.
103
+ *
104
+ * @param wa - Cliente dueño / Owner client
105
+ * @param path - Ruta del contenido / Content path
106
+ * @returns Binario o null / Binary or null
107
+ */
108
+ async function read_content(wa, path) {
109
+ const raw = await wa.engine.get_buffer?.(path);
110
+ if (raw?.length) {
111
+ return raw;
112
+ }
113
+ const legacy = deserialize(await wa.engine.get(path));
114
+ return legacy?.data ? Buffer.from(legacy.data, 'base64') : null;
115
+ }
116
+ /**
117
+ * Persiste el binario de un documento: crudo cuando el driver lo soporta, JSON con base64
118
+ * en caso contrario.
119
+ * Persists a document's binary: raw when the driver supports it, base64 JSON otherwise.
120
+ *
121
+ * @param wa - Cliente dueño / Owner client
122
+ * @param path - Ruta del contenido / Content path
123
+ * @param data - Binario a guardar / Binary to store
124
+ */
125
+ async function write_content(wa, path, data) {
126
+ if (wa.engine.set_buffer) {
127
+ await wa.engine.set_buffer(path, data);
128
+ }
129
+ else {
130
+ await wa.engine.set(path, serialize({ data: data.toString('base64') }));
131
+ }
132
+ }
98
133
  /**
99
134
  * Stream del binario del mensaje: cache del engine o descarga de baileys.
100
135
  * Message binary stream: engine cache or baileys download.
101
136
  */
102
137
  async function media_stream(wa, doc) {
103
- const cached = deserialize(await wa.engine.get(`/chat/${doc.cid}/message/${doc.id}/content`));
104
- if (cached?.data) {
105
- return Readable.from(Buffer.from(cached.data, 'base64'));
138
+ const cached = await read_content(wa, `/chat/${doc.cid}/message/${doc.id}/content`);
139
+ if (cached) {
140
+ return Readable.from(cached);
106
141
  }
107
142
  const socket = internals(wa).socket;
108
143
  if (socket) {
@@ -255,8 +290,7 @@ export class Message {
255
290
  * @returns Buffer del contenido / Content buffer
256
291
  */
257
292
  async content() {
258
- const cached = deserialize(await this._wa.engine.get(`/chat/${this._raw.cid}/message/${this._raw.id}/content`));
259
- return cached?.data ? Buffer.from(cached.data, 'base64') : Buffer.alloc(0);
293
+ return (await read_content(this._wa, `/chat/${this._raw.cid}/message/${this._raw.id}/content`)) ?? Buffer.alloc(0);
260
294
  }
261
295
  /**
262
296
  * Stream del contenido; para media descarga de baileys si el cache falta.
@@ -779,10 +813,21 @@ export class Sticker extends Message {
779
813
  * Document message; the actual MIME lives in the base `mime`.
780
814
  */
781
815
  export class Document extends Message {
816
+ /** @internal */
817
+ get _media() {
818
+ return unwrap(this._raw.raw.message ?? {}).documentMessage;
819
+ }
820
+ /** Nombre del archivo. / File name. */
821
+ get name() {
822
+ return this._media?.fileName ?? '';
823
+ }
824
+ /** Número de páginas (PDF); 0 cuando no aplica. / Page count (PDF); 0 when not applicable. */
825
+ get pages() {
826
+ return this._media?.pageCount ?? 0;
827
+ }
782
828
  /** Peso en bytes. / Size in bytes. */
783
829
  get size() {
784
- const media = unwrap(this._raw.raw.message ?? {}).documentMessage;
785
- return to_number(media?.fileLength);
830
+ return to_number(this._media?.fileLength);
786
831
  }
787
832
  async content() {
788
833
  return drain(await this.stream());
@@ -1064,4 +1109,3 @@ export function message(wa, raw) {
1064
1109
  };
1065
1110
  return new (types[doc.type] ?? Message)(wa, doc);
1066
1111
  }
1067
- //# sourceMappingURL=index.js.map
@@ -125,4 +125,3 @@ export class Feed extends Message {
125
125
  throw new Error('ERR_FEED_UNSUPPORTED');
126
126
  }
127
127
  }
128
- //# sourceMappingURL=index.js.map
@@ -42,6 +42,20 @@ export interface Engine {
42
42
  count(path: string): Promise<number>;
43
43
  /** Vacía completamente el almacén. / Clears the entire store. */
44
44
  clear(): Promise<void>;
45
+ /**
46
+ * Lee un binario por path. Opcional: un driver que no lo implemente sigue siendo válido y
47
+ * la librería cae al documento serializado.
48
+ * Reads a binary by path. Optional: a driver that does not implement it stays valid and the
49
+ * library falls back to the serialized document.
50
+ */
51
+ get_buffer?(path: string): Promise<Buffer | null>;
52
+ /**
53
+ * Escribe un binario sin pasar por JSON ni base64 (un 33% menos de peso y sin parseo para
54
+ * leerlo). Opcional, igual que `get_buffer`.
55
+ * Writes a binary without JSON nor base64 (33% less weight and no parsing to read it back).
56
+ * Optional, just like `get_buffer`.
57
+ */
58
+ set_buffer?(path: string, data: Buffer, score?: number): Promise<void>;
45
59
  }
46
60
  export { FileSystemEngine } from '../../../lib/store/engine/lib/file_system/index.js';
47
61
  export { RedisEngine, type RedisClient } from '../../../lib/store/engine/lib/redis/index.js';
@@ -8,4 +8,3 @@ export { FileSystemEngine } from '../../../lib/store/engine/lib/file_system/inde
8
8
  export { RedisEngine } from '../../../lib/store/engine/lib/redis/index.js';
9
9
  export { S3Engine } from '../../../lib/store/engine/lib/s3/index.js';
10
10
  export { SQLiteEngine } from '../../../lib/store/engine/lib/sqlite/index.js';
11
- //# sourceMappingURL=index.js.map
@@ -78,6 +78,16 @@ export declare class FileSystemEngine implements Engine {
78
78
  * Counts direct children with a valid document.
79
79
  */
80
80
  count(path: string): Promise<number>;
81
+ /**
82
+ * Lee el binario del documento, o null si no existe.
83
+ * Reads the document binary, or null when missing.
84
+ */
85
+ get_buffer(path: string): Promise<Buffer | null>;
86
+ /**
87
+ * Escribe el binario del documento como archivo crudo, sin JSON ni base64.
88
+ * Writes the document binary as a raw file, with no JSON nor base64.
89
+ */
90
+ set_buffer(path: string, data: Buffer, score?: number): Promise<void>;
81
91
  /**
82
92
  * Vacía completamente el almacén.
83
93
  * Clears the entire store.
@@ -10,6 +10,8 @@ import { IndexCache, normalize_path, SortedIndex, split_path } from '../../../..
10
10
  const INDEX_FILE = 'index.json';
11
11
  /** Índice de orden persistido junto a los hijos del directorio. / Ordering index persisted next to the directory children. */
12
12
  const ORDER_FILE = '.order';
13
+ /** Binario del documento, guardado crudo junto a su JSON. / Document binary, stored raw next to its JSON. */
14
+ const BINARY_FILE = 'content.bin';
13
15
  /**
14
16
  * Driver de persistencia en sistema de archivos.
15
17
  * Cada documento se almacena como `<base>/<path>/index.json` de modo que un recurso pueda
@@ -158,6 +160,31 @@ export class FileSystemEngine {
158
160
  async count(path) {
159
161
  return (await this._index(path)).size;
160
162
  }
163
+ /**
164
+ * Lee el binario del documento, o null si no existe.
165
+ * Reads the document binary, or null when missing.
166
+ */
167
+ async get_buffer(path) {
168
+ return readFile(join(this._dir(path), BINARY_FILE)).catch(() => null);
169
+ }
170
+ /**
171
+ * Escribe el binario del documento como archivo crudo, sin JSON ni base64.
172
+ * Writes the document binary as a raw file, with no JSON nor base64.
173
+ */
174
+ async set_buffer(path, data, score) {
175
+ const dir = this._dir(path);
176
+ await mkdir(dir, { recursive: true });
177
+ const file = join(dir, BINARY_FILE);
178
+ const tmp = `${file}.${randomBytes(6).toString('hex')}.tmp`;
179
+ await writeFile(tmp, data);
180
+ await rename(tmp, file);
181
+ const when = score ?? Date.now();
182
+ if (score !== undefined) {
183
+ await utimes(file, new Date(score), new Date(score)).catch(() => { });
184
+ }
185
+ const { parent, name } = split_path(path);
186
+ this._indexes.get(parent)?.set(name, when);
187
+ }
161
188
  /**
162
189
  * Vacía completamente el almacén.
163
190
  * Clears the entire store.
@@ -172,4 +199,3 @@ export class FileSystemEngine {
172
199
  this._indexes.clear();
173
200
  }
174
201
  }
175
- //# sourceMappingURL=index.js.map
@@ -203,4 +203,3 @@ export function split_path(path) {
203
203
  const cut = full.lastIndexOf('/');
204
204
  return cut === -1 ? { parent: '', name: full } : { parent: full.slice(0, cut), name: full.slice(cut + 1) };
205
205
  }
206
- //# sourceMappingURL=index.js.map