kage-baileys 3.0.0 → 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.
@@ -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); };
@@ -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": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Kage Baileys una libreria de WhatsApp.",
5
5
  "keywords": [
6
6
  "kage-baileys",
@@ -39,7 +39,8 @@
39
39
  "check": "npm run lint && npm test",
40
40
  "example": "node Example/connection.cjs",
41
41
  "gen:types": "node scripts/generate-public-types.cjs",
42
- "check:engine": "node engine-requirements.js"
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": {
@@ -38,6 +38,6 @@ test('package update helper requests a real npm version document', async () => {
38
38
  });
39
39
  test('package fallback is the installed library version, not a WhatsApp version', async () => {
40
40
  await mocked(async()=>{throw new Error('offline')},async()=>{
41
- const result=await fetchLatestKageVersion();assert.deepEqual(result.version,[1,1,8]);assert.equal(result.isLatest,false);
41
+ const result=await fetchLatestKageVersion();assert.deepEqual(result.version,require('../package.json').version.split('.').map(Number));assert.equal(result.isLatest,false);
42
42
  });
43
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
+ });