kage-baileys 2.0.1 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  const [major, minor] = process.versions.node.split('.').map(Number);
3
3
  if (major < 22 || (major === 22 && minor < 12)) {
4
- console.error('Kage Baileys requires Node.js >=22.12.0. Use a supported Node.js LTS release.');
4
+ console.error('Kage Baileys necesita Node >=22.12.0 por interoperabilidad CommonJS/ESM. Selecciona Node 22 o 24 en el panel. No basta con borrar engines.');
5
5
  process.exitCode = 1;
6
6
  }
@@ -114,7 +114,6 @@ function installKageTools(conn, options = {}) {
114
114
  const status = require('./status-tools');
115
115
  const country = require('./country-info');
116
116
  conn.getReceivedStatuses = (includeUnavailable = false) => status.listStatuses(conn, includeUnavailable);
117
- conn.reactToReceivedStatus = (entry, emoji) => status.reactToStatus(conn, entry, emoji);
118
117
  conn.getCountryForJid = jid => country.getCountryForJid(conn, jid);
119
118
  conn.downloadNormalMedia = (raw, opts) => downloadNormalMedia(conn, raw, opts);
120
119
  conn.saveNormalMedia = (raw, opts) => saveNormalMedia(conn, raw, opts);
@@ -150,7 +149,7 @@ function installKageTools(conn, options = {}) {
150
149
  conn.enableStatusTracking = (settings = {}) => {
151
150
  if (stop) return stop;
152
151
  let active = true;
153
- const scopedSettings = new Proxy(settings, { get(target, key) { return key === 'statusReaction' ? active && target[key] : target[key]; } });
152
+ const scopedSettings = new Proxy(settings, { get(target, key) { return key === 'statusSave' ? active && target[key] : target[key]; } });
154
153
  const upsert = event => { for (const raw of event?.messages || []) status.handleStatus(raw, conn, scopedSettings).catch(e => conn.logger?.warn?.({ error: e.message }, 'status tracking')); };
155
154
  const deleted = event => { for (const key of event?.keys || []) status.markStatusDeleted(conn, key); };
156
155
  const updated = items => { for (const item of items || []) if (item.key?.remoteJid === 'status@broadcast' && item.update?.message === null) status.markStatusDeleted(conn, item.key); };
@@ -46,7 +46,7 @@ const axios_1 = __importDefault(require("axios"));
46
46
  const crypto_1 = require("crypto");
47
47
  const os_1 = require("os");
48
48
  const WAProto_1 = require("../../WAProto");
49
- const kage-baileys_version_json_1 = require("../Defaults/kage-baileys-version.json");
49
+ const kageVersionJson = require("../Defaults/wileys-version.json");
50
50
  const Types_1 = require("../Types");
51
51
  const WABinary_1 = require("../WABinary");
52
52
  const COMPANION_PLATFORM_MAP = {
@@ -261,19 +261,22 @@ exports.printQRIfNecessaryListener = printQRIfNecessaryListener;
261
261
 
262
262
  const fetchLatestKageVersion = async (options = {}) => {
263
263
  try {
264
- const { data } = await axios_1.default.get('https://registry.npmjs.org/kage-baileys', {
264
+ const { data } = await axios_1.default.get('https://registry.npmjs.org/kage-baileys/latest', {
265
+ timeout: 15000,
265
266
  ...options,
266
267
  responseType: 'json'
267
268
  });
268
- const versionStr = data.version;
269
- const [major, minor, patch] = versionStr.split('.').map(Number);
269
+ const versionStr = data.version;
270
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(versionStr || '');
271
+ if (!match) throw new Error('Invalid Kage package version');
272
+ const [major, minor, patch] = match.slice(1).map(Number);
270
273
  return {
271
274
  version: [major, minor, patch],
272
275
  isLatest: true
273
276
  };
274
277
  } catch (error) {
275
278
  return {
276
- version: kage-baileys_version_json_1.version,
279
+ version: require('../../package.json').version.split('-')[0].split('.').map(Number),
277
280
  isLatest: false,
278
281
  error
279
282
  };
@@ -281,26 +284,8 @@ const fetchLatestKageVersion = async (options = {}) => {
281
284
  };
282
285
  exports.fetchLatestKageVersion = fetchLatestKageVersion;
283
286
 
284
- const fetchLatestBaileysVersion = async (options = {}) => {
285
- try {
286
- const { data } = await axios_1.default.get('https://registry.npmjs.org/kage-baileys', {
287
- ...options,
288
- responseType: 'json'
289
- });
290
- const versionStr = data.version;
291
- const [major, minor, patch] = versionStr.split('.').map(Number);
292
- return {
293
- version: [major, minor, patch],
294
- isLatest: true
295
- };
296
- } catch (error) {
297
- return {
298
- version: kage-baileys_version_json_1.version,
299
- isLatest: false,
300
- error
301
- };
302
- }
303
- };
287
+ // WhatsApp needs its WEB protocol version, not the npm library semver.
288
+ const fetchLatestBaileysVersion = async (options = {}) => fetchLatestWaWebVersion(options);
304
289
  exports.fetchLatestBaileysVersion = fetchLatestBaileysVersion;
305
290
 
306
291
  /**
@@ -310,14 +295,15 @@ exports.fetchLatestBaileysVersion = fetchLatestBaileysVersion;
310
295
  const fetchLatestWaWebVersion = async (options) => {
311
296
  try {
312
297
  const { data } = await axios_1.default.get('https://web.whatsapp.com/sw.js', {
298
+ timeout: 15000,
313
299
  ...options,
314
- responseType: 'json'
300
+ responseType: 'text'
315
301
  });
316
302
  const regex = /\\?"client_revision\\?":\s*(\d+)/;
317
303
  const match = data.match(regex);
318
304
  if (!(match === null || match === void 0 ? void 0 : match[1])) {
319
305
  return {
320
- version: kage-baileys_version_json_1.version,
306
+ version: kageVersionJson.version,
321
307
  isLatest: false,
322
308
  error: {
323
309
  message: 'Could not find client revision in the fetched content'
@@ -332,7 +318,7 @@ const fetchLatestWaWebVersion = async (options) => {
332
318
  }
333
319
  catch (error) {
334
320
  return {
335
- version: kage-baileys_version_json_1.version,
321
+ version: kageVersionJson.version,
336
322
  isLatest: false,
337
323
  error
338
324
  };
@@ -2,7 +2,7 @@
2
2
  // Estados recibidos por ESTA conexión. No consulta estados ocultos ni audiencias privadas.
3
3
  const { mkdirSync, readFileSync, writeFileSync, renameSync, chmodSync } = require('node:fs')
4
4
  const { resolve, join } = require('node:path')
5
- const { createHash, randomInt } = require('node:crypto')
5
+ const { createHash } = require('node:crypto')
6
6
  const reportError = (area, error) => console.error(`[${area}]`, error?.message || error)
7
7
  const caches = new WeakMap()
8
8
  const DAY = 86_400_000
@@ -155,8 +155,9 @@ function captureStatus(raw, conn) {
155
155
  viewOnce: !!(old?.viewOnce || info.wrappers.some(w => /viewOnce/.test(w))),
156
156
  routes: [...new Set([...(old?.routes || []), raw.key.remoteJid])]
157
157
  }
158
- state.entries.set(identity, entry); prune(state); persist(state)
159
- return entry
158
+ const stored = old ? Object.assign(old, entry) : entry
159
+ state.entries.set(identity, stored); prune(state); persist(state)
160
+ return stored
160
161
  }
161
162
  function listStatuses(conn, includeUnavailable = false) { const state = stateFor(conn); prune(state); return [...state.entries.values()].filter(e => includeUnavailable || !['deleted', 'expired'].includes(e.availability)).sort((a, b) => a.receivedAt - b.receivedAt) }
162
163
  function findStatus(conn, selector = '') {
@@ -164,27 +165,6 @@ function findStatus(conn, selector = '') {
164
165
  return listStatuses(conn, true).find(entry => entry.id === id || entry.sourceKey?.id === selector)
165
166
  }
166
167
  function clearStatuses(conn) { const state = stateFor(conn); const count = state.entries.size; for (const entry of state.entries.values()) retire(entry, 'expired', Date.now()); state.entries.clear(); persist(state); return count }
167
- function isStatusEmoji(text) {
168
- if (typeof text !== 'string' || text.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20e3/u.test(text)) return false
169
- return [...new Intl.Segmenter('es', { granularity: 'grapheme' }).segment(text)].length === 1
170
- }
171
- async function reactToStatus(conn, entry, emoji, options = {}) {
172
- if (!isStatusEmoji(emoji)) throw new Error('Escribe un solo emoji, por ejemplo ❤️, 👍🏽 o 👨‍👩‍👧‍👦.')
173
- if (!entry || ['deleted', 'expired'].includes(entry.availability) || entry.expiresAt <= Date.now()) throw new Error('Ese estado ya no está disponible en la caché.')
174
- if (!entry.sourceKey?.id || !/@(s\.whatsapp\.net|lid)$/.test(entry.author)) throw new Error('La notificación no incluye la clave y el autor originales; no es posible reaccionar con seguridad.')
175
- const self = normalizeJid(conn.user?.jid || conn.user?.id)
176
- if (entry.author === self || entry.author === normalizeJid(conn.user?.lid) || entry.sourceKey.fromMe) throw new Error('No se reacciona a los estados de la propia cuenta.')
177
- const state = stateFor(conn)
178
- if (state.busy) throw new Error('Hay otra reacción en curso; espera unos segundos.')
179
- const interval = Math.max(1000, Number(options.intervalMs ?? global.safety?.statusReactionIntervalMs) || 5000)
180
- if (Date.now() - state.last < interval) throw new Error('Espera unos segundos entre reacciones.')
181
- state.busy = true; state.last = Date.now()
182
- try {
183
- if (typeof conn.readMessages === 'function') await conn.readMessages([entry.sourceKey])
184
- await conn.sendMessage('status@broadcast', { react: { text: emoji, key: entry.sourceKey } }, { statusJidList: [entry.author] })
185
- entry.reactedWith = emoji; entry.reactedAt = Date.now(); state.last = Date.now(); persist(state)
186
- } finally { state.busy = false }
187
- }
188
168
  async function handleStatus(raw, conn, settings = {}) {
189
169
  if (raw?.key?.remoteJid !== 'status@broadcast') return false
190
170
  const entry = captureStatus(raw, conn)
@@ -192,27 +172,8 @@ async function handleStatus(raw, conn, settings = {}) {
192
172
  try { await require('./status-archive').handleStatusArchive(conn, entry, settings, settings.archiveOptions || {}) }
193
173
  catch (error) { reportError('status-archive', error) }
194
174
  }
195
- if (!entry || !settings.statusReaction || raw.key.fromMe || entry.reactedAt) return true
196
- const state = stateFor(conn)
197
- state.scheduled ||= new Set()
198
- if (state.scheduled.has(entry.id) || state.scheduled.size >= 30) return true
199
- state.scheduled.add(entry.id)
200
- const task = (state.reactionTail || Promise.resolve()).then(async () => {
201
- const gap = Math.max(1000, Number(settings.statusReactionIntervalMs ?? global.safety?.statusReactionIntervalMs) || 5000)
202
- const wait = Math.max(0, state.last + gap - Date.now())
203
- if (wait) await new Promise(resolve => setTimeout(resolve, wait))
204
- if (!settings.statusReaction || entry.reactedAt || ['deleted', 'expired'].includes(entry.availability)) return
205
- const pool = Array.isArray(settings.statusEmojis) ? settings.statusEmojis.filter(isStatusEmoji) : ['❤️', '🔥', '👍', '😊', '👏', '💜']
206
- const emoji = settings.statusEmojiMode === 'random' ? pool.length ? pool[randomInt(pool.length)] : '❤️' : settings.statusEmoji || '❤️'
207
- if (!isStatusEmoji(emoji)) return
208
- const self = normalizeJid(conn.user?.jid || conn.user?.id)
209
- if (!entry.author || entry.author === self || entry.author === normalizeJid(conn.user?.lid)) return
210
- await reactToStatus(conn, entry, emoji, { intervalMs: gap })
211
- })
212
- state.reactionTail = task.catch(error => reportError('reacciones-estados', error)).finally(() => state.scheduled.delete(entry.id))
213
175
  return true
214
176
  }
215
- async function waitStatusReactions(conn) { await stateFor(conn).reactionTail }
216
177
 
217
178
  exports.markStatusDeleted = markStatusDeleted;
218
179
  exports.flushStatuses = flushStatuses;
@@ -221,8 +182,5 @@ exports.captureStatus = captureStatus;
221
182
  exports.listStatuses = listStatuses;
222
183
  exports.findStatus = findStatus;
223
184
  exports.clearStatuses = clearStatuses;
224
- exports.isStatusEmoji = isStatusEmoji;
225
- exports.reactToStatus = reactToStatus;
226
185
  exports.handleStatus = handleStatus;
227
186
 
228
- exports.waitStatusReactions = waitStatusReactions;
package/lib/index.d.ts CHANGED
@@ -193,8 +193,8 @@ export declare const extractSyncdPatches: any;
193
193
  export declare const extractUrlFromText: any;
194
194
  export declare const extractVideoThumb: any;
195
195
  export declare const fetchLatestBaileysVersion: any;
196
+ export declare const fetchLatestKageVersion: any;
196
197
  export declare const fetchLatestWaWebVersion: any;
197
- export declare const fetchLatestWileysVersion: any;
198
198
  export declare const findStatus: any;
199
199
  export declare const flushStatuses: any;
200
200
  export declare const generateCodeBlockContent: any;
@@ -283,7 +283,6 @@ export declare const isJidUser: any;
283
283
  export declare const isKnownStatusContact: any;
284
284
  export declare const isLidUser: any;
285
285
  export declare const isRealMessage: any;
286
- export declare const isStatusEmoji: any;
287
286
  export declare const isWABusinessPlatform: any;
288
287
  export declare const jidDecode: any;
289
288
  export declare const jidEncode: any;
@@ -313,7 +312,6 @@ export declare const processContactAction: any;
313
312
  export declare const processHistoryMessage: any;
314
313
  export declare const processSyncAction: any;
315
314
  export declare const promiseTimeout: any;
316
- export declare const reactToStatus: any;
317
315
  export declare const readAndEmitEventStream: any;
318
316
  export declare const reduceBinaryNodeToDictionary: any;
319
317
  export declare const resendViewOnceMessage: any;
@@ -344,7 +342,6 @@ export declare const uploadFile: any;
344
342
  export declare const useMultiFileAuthState: any;
345
343
  export declare const version: any;
346
344
  export declare const vid2jpg: any;
347
- export declare const waitStatusReactions: any;
348
345
  export declare const writeRandomPadMax16: any;
349
346
  export declare const xmppPreKey: any;
350
347
  export declare const xmppSignedPreKey: any;
package/lib/index.js CHANGED
@@ -95,7 +95,7 @@ for (const __p of __kageExtraModules) {
95
95
  try { __kageMergeSafe(require(__p)); } catch (__e) { /* opcional */ }
96
96
  }
97
97
  try { __kageMergeSafe(require("./VoIP/index.js")); } catch (__e) { /* VoIP opcional */ }
98
- exports.KAGE_BUILD = Object.freeze({ name: "kage-baileys", version: "1.1.8" });
98
+ exports.KAGE_BUILD = Object.freeze({ name: "kage-baileys", version: "3.0.1" });
99
99
 
100
100
  __exportStar(require("./Utils/status-tools.js"), exports);
101
101
  __exportStar(require("./Utils/country-info.js"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kage-baileys",
3
- "version": "2.0.1",
3
+ "version": "3.0.1",
4
4
  "description": "Kage Baileys una libreria de WhatsApp.",
5
5
  "keywords": [
6
6
  "kage-baileys",
@@ -38,8 +38,9 @@
38
38
  "test": "node --test test/*.test.cjs",
39
39
  "check": "npm run lint && npm test",
40
40
  "example": "node Example/connection.cjs",
41
- "preinstall": "node engine-requirements.js",
42
- "gen:types": "node scripts/generate-public-types.cjs"
41
+ "gen:types": "node scripts/generate-public-types.cjs",
42
+ "check:engine": "node engine-requirements.js",
43
+ "prepublishOnly": "npm run gen:protobuf && npm run gen:types && npm run check"
43
44
  },
44
45
  "dependencies": {
45
46
  "@adiwajshing/keyed-db": "^0.2.4",
@@ -71,7 +72,7 @@
71
72
  },
72
73
  "devDependencies": {
73
74
  "@types/node": "^20.0.0",
74
- "protobufjs-cli": "2.7.1",
75
+ "protobufjs-cli": "^2.7.1",
75
76
  "typescript": "^5.8.2"
76
77
  },
77
78
  "peerDependencies": {
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+ const test = require('node:test');
3
+ const assert = require('node:assert/strict');
4
+ const axios = require('axios');
5
+ const { fetchLatestBaileysVersion, fetchLatestKageVersion } = require('../lib/Utils/generics');
6
+ const fallback = require('../lib/Defaults/wileys-version.json').version;
7
+ async function mocked(get, fn) {
8
+ const old = axios.get;
9
+ axios.get = get;
10
+ try { await fn(); } finally { axios.get = old; }
11
+ }
12
+ test('connection version is WhatsApp WEB revision, not npm library semver', async () => {
13
+ await mocked(async (url, options) => {
14
+ assert.equal(url,'https://web.whatsapp.com/sw.js');
15
+ assert.equal(options.responseType,'text');
16
+ assert.equal(options.timeout,15000);
17
+ return {data:'self.config={"client_revision":1043857761}'};
18
+ }, async () => {
19
+ assert.deepEqual(await fetchLatestBaileysVersion(),{version:[2,3000,1043857761],isLatest:true});
20
+ });
21
+ });
22
+ test('network failure retains the included WhatsApp protocol version and original error', async () => {
23
+ const error = new Error('offline test');
24
+ await mocked(async () => {throw error}, async () => {
25
+ const result = await fetchLatestBaileysVersion();
26
+ assert.deepEqual(result.version,fallback);assert.equal(result.isLatest,false);assert.equal(result.error,error);
27
+ });
28
+ });
29
+ test('malformed version response cannot pass an undefined or npm version to socket', async () => {
30
+ await mocked(async () => ({data:'<html>service unavailable</html>'}), async () => {
31
+ const result = await fetchLatestBaileysVersion();assert.deepEqual(result.version,fallback);assert.equal(result.isLatest,false);
32
+ });
33
+ });
34
+ test('package update helper requests a real npm version document', async () => {
35
+ await mocked(async url => {assert.equal(url,'https://registry.npmjs.org/kage-baileys/latest');return {data:{version:'2.0.1'}}},async()=>{
36
+ const result=await fetchLatestKageVersion();assert.deepEqual(result.version,[2,0,1]);assert.equal(result.isLatest,true);
37
+ });
38
+ });
39
+ test('package fallback is the installed library version, not a WhatsApp version', async () => {
40
+ await mocked(async()=>{throw new Error('offline')},async()=>{
41
+ const result=await fetchLatestKageVersion();assert.deepEqual(result.version,require('../package.json').version.split('.').map(Number));assert.equal(result.isLatest,false);
42
+ });
43
+ });
@@ -11,8 +11,6 @@ const countries = require('../lib/Utils/countries.json');
11
11
  const examples = require('libphonenumber-js/examples.mobile.json');
12
12
  const raw = (id = 'S1', author = '51987654321@s.whatsapp.net', message = { conversation: 'Hola' }) => ({ key: { remoteJid: 'status@broadcast', participant: author, id, fromMe: false }, messageTimestamp: Math.floor(Date.now()/1000), message });
13
13
  const socket = () => ({ user: { id: '34612345678:2@s.whatsapp.net' }, statusStorageDir: false, sendMessage: async () => ({}), readMessages: async () => {}, ev: new EventEmitter() });
14
- for (const emoji of ['❤️','🔥','👍','👍🏽','👨‍👩‍👧‍👦','😊','🇵🇪','1️⃣','💜','👏','🌈','🏳️‍🌈','🧑🏽‍💻','🫶','☀️','⭐','🐕','💯','😀','🎉']) test(`emoji accepted: ${emoji}`, () => assert.equal(api.isStatusEmoji(emoji), true));
15
- for (const value of ['', 'hola', '❤️🔥', 'a❤️', '123', null, 2, ' ', '\n', '👍 👍', 'x', '🇵🇪🇲🇽', '❤️'.repeat(30)]) test(`emoji rejected: ${JSON.stringify(value)}`, () => assert.equal(api.isStatusEmoji(value), false));
16
14
  for (const code of ['PE','MX','ES','AR','CO','CL','EC','BO','BR','US','CA','GB','FR','DE','IT','JP','KR','AU','NZ','IN','ZA','EG','MA','UY','PY','VE','CR','PA','DO','GT']) test(`offline country lookup ${code}`, () => {
17
15
  const info = api.getCountryInfo(require('libphonenumber-js').getExampleNumber(code, examples).number);
18
16
  assert.ok(info, code); assert.equal(info.code, code); assert.equal(info.name, countries[code].name);
@@ -100,23 +98,6 @@ test('status cache bounded to 500 entries', () => {
100
98
  const c = socket(); for (let i=0;i<510;i++) api.captureStatus(raw('id-'+i),c);
101
99
  assert.equal(api.listStatuses(c).length,500); assert.equal(api.clearStatuses(c),500);
102
100
  });
103
- test('manual reaction uses status route, real message key and emoji', async () => {
104
- const c = socket(); let sent; c.sendMessage = async (...args) => { sent=args; };
105
- const e = api.captureStatus(raw(),c); await api.reactToStatus(c,e,'👍🏽');
106
- assert.equal(sent[0],'status@broadcast'); assert.equal(sent[1].react.text,'👍🏽'); assert.deepEqual(sent[2].statusJidList,[e.author]);
107
- });
108
- test('cannot react to deleted or self statuses', async () => {
109
- const c = socket(); const e = api.captureStatus(raw(),c); api.markStatusDeleted(c,e.sourceKey);
110
- await assert.rejects(api.reactToStatus(c,e,'🔥'), /disponible/);
111
- const self = api.captureStatus(raw('self','34612345678@s.whatsapp.net'),c);
112
- await assert.rejects(api.reactToStatus(c,self,'🔥'), /propia cuenta/);
113
- });
114
- test('random reaction uses configured emoji pool and deduplicates delivery', async () => {
115
- const c = socket(); const sent=[]; c.sendMessage=async (...args) => { sent.push(args); };
116
- const settings = { statusReaction:true,statusEmojiMode:'random',statusEmojis:['🔥','💜'] };
117
- await api.handleStatus(raw(),c,settings); await api.handleStatus(raw(),c,settings); await api.waitStatusReactions(c);
118
- assert.equal(sent.length,1); assert.ok(settings.statusEmojis.includes(sent[0][1].react.text));
119
- });
120
101
  test('socket bindings install and detach status listeners without duplicates', async () => {
121
102
  const c = socket(); api.installKageTools(c); api.installKageTools(c);
122
103
  const stop = c.enableStatusTracking(); assert.equal(c.enableStatusTracking(),stop); assert.equal(c.ev.listenerCount('messages.upsert'),1);
@@ -140,14 +121,6 @@ test('browser presets support documented calls and missing browser', () => {
140
121
  assert.equal(api.Browsers().length,3); assert.doesNotThrow(()=>api.getPlatformId());
141
122
  });
142
123
 
143
- test('automatic reaction queue processes a burst with configured pacing', async () => {
144
- const c = socket(); const sent=[];
145
- c.sendMessage = async (...args) => { sent.push({ time: Date.now(), args }); };
146
- const settings = { statusReaction: true, statusEmoji: '🔥', statusReactionIntervalMs: 1000 };
147
- await api.handleStatus(raw('first'), c, settings); await api.handleStatus(raw('second'), c, settings);
148
- await api.waitStatusReactions(c);
149
- assert.equal(sent.length,2); assert.ok(sent[1].time - sent[0].time >= 990);
150
- });
151
124
  test('detaching status tracking never overwrites persisted activation setting', () => {
152
125
  const c = socket(); api.installKageTools(c); const settings = { statusReaction: true };
153
126
  const stop = c.enableStatusTracking(settings); stop(); stop();
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ const test = require('node:test');
3
+ const assert = require('node:assert/strict');
4
+ const { EventEmitter } = require('node:events');
5
+ const api = require('../lib');
6
+ const raw = id => ({ key: { remoteJid: 'status@broadcast', participant: '51987654321@s.whatsapp.net', id }, messageTimestamp: Math.floor(Date.now()/1000), message: { conversation: 'estado' } });
7
+ const socket = () => ({ user: { id: '34612345678@s.whatsapp.net' }, statusStorageDir: false, ev: new EventEmitter(), sent: [], async sendMessage(...args) { this.sent.push(args); } });
8
+ test('Baileys has no application-level status reaction exports or socket method', () => {
9
+ for (const name of ['reactToStatus', 'waitStatusReactions', 'isStatusEmoji']) assert.equal(api[name], undefined);
10
+ const conn = api.installKageTools(socket());
11
+ assert.equal(conn.reactToReceivedStatus, undefined);
12
+ });
13
+ test('statusReaction settings never send reactions from Baileys', async () => {
14
+ const conn = socket();
15
+ await api.handleStatus(raw('A'), conn, { statusReaction: true, statusEmoji: '🔥' });
16
+ await api.handleStatus(raw('A'), conn, { statusReaction: true });
17
+ await new Promise(resolve => setImmediate(resolve));
18
+ assert.equal(conn.sent.length, 0);
19
+ assert.equal(api.listStatuses(conn).length, 1);
20
+ });
21
+ test('optional status tracking only records received statuses', async () => {
22
+ const conn = api.installKageTools(socket());
23
+ const stop = conn.enableStatusTracking({ statusReaction: true });
24
+ conn.ev.emit('messages.upsert', { type: 'notify', messages: [raw('B')] });
25
+ await new Promise(resolve => setImmediate(resolve));
26
+ assert.equal(conn.sent.length, 0);
27
+ assert.equal(conn.getReceivedStatuses().length, 1);
28
+ stop();
29
+ assert.equal(conn.ev.listenerCount('messages.upsert'), 0);
30
+ });
31
+ test('official status reaction schema survives encoding and decoding', () => {
32
+ const source = { reactionMessage: { key: { remoteJid: 'status@broadcast', participant: '51987654321@s.whatsapp.net', id: 'ORIGINAL', fromMe: false }, text: '❤️', senderTimestampMs: '1770000000000' } };
33
+ const message = api.proto.Message.fromObject(source);
34
+ assert.equal(api.proto.Message.verify(message), null);
35
+ const decoded = api.proto.Message.decode(api.proto.Message.encode(message).finish());
36
+ assert.equal(decoded.reactionMessage.text, '❤️');
37
+ assert.equal(decoded.reactionMessage.key.id, 'ORIGINAL');
38
+ assert.equal(decoded.reactionMessage.key.remoteJid, 'status@broadcast');
39
+ assert.equal(api.proto.MessageAssociation.AssociationType.STATUS_REACTION, 18);
40
+ });