kage-baileys 3.0.2 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Example/connection.cjs +0 -4
- package/lib/Socket/chats.js +2 -2
- package/lib/Socket/messages-recv.js +5 -9
- package/lib/Utils/automation-tools.js +16 -51
- package/lib/Utils/status-tools.js +2 -4
- package/lib/Utils/view-once-compat.js +5 -13
- package/lib/index.d.ts +0 -7
- package/lib/index.js +1 -5
- package/package.json +2 -1
- package/lib/Utils/message-inspector.js +0 -105
- package/lib/Utils/status-refresh.js +0 -37
- package/lib/Utils/terminal-banner.js +0 -32
- package/test/recepcion-reparada.test.cjs +0 -98
package/Example/connection.cjs
CHANGED
|
@@ -29,7 +29,6 @@ async function connect() {
|
|
|
29
29
|
auth ||= await useMultiFileAuthState(path.join(dataDir, 'auth'));
|
|
30
30
|
sock = makeWASocket({
|
|
31
31
|
logger,
|
|
32
|
-
kageTools: { banner: true, eventTracking: true, statusTracking: true },
|
|
33
32
|
auth: { creds: auth.state.creds, keys: makeCacheableSignalKeyStore(auth.state.keys, logger) },
|
|
34
33
|
browser: Browsers.macOS('Desktop'),
|
|
35
34
|
syncFullHistory: process.env.KAGE_FULL_HISTORY !== '0',
|
|
@@ -41,9 +40,6 @@ async function connect() {
|
|
|
41
40
|
getMessage: async key => (await store.loadMessage(key.remoteJid, key.id))?.message
|
|
42
41
|
});
|
|
43
42
|
store.bind(sock.ev);
|
|
44
|
-
sock.ev.on('kage.event', event => {
|
|
45
|
-
if (['call', 'unsupported'].includes(event.kind)) logger.info(event, 'Evento detectado');
|
|
46
|
-
});
|
|
47
43
|
sock.ev.on('creds.update', () => {
|
|
48
44
|
credsWrite = credsWrite.then(() => auth.saveCreds()).catch(error => {
|
|
49
45
|
logger.error({ error }, 'Error de escritura de credenciales; se detiene para no perder la sesión');
|
package/lib/Socket/chats.js
CHANGED
|
@@ -737,7 +737,7 @@ const makeChatsSocket = (config) => {
|
|
|
737
737
|
return jid;
|
|
738
738
|
}
|
|
739
739
|
if ((0, WABinary_1.isLidUser)(jid)) {
|
|
740
|
-
return (0, WABinary_1.
|
|
740
|
+
return (0, WABinary_1.lidToJid)(jid);
|
|
741
741
|
}
|
|
742
742
|
return jid;
|
|
743
743
|
};
|
|
@@ -770,7 +770,7 @@ const makeChatsSocket = (config) => {
|
|
|
770
770
|
}
|
|
771
771
|
return keys;
|
|
772
772
|
};
|
|
773
|
-
const getDedupState = (msg) =>
|
|
773
|
+
const getDedupState = (msg) => msg.messageStubType === WAProto_1.proto.WebMessageInfo.StubType.CIPHERTEXT
|
|
774
774
|
? 'ciphertext'
|
|
775
775
|
: 'final';
|
|
776
776
|
const getPendingDedupState = (dedupState) => dedupState === 'final' ? 'processing' : dedupState;
|
|
@@ -766,8 +766,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
766
766
|
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
767
767
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
768
768
|
if (encNode && encNode.attrs.type === 'msmsg') {
|
|
769
|
-
logger.
|
|
770
|
-
ev.emit('kage.unsupported-message', { id: node.attrs.id, chat: node.attrs.from, encryption: 'msmsg', reason: 'UNSUPPORTED_ENCRYPTION' });
|
|
769
|
+
logger.debug({ key: node.attrs.key }, 'ignored msmsg');
|
|
771
770
|
await sendMessageAck(node);
|
|
772
771
|
return;
|
|
773
772
|
}
|
|
@@ -823,9 +822,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
823
822
|
// no type in the receipt => message delivered
|
|
824
823
|
let type = undefined;
|
|
825
824
|
if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
|
|
826
|
-
|
|
827
|
-
const mapped = node.attrs.participant_pn || await Promise.resolve(signalRepository.lidMapping?.getPNForLID?.(originalLid)).catch(() => undefined);
|
|
828
|
-
if (mapped) { msg.key.participantAlt = originalLid; msg.key.participant = mapped; }
|
|
825
|
+
msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
|
|
829
826
|
}
|
|
830
827
|
if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid) && ((_f = (_e = (_d = (_c = msg.message) === null || _c === void 0 ? void 0 : _c.extendedTextMessage) === null || _d === void 0 ? void 0 : _d.contextInfo) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.endsWith('@lid'))) {
|
|
831
828
|
if (msg.message.extendedTextMessage.contextInfo) {
|
|
@@ -838,10 +835,9 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
838
835
|
void warmupGroupParticipants(msg.key.remoteJid);
|
|
839
836
|
}
|
|
840
837
|
if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
||
|
|
844
|
-
if (mapped) { msg.key.remoteJidAlt = originalLid; msg.key.remoteJid = mapped; }
|
|
838
|
+
msg.key.remoteJid = node.attrs.sender_pn
|
|
839
|
+
|| node.attrs.peer_recipient_pn
|
|
840
|
+
|| (0, WABinary_1.lidToJid)(msg.key.remoteJid);
|
|
845
841
|
}
|
|
846
842
|
let participant = msg.key.participant;
|
|
847
843
|
if (category === 'peer') { // special peer message
|
|
@@ -8,7 +8,7 @@ function unwrapNormalMedia(raw) {
|
|
|
8
8
|
let msg = raw?.message;
|
|
9
9
|
for (let i = 0; i < 12 && msg; i++) {
|
|
10
10
|
if (msg.viewOnceMessage || msg.viewOnceMessageV2 || msg.viewOnceMessageV2Extension) throw new Error('VIEW_ONCE_PROTECTED: pide al remitente una copia normal con su consentimiento.');
|
|
11
|
-
const wrapper = ['
|
|
11
|
+
const wrapper = ['ephemeralMessage', 'documentWithCaptionMessage'].find(k => msg[k]?.message);
|
|
12
12
|
if (wrapper) { msg = msg[wrapper].message; continue; }
|
|
13
13
|
for (const type of ['imageMessage', 'videoMessage', 'audioMessage', 'documentMessage', 'stickerMessage']) {
|
|
14
14
|
if (!msg[type]) continue;
|
|
@@ -33,32 +33,16 @@ async function downloadNormalMedia(conn, raw, options = {}) {
|
|
|
33
33
|
const chunks = [];
|
|
34
34
|
let size = 0;
|
|
35
35
|
const job = (async () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (stopped) throw new Error('MEDIA_TIMEOUT');
|
|
44
|
-
const data = Buffer.from(chunk); size += data.length;
|
|
45
|
-
if (size > maxBytes) throw new Error('MEDIA_TOO_LARGE: se interrumpió al alcanzar el límite.');
|
|
46
|
-
chunks.push(data);
|
|
47
|
-
}
|
|
48
|
-
if (!size) throw new Error('EMPTY_MEDIA: WhatsApp no entregó datos.');
|
|
49
|
-
return { buffer: Buffer.concat(chunks, size), type: media.type, mimetype: media.value.mimetype || 'application/octet-stream', caption: media.value.caption || '', bytes: size };
|
|
50
|
-
} catch (error) {
|
|
51
|
-
stream?.destroy?.();
|
|
52
|
-
const code = error?.output?.statusCode || error?.response?.status || error?.statusCode;
|
|
53
|
-
const retry = [403,404,408,410,429,500,502,503,504].includes(Number(code)) || /EMPTY_MEDIA|ECONNRESET|ETIMEDOUT|ENOTFOUND/.test(error?.message || error?.code || '');
|
|
54
|
-
if (attempt || stopped || !retry) throw error;
|
|
55
|
-
if ([403,404,410].includes(Number(code)) && typeof conn.updateMediaMessage === 'function') {
|
|
56
|
-
const updated = await conn.updateMediaMessage(target);
|
|
57
|
-
if (updated?.message) target = updated;
|
|
58
|
-
}
|
|
59
|
-
if (stopped) throw new Error('MEDIA_TIMEOUT');
|
|
60
|
-
}
|
|
36
|
+
stream = await download({ ...raw, message: media.message }, 'stream', { options: { timeout: timeoutMs } }, { logger, reuploadRequest: conn.updateMediaMessage?.bind(conn) });
|
|
37
|
+
if (stopped) { stream.destroy?.(); throw new Error('MEDIA_TIMEOUT'); }
|
|
38
|
+
for await (const chunk of stream) {
|
|
39
|
+
if (stopped) throw new Error('MEDIA_TIMEOUT');
|
|
40
|
+
const data = Buffer.from(chunk); size += data.length;
|
|
41
|
+
if (size > maxBytes) throw new Error('MEDIA_TOO_LARGE: se interrumpió al alcanzar el límite.');
|
|
42
|
+
chunks.push(data);
|
|
61
43
|
}
|
|
44
|
+
if (!size) throw new Error('EMPTY_MEDIA: WhatsApp no entregó datos.');
|
|
45
|
+
return { buffer: Buffer.concat(chunks, size), type: media.type, mimetype: media.value.mimetype || 'application/octet-stream', caption: media.value.caption || '', bytes: size };
|
|
62
46
|
})();
|
|
63
47
|
try {
|
|
64
48
|
return await Promise.race([job, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('MEDIA_TIMEOUT: tiempo de espera agotado.')), timeoutMs); })]);
|
|
@@ -112,7 +96,7 @@ function handleAutoMedia(raw, conn, settings = {}, options = {}) {
|
|
|
112
96
|
while (state.seen.size > 500) state.seen.delete(state.seen.keys().next().value);
|
|
113
97
|
state.pending++;
|
|
114
98
|
const job = state.tail.then(() => saveNormalMedia(conn, raw, options));
|
|
115
|
-
state.tail = job.catch(() => {
|
|
99
|
+
state.tail = job.catch(() => {}).finally(() => { state.pending--; });
|
|
116
100
|
return job;
|
|
117
101
|
}
|
|
118
102
|
async function sendAIResponse(conn, jid, text, options = {}) {
|
|
@@ -129,12 +113,6 @@ function installKageTools(conn, options = {}) {
|
|
|
129
113
|
Object.defineProperty(conn, '__kageToolsInstalled', { value: true });
|
|
130
114
|
const status = require('./status-tools');
|
|
131
115
|
const country = require('./country-info');
|
|
132
|
-
conn.inspectMessage = raw => require('./message-inspector').inspectMessage(raw);
|
|
133
|
-
conn.getMessageDiagnostics = () => require('./message-inspector').getMessageDiagnostics(conn);
|
|
134
|
-
conn.enableEventTracking = opts => require('./message-inspector').installMessageInspector(conn, opts);
|
|
135
|
-
conn.refreshReceivedStatuses = opts => require('./status-refresh').refreshReceivedStatuses(conn, opts);
|
|
136
|
-
if (options.eventTracking) conn.enableEventTracking(options.eventOptions || {});
|
|
137
|
-
if (options.banner) require('./terminal-banner').showKageBanner();
|
|
138
116
|
conn.getReceivedStatuses = (includeUnavailable = false) => status.listStatuses(conn, includeUnavailable);
|
|
139
117
|
conn.getCountryForJid = jid => country.getCountryForJid(conn, jid);
|
|
140
118
|
conn.downloadNormalMedia = (raw, opts) => downloadNormalMedia(conn, raw, opts);
|
|
@@ -151,25 +129,17 @@ function installKageTools(conn, options = {}) {
|
|
|
151
129
|
conn.enableMediaAutomation = (settings = {}) => {
|
|
152
130
|
if (conn.__kageStopMedia) return conn.__kageStopMedia;
|
|
153
131
|
let active = true;
|
|
154
|
-
const recent = new Map();
|
|
155
132
|
const scoped = new Proxy(settings, { get(target, key) { return !active && ['autoDownloadChats','autoViewOnceChats','autoViewOnceConsent'].includes(key) ? undefined : target[key]; } });
|
|
156
133
|
const upsert = event => {
|
|
157
|
-
if (event?.type
|
|
134
|
+
if (event?.type !== 'notify') return;
|
|
158
135
|
for (const raw of event.messages || []) {
|
|
159
|
-
const id = JSON.stringify([raw.key?.remoteJid, raw.key?.id]);
|
|
160
|
-
if (raw.messageTimestamp) recent.set(id, raw.messageTimestamp);
|
|
161
|
-
while(recent.size > 1000) recent.delete(recent.keys().next().value);
|
|
162
136
|
handleAutoMedia(raw, conn, scoped).catch(error => conn.logger?.warn?.({ error: error.message }, 'media automation'));
|
|
163
137
|
require('./view-once-compat').handleAutoViewOnce(raw, conn, scoped).catch(error => conn.logger?.warn?.({ error: error.message }, 'view once automation'));
|
|
164
138
|
}
|
|
165
139
|
};
|
|
166
|
-
const
|
|
167
|
-
const timestamp = item.update.messageTimestamp || recent.get(JSON.stringify([item.key?.remoteJid, item.key?.id]));
|
|
168
|
-
if (timestamp) upsert({ type: 'append', messages: [{ ...item.update, key: item.key, messageTimestamp: timestamp }] });
|
|
169
|
-
} };
|
|
170
|
-
const stop = () => { active = false; recent.clear(); conn.ev.off('messages.update', updated); conn.ev.off('messages.upsert', upsert); conn.ev.off('connection.update', closed); delete conn.__kageStopMedia; };
|
|
140
|
+
const stop = () => { active = false; conn.ev.off('messages.upsert', upsert); conn.ev.off('connection.update', closed); delete conn.__kageStopMedia; };
|
|
171
141
|
const closed = update => { if (update.connection === 'close') stop(); };
|
|
172
|
-
conn.ev.on('messages.upsert', upsert); conn.ev.on('
|
|
142
|
+
conn.ev.on('messages.upsert', upsert); conn.ev.on('connection.update', closed);
|
|
173
143
|
conn.__kageStopMedia = stop;
|
|
174
144
|
return stop;
|
|
175
145
|
};
|
|
@@ -181,16 +151,11 @@ function installKageTools(conn, options = {}) {
|
|
|
181
151
|
let active = true;
|
|
182
152
|
const scopedSettings = new Proxy(settings, { get(target, key) { return key === 'statusSave' ? active && target[key] : target[key]; } });
|
|
183
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')); };
|
|
184
|
-
const history = event => upsert(event);
|
|
185
154
|
const deleted = event => { for (const key of event?.keys || []) status.markStatusDeleted(conn, key); };
|
|
186
|
-
const updated = items => { for (const item of items || [])
|
|
187
|
-
if (item.key?.remoteJid === 'status@broadcast' && item.update?.message === null) status.markStatusDeleted(conn, item.key);
|
|
188
|
-
else if (item.update?.message) upsert({ messages: [{ ...item.update, key: item.key }] });
|
|
189
|
-
} };
|
|
155
|
+
const updated = items => { for (const item of items || []) if (item.key?.remoteJid === 'status@broadcast' && item.update?.message === null) status.markStatusDeleted(conn, item.key); };
|
|
190
156
|
const closed = update => { if (update.connection === 'close') stop?.(); };
|
|
191
157
|
const timer = setInterval(() => status.flushStatuses(conn), 60000); timer.unref?.();
|
|
192
158
|
conn.ev.on('messages.upsert', upsert);
|
|
193
|
-
conn.ev.on('messaging-history.set', history);
|
|
194
159
|
conn.ev.on('messages.delete', deleted);
|
|
195
160
|
conn.ev.on('messages.update', updated);
|
|
196
161
|
conn.ev.on('connection.update', closed);
|
|
@@ -198,7 +163,7 @@ function installKageTools(conn, options = {}) {
|
|
|
198
163
|
if (!active) return;
|
|
199
164
|
active = false;
|
|
200
165
|
clearInterval(timer);
|
|
201
|
-
conn.ev.off('messages.upsert', upsert); conn.ev.off('
|
|
166
|
+
conn.ev.off('messages.upsert', upsert); conn.ev.off('messages.delete', deleted);
|
|
202
167
|
conn.ev.off('messages.update', updated); conn.ev.off('connection.update', closed);
|
|
203
168
|
status.flushStatuses(conn); stop = undefined;
|
|
204
169
|
};
|
|
@@ -19,10 +19,8 @@ function persist(state) {
|
|
|
19
19
|
}
|
|
20
20
|
function stateFor(conn) {
|
|
21
21
|
let state = caches.get(conn)
|
|
22
|
-
const identity = normalizeJid(conn.user?.jid || conn.user?.id)
|
|
23
|
-
if (state && state.account !== identity) { persist(state); caches.delete(conn); state = null }
|
|
24
22
|
if (!state) {
|
|
25
|
-
state = { entries: new Map(), next: 1, last: 0, busy: false
|
|
23
|
+
state = { entries: new Map(), next: 1, last: 0, busy: false }
|
|
26
24
|
const account = normalizeJid(conn.user?.jid || conn.user?.id)
|
|
27
25
|
if (account && conn.statusStorageDir !== false) {
|
|
28
26
|
state.dir = resolve(conn.statusStorageDir || 'data/status')
|
|
@@ -73,7 +71,7 @@ function statusContent(message = {}) {
|
|
|
73
71
|
const contexts = []
|
|
74
72
|
for (let i = 0; i < 12 && current; i++) {
|
|
75
73
|
for (const value of Object.values(current)) if (value?.contextInfo) contexts.push(value.contextInfo)
|
|
76
|
-
const key = ['
|
|
74
|
+
const key = ['ephemeralMessage', 'viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension',
|
|
77
75
|
'documentWithCaptionMessage', 'statusMentionMessage', 'groupStatusMentionMessage', 'groupStatusMessage', 'groupStatusMessageV2']
|
|
78
76
|
.find(name => current[name]?.message)
|
|
79
77
|
if (!key) break
|
|
@@ -7,7 +7,7 @@ function getViewOnceContent(raw) {
|
|
|
7
7
|
let message = raw?.message ?? raw;
|
|
8
8
|
let viewOnce = false;
|
|
9
9
|
for (let i = 0; i < 12 && message && typeof message === 'object'; i++) {
|
|
10
|
-
const wrapper = ['
|
|
10
|
+
const wrapper = ['ephemeralMessage', 'documentWithCaptionMessage', 'viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension'].find(k => message[k]?.message);
|
|
11
11
|
if (wrapper) { viewOnce ||= wrapper.startsWith('viewOnce'); message = message[wrapper].message; continue; }
|
|
12
12
|
const type = types.find(k => message[k]);
|
|
13
13
|
if (!type) return null;
|
|
@@ -40,19 +40,12 @@ async function resendViewOnceMessage(conn, raw, options = {}) {
|
|
|
40
40
|
if (field === 'document') body.fileName = value.fileName || 'archivo';
|
|
41
41
|
return conn.sendMessage(options.jid || raw.key.remoteJid, body, options.quoted ? { quoted: options.quoted } : {});
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function handleAutoViewOnce(raw, conn, settings = {}, options = {}) {
|
|
44
44
|
const chat = raw?.key?.remoteJid;
|
|
45
|
-
const
|
|
46
|
-
const aliases = new Set([chat, raw?.key?.remoteJidAlt].filter(Boolean).map(normalize));
|
|
47
|
-
for (const jid of [...aliases]) {
|
|
48
|
-
if (jid.endsWith('@lid')) {
|
|
49
|
-
try { const pn = await conn.signalRepository?.lidMapping?.getPNForLID?.(jid); if (pn) aliases.add(normalize(pn)); } catch {}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const active = () => Array.isArray(settings.autoViewOnceChats) && settings.autoViewOnceChats.some(jid => aliases.has(normalize(jid)));
|
|
45
|
+
const active = () => Array.isArray(settings.autoViewOnceChats) && settings.autoViewOnceChats.includes(chat);
|
|
53
46
|
// Preserve the explicitly consented archive API for existing library clients.
|
|
54
47
|
if (!active()) return require('./consented-media').handleConsentedAutoViewOnce(raw, conn, settings, options);
|
|
55
|
-
if (!raw?.key?.id ||
|
|
48
|
+
if (!raw?.key?.id || raw.key.fromMe || !/@(s\.whatsapp\.net|lid|g\.us)$/.test(chat || '') || !getViewOnceContent(raw)?.viewOnce) return Promise.resolve(null);
|
|
56
49
|
const timestamp = Number(raw.messageTimestamp?.toNumber?.() ?? raw.messageTimestamp ?? 0) * 1000;
|
|
57
50
|
if (!timestamp || Date.now() - timestamp > 300000 || timestamp > Date.now() + 60000) return Promise.resolve(null);
|
|
58
51
|
let state = queues.get(conn);
|
|
@@ -65,8 +58,7 @@ async function handleAutoViewOnce(raw, conn, settings = {}, options = {}) {
|
|
|
65
58
|
state.seen.set(id, now); state.pending++;
|
|
66
59
|
while (state.seen.size > 500) state.seen.delete(state.seen.keys().next().value);
|
|
67
60
|
const task = state.tail.then(() => active() ? resendViewOnceMessage(conn, raw, { ...options, jid: chat, shouldSend: active }) : null);
|
|
68
|
-
|
|
69
|
-
state.tail = task.catch(() => { state.seen.delete(id); }).finally(() => { state.pending--; });
|
|
61
|
+
state.tail = task.catch(() => {}).finally(() => { state.pending--; });
|
|
70
62
|
return task;
|
|
71
63
|
}
|
|
72
64
|
exports.getViewOnceContent = getViewOnceContent;
|
package/lib/index.d.ts
CHANGED
|
@@ -249,7 +249,6 @@ export declare const getHistoryMsg: any;
|
|
|
249
249
|
export declare const getHttpStream: any;
|
|
250
250
|
export declare const getKeyAuthor: any;
|
|
251
251
|
export declare const getMediaKeys: any;
|
|
252
|
-
export declare const getMessageDiagnostics: any;
|
|
253
252
|
export declare const getMessageReportingToken: any;
|
|
254
253
|
export declare const getNextPreKeys: any;
|
|
255
254
|
export declare const getNextPreKeysNode: any;
|
|
@@ -273,9 +272,7 @@ export declare const hkdf: any;
|
|
|
273
272
|
export declare const hkdfInfoKey: any;
|
|
274
273
|
export declare const hmacSign: any;
|
|
275
274
|
export declare const initAuthCreds: any;
|
|
276
|
-
export declare const inspectMessage: any;
|
|
277
275
|
export declare const installKageTools: any;
|
|
278
|
-
export declare const installMessageInspector: any;
|
|
279
276
|
export declare const isJidBot: any;
|
|
280
277
|
export declare const isJidBroadcast: any;
|
|
281
278
|
export declare const isJidGroup: any;
|
|
@@ -317,18 +314,14 @@ export declare const processSyncAction: any;
|
|
|
317
314
|
export declare const promiseTimeout: any;
|
|
318
315
|
export declare const readAndEmitEventStream: any;
|
|
319
316
|
export declare const reduceBinaryNodeToDictionary: any;
|
|
320
|
-
export declare const refreshReceivedStatuses: any;
|
|
321
|
-
export declare const rememberMessage: any;
|
|
322
317
|
export declare const resendViewOnceMessage: any;
|
|
323
318
|
export declare const saveConsentedViewOnce: any;
|
|
324
319
|
export declare const saveNormalMedia: any;
|
|
325
320
|
export declare const saveReceivedStatus: any;
|
|
326
|
-
export declare const say: any;
|
|
327
321
|
export declare const sendAIResponse: any;
|
|
328
322
|
export declare const sha256: any;
|
|
329
323
|
export declare const shouldIncludeReportingToken: any;
|
|
330
324
|
export declare const shouldIncrementChatUnread: any;
|
|
331
|
-
export declare const showKageBanner: any;
|
|
332
325
|
export declare const signedKeyPair: any;
|
|
333
326
|
export declare const statusContent: any;
|
|
334
327
|
export declare const toBuffer: 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: "3.0.1"
|
|
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);
|
|
@@ -104,7 +104,3 @@ __exportStar(require("./Utils/automation-tools.js"), exports);
|
|
|
104
104
|
__exportStar(require("./Utils/consented-media.js"), exports);
|
|
105
105
|
__exportStar(require("./Utils/view-once-compat.js"), exports);
|
|
106
106
|
__exportStar(require("./Utils/status-archive.js"), exports);
|
|
107
|
-
|
|
108
|
-
__exportStar(require("./Utils/message-inspector.js"), exports);
|
|
109
|
-
__exportStar(require("./Utils/status-refresh.js"), exports);
|
|
110
|
-
__exportStar(require("./Utils/terminal-banner.js"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kage-baileys",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"description": "Kage Baileys una libreria de WhatsApp.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kage-baileys",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"form-data": "^4.0.0",
|
|
58
58
|
"gradient-string": "^2.0.2",
|
|
59
59
|
"jimp": "1.6.1",
|
|
60
|
+
"kage-baileys": "^3.0.2",
|
|
60
61
|
"libphonenumber-js": "^1.10.20",
|
|
61
62
|
"libsignal": "^6.0.0",
|
|
62
63
|
"link-preview-js": "4.0.4",
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const states = new WeakMap();
|
|
3
|
-
const wrappers = ['deviceSentMessage', 'ephemeralMessage', 'viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension', 'documentWithCaptionMessage', 'editedMessage', 'statusMentionMessage', 'groupStatusMentionMessage', 'groupStatusMessage', 'groupStatusMessageV2'];
|
|
4
|
-
const labels = { conversation:'Texto', extendedTextMessage:'Texto', imageMessage:'Imagen', videoMessage:'Video', audioMessage:'Audio', documentMessage:'Documento', stickerMessage:'Sticker', reactionMessage:'Reacción', encReactionMessage:'Reacción cifrada', contactMessage:'Contacto', contactsArrayMessage:'Contactos', locationMessage:'Ubicación', liveLocationMessage:'Ubicación en vivo', pollCreationMessage:'Encuesta', pollCreationMessageV2:'Encuesta', pollCreationMessageV3:'Encuesta', pollUpdateMessage:'Voto de encuesta', protocolMessage:'Protocolo', interactiveMessage:'Interactivo', interactiveResponseMessage:'Respuesta interactiva', buttonsResponseMessage:'Respuesta de botón', listResponseMessage:'Respuesta de lista', eventMessage:'Evento programado', call:'Llamada', scheduledCallCreationMessage:'Llamada programada' };
|
|
5
|
-
const clean = jid => String(jid || '').replace(/:\d+@/, '@');
|
|
6
|
-
const numeric = n => { const value = Number(n?.toNumber?.() ?? n ?? 0); return Number.isFinite(value) ? value : 0; };
|
|
7
|
-
function inspectMessage(raw = {}) {
|
|
8
|
-
let message = raw.message || raw.fakeObj?.message || {};
|
|
9
|
-
const layers = [];
|
|
10
|
-
for (let i=0; i<16; i++) {
|
|
11
|
-
const wrapper = wrappers.find(k => message[k]?.message);
|
|
12
|
-
if (!wrapper) break;
|
|
13
|
-
layers.push(wrapper); message = message[wrapper].message;
|
|
14
|
-
}
|
|
15
|
-
const keys = Object.keys(message).filter(k => message[k] != null && !['messageContextInfo','senderKeyDistributionMessage','fastRatchetKeySenderKeyDistributionMessage'].includes(k));
|
|
16
|
-
const type = keys.find(k => labels[k]) || keys[0] || '';
|
|
17
|
-
const value = typeof message[type] === 'object' ? message[type] : {};
|
|
18
|
-
const context = value?.contextInfo || {};
|
|
19
|
-
const key = raw.key || raw.fakeObj?.key || {};
|
|
20
|
-
const chat = clean(key.remoteJid || raw.chat);
|
|
21
|
-
const group = chat.endsWith('@g.us');
|
|
22
|
-
const stubType = numeric(raw.messageStubType);
|
|
23
|
-
const encrypted = stubType === 2 && !type;
|
|
24
|
-
const viewOnce = layers.some(k=>k.startsWith('viewOnce')) || value?.viewOnce === true;
|
|
25
|
-
const text = typeof message[type] === 'string' ? message[type] : value?.text || value?.caption || '';
|
|
26
|
-
return {
|
|
27
|
-
id: key.id || raw.id || null, chat, chatAlt: clean(key.remoteJidAlt) || null,
|
|
28
|
-
sender: clean(key.participant || raw.participant || raw.sender || (!key.fromMe && !group && chat !== 'status@broadcast' ? chat : '')) || null,
|
|
29
|
-
senderAlt: clean(key.participantAlt) || null, fromMe: !!key.fromMe,
|
|
30
|
-
route: chat === 'status@broadcast' ? 'estado' : group ? 'grupo' : chat.endsWith('@newsletter') ? 'canal' : /@(s\.whatsapp\.net|lid)$/.test(chat) ? 'privado' : 'sistema',
|
|
31
|
-
type: type || (encrypted ? 'ciphertext' : stubType ? 'stub' : 'unknown'),
|
|
32
|
-
label: encrypted ? 'Mensaje cifrado pendiente de descifrar' : labels[type] || (stubType ? 'Aviso del sistema' : type || 'Sin contenido'),
|
|
33
|
-
wrappers: layers, viewOnce, ephemeral: layers.includes('ephemeralMessage') || numeric(context.expiration)>0,
|
|
34
|
-
ptt: !!value?.ptt, gif: !!value?.gifPlayback, mime: value?.mimetype || null,
|
|
35
|
-
bytes: numeric(value?.fileLength) || (typeof text === 'string' ? Buffer.byteLength(text) : 0),
|
|
36
|
-
width: numeric(value?.width), height: numeric(value?.height), seconds: numeric(value?.seconds),
|
|
37
|
-
timestamp: numeric(raw.messageTimestamp), stubType: stubType || null,
|
|
38
|
-
quotedId: context.stanzaId || null, mentions: (context.mentionedJid || []).slice(0,100),
|
|
39
|
-
forwarded: !!context.isForwarded, forwardingScore: numeric(context.forwardingScore),
|
|
40
|
-
downloadable: !!(value?.mediaKey && (value?.directPath || value?.url)), encrypted,
|
|
41
|
-
receivedAt: Date.now()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function stateFor(conn) {
|
|
45
|
-
let state=states.get(conn);
|
|
46
|
-
if(!state){state={events:[],messages:new Map(),counts:{},listeners:[],stopped:false};states.set(conn,state);}
|
|
47
|
-
return state;
|
|
48
|
-
}
|
|
49
|
-
function rememberMessage(conn, raw) {
|
|
50
|
-
const info=inspectMessage(raw); const state=stateFor(conn);
|
|
51
|
-
if(info.id){
|
|
52
|
-
const key=JSON.stringify([info.chat,info.sender,info.id,info.fromMe]);
|
|
53
|
-
const previous=state.messages.get(key) || [...state.messages.values()].find(item=>item.chat===info.chat&&item.id===info.id&&item.fromMe===info.fromMe);
|
|
54
|
-
if (!info.timestamp && previous) info.timestamp=previous.timestamp;
|
|
55
|
-
if (!info.sender && previous) info.sender=previous.sender;
|
|
56
|
-
state.messages.set(key,info);
|
|
57
|
-
}
|
|
58
|
-
for(const [key,info] of state.messages) if(Date.now()-info.receivedAt>86400000) state.messages.delete(key);
|
|
59
|
-
while(state.messages.size>1000) state.messages.delete(state.messages.keys().next().value);
|
|
60
|
-
return info;
|
|
61
|
-
}
|
|
62
|
-
function installMessageInspector(conn, options={}) {
|
|
63
|
-
const state=stateFor(conn);
|
|
64
|
-
if(state.stop && !state.stopped) return state.stop;
|
|
65
|
-
state.stopped=false;
|
|
66
|
-
const record=(kind,detail)=>{
|
|
67
|
-
const event={kind,time:Date.now(),...detail};
|
|
68
|
-
state.events.push(event); while(state.events.length>100) state.events.shift();
|
|
69
|
-
state.counts[kind]=(state.counts[kind]||0)+1;
|
|
70
|
-
conn.ev.emit('kage.event',event);
|
|
71
|
-
if(typeof options.onEvent==='function') { try{options.onEvent(event);}catch(error){conn.logger?.warn?.({error:error.message},'event monitor callback');} }
|
|
72
|
-
};
|
|
73
|
-
const bind=(name,callback)=>{conn.ev.on(name,callback);state.listeners.push([name,callback]);};
|
|
74
|
-
bind('messages.upsert',data=>{for(const raw of data?.messages||[]) record('message',rememberMessage(conn,raw));});
|
|
75
|
-
bind('messaging-history.set',data=>{for(const raw of data?.messages||[]) rememberMessage(conn,raw);record('history',{count:data?.messages?.length||0});});
|
|
76
|
-
bind('messages.update',items=>{for(const item of items||[]) {
|
|
77
|
-
if(item.update?.message) record('message.updated',rememberMessage(conn,{...item.update,key:item.key}));
|
|
78
|
-
else record('message.receipt',{id:item.key?.id,chat:clean(item.key?.remoteJid),status:item.update?.status ?? null,deleted:item.update?.message===null});
|
|
79
|
-
}});
|
|
80
|
-
bind('messages.delete',data=>record('message.deleted',{keys:(data?.keys||[]).slice(0,100).map(k=>({id:k.id,chat:clean(k.remoteJid)})),all:!!data?.all}));
|
|
81
|
-
bind('messages.reaction',items=>{for(const item of items||[]) record('reaction',{id:item.key?.id,chat:clean(item.key?.remoteJid),emoji:item.reaction?.text||''});});
|
|
82
|
-
bind('message-receipt.update',items=>{for(const item of items||[]) record('receipt',{id:item.key?.id,chat:clean(item.key?.remoteJid),user:clean(item.receipt?.userJid)});});
|
|
83
|
-
bind('call',calls=>{for(const call of calls||[]) record('call',{id:call.id,chat:clean(call.chatId||call.from),from:clean(call.from),status:call.status,isVideo:!!call.isVideo,isGroup:!!call.isGroup});});
|
|
84
|
-
bind('presence.update',data=>record('presence',{chat:clean(data?.id),users:Object.entries(data?.presences||{}).slice(0,30).map(([jid,p])=>({jid:clean(jid),state:p?.lastKnownPresence}))}));
|
|
85
|
-
bind('group-participants.update',data=>record('group.participants',{chat:clean(data?.id),action:data?.action,participants:(data?.participants||[]).slice(0,100).map(p=>typeof p==='string'?p:p.id)}));
|
|
86
|
-
bind('groups.update',items=>{for(const item of items||[])record('group.updated',{chat:clean(item.id),fields:Object.keys(item).filter(k=>k!=='id')});});
|
|
87
|
-
bind('chats.upsert',items=>record('chats.upsert',{chats:(items||[]).map(item=>clean(item.id)).slice(0,100)}));
|
|
88
|
-
bind('chats.update',items=>record('chats.update',{chats:(items||[]).map(item=>clean(item.id)).slice(0,100)}));
|
|
89
|
-
bind('contacts.upsert',items=>record('contacts.upsert',{count:items?.length||0}));
|
|
90
|
-
bind('contacts.update',items=>record('contacts.update',{count:items?.length||0}));
|
|
91
|
-
bind('kage.automation',data=>record('automation.'+String(data.kind||'media'),{id:data.id,chat:clean(data.chat),outcome:data.outcome,error:data.error||null}));
|
|
92
|
-
bind('kage.unsupported-message',data=>record('unsupported',{id:data?.id,chat:clean(data?.chat),encryption:data?.encryption,reason:data?.reason}));
|
|
93
|
-
bind('connection.update',data=>{record('connection',{connection:data.connection||null,receivedPendingNotifications:data.receivedPendingNotifications??null});if(data.connection==='close')state.stop();});
|
|
94
|
-
state.stop=()=>{if(state.stopped)return;state.stopped=true;for(const [name,fn]of state.listeners)conn.ev.off(name,fn);state.listeners=[];};
|
|
95
|
-
return state.stop;
|
|
96
|
-
}
|
|
97
|
-
function getMessageDiagnostics(conn) {
|
|
98
|
-
const state=stateFor(conn);
|
|
99
|
-
// Never expose references to internal mutable buffers or credentials.
|
|
100
|
-
return JSON.parse(JSON.stringify({active:!!state.stop&&!state.stopped,counts:state.counts,recent:state.events, messages:[...state.messages.values()]}));
|
|
101
|
-
}
|
|
102
|
-
exports.inspectMessage=inspectMessage;
|
|
103
|
-
exports.rememberMessage=rememberMessage;
|
|
104
|
-
exports.installMessageInspector=installMessageInspector;
|
|
105
|
-
exports.getMessageDiagnostics=getMessageDiagnostics;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const cooldowns=new WeakMap();
|
|
3
|
-
async function refreshReceivedStatuses(conn, options={}) {
|
|
4
|
-
const status=require('./status-tools');
|
|
5
|
-
const capture=options.capture || (raw=>status.captureStatus(raw,conn));
|
|
6
|
-
const list=options.list || (()=>status.listStatuses(conn));
|
|
7
|
-
let state=cooldowns.get(conn);if(!state){state={last:0,keys:new Map(),job:null};cooldowns.set(conn,state);}
|
|
8
|
-
if(state.job)return state.job;
|
|
9
|
-
if(Date.now()-state.last<10000)return {received:list().length,imported:0,requested:0,errors:[],throttled:true};
|
|
10
|
-
state.last=Date.now();
|
|
11
|
-
state.job=(async()=>{
|
|
12
|
-
let imported=0,requested=0;const errors=[];
|
|
13
|
-
const sources=[options.messages,conn.chats?.['status@broadcast']?.messages,options.store?.messages?.['status@broadcast']];
|
|
14
|
-
if(options.capture) sources.push(status.listStatuses(conn).map(entry=>entry.raw).filter(Boolean));
|
|
15
|
-
for(const source of sources){
|
|
16
|
-
const items=Array.isArray(source)?source:source instanceof Map?[...source.values()]:source?.array||[];
|
|
17
|
-
for(const raw of items.slice(-500))if(raw?.key?.remoteJid==='status@broadcast'&&capture(raw))imported++;
|
|
18
|
-
}
|
|
19
|
-
const entries=list();
|
|
20
|
-
// There is no arbitrary status-list query here. Request only known message keys delivered to us.
|
|
21
|
-
const missing=entries.filter(entry=>!entry.type&&entry.sourceKey?.id&&!['expired','deleted'].includes(entry.availability)&&entry.expiresAt>Date.now());
|
|
22
|
-
for(const entry of missing.slice(0,5)){
|
|
23
|
-
const key=JSON.stringify([entry.author,entry.sourceKey.id]);
|
|
24
|
-
if(Date.now()-(state.keys.get(key)||0)<60000)continue;
|
|
25
|
-
if(typeof conn.requestPlaceholderResend!=='function')break;
|
|
26
|
-
state.keys.set(key,Date.now()); requested++;
|
|
27
|
-
let timer;
|
|
28
|
-
try { await Promise.race([conn.requestPlaceholderResend(entry.sourceKey),new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('STATUS_REFRESH_PENDING')),4000);})]); }
|
|
29
|
-
catch(error){errors.push({id:entry.id,error:String(error.message||error).slice(0,200)});}
|
|
30
|
-
finally{clearTimeout(timer);}
|
|
31
|
-
}
|
|
32
|
-
for(const [key,time]of state.keys)if(Date.now()-time>86400000)state.keys.delete(key);
|
|
33
|
-
return {received:list().length,imported,requested,errors,throttled:false};
|
|
34
|
-
})();
|
|
35
|
-
try{return await state.job;}finally{state.job=null;}
|
|
36
|
-
}
|
|
37
|
-
exports.refreshReceivedStatuses=refreshReceivedStatuses;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const chalk=require('chalk');
|
|
3
|
-
const gradient=require('gradient-string');
|
|
4
|
-
const tinyMap=Object.fromEntries([...('abcdefghijklmnopqrstuvwxyz')].map((c,i)=>[c,[...'ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀꜱᴛᴜᴠᴡxʏᴢ'][i]]));
|
|
5
|
-
function say(text, options={}) {
|
|
6
|
-
const stream=options.stream||process.stdout;
|
|
7
|
-
let rendered=String(text??'').replace(/[\x00-\x08\x0b-\x1f\x7f]/g,'');
|
|
8
|
-
if(options.font==='tiny')rendered=[...rendered.toLowerCase()].map(c=>tinyMap[c]||c).join('');
|
|
9
|
-
else if(options.font==='block')rendered=rendered.toUpperCase().split('\n').map(line=>'▰ '+[...line].join(' ')+' ▰').join('\n');
|
|
10
|
-
else if(options.font==='simple')rendered=rendered.toUpperCase();
|
|
11
|
-
const width=Number(options.width)||stream.columns||80;
|
|
12
|
-
const color=options.forceColor??!!stream.isTTY;
|
|
13
|
-
const colors=(options.colors||[]).filter(name=>typeof chalk[name]==='function');
|
|
14
|
-
rendered=rendered.split('\n').map((line,i)=>{
|
|
15
|
-
const pad=options.align==='center'?Math.max(0,Math.floor((width-[...line].length)/2)):options.align==='right'?Math.max(0,width-[...line].length):0;
|
|
16
|
-
let output=line;
|
|
17
|
-
if(color&&Array.isArray(options.gradient)&&options.gradient.length>=2){try{output=gradient(options.gradient)(line);}catch{}}
|
|
18
|
-
else if(color&&colors.length){const paint=new chalk.Instance({level:1});output=[...line].map((char,index)=>paint[colors[(index+i)%colors.length]](char)).join('');}
|
|
19
|
-
return ' '.repeat(Math.min(500,pad))+output;
|
|
20
|
-
}).join('\n');
|
|
21
|
-
stream.write(rendered+'\n'); return rendered;
|
|
22
|
-
}
|
|
23
|
-
let shown=false;
|
|
24
|
-
function showKageBanner(options={}) {
|
|
25
|
-
if(shown||process.env.KAGE_BANNER==='0')return;
|
|
26
|
-
shown=true;
|
|
27
|
-
say('kage baileys',{font:'block',align:'center',gradient:['red','magenta'],...options});
|
|
28
|
-
say('Creado por xrljosedv',{font:'console',align:'center',colors:['cyan','magenta','yellow'],...options});
|
|
29
|
-
say('Mensajes · Estados · Llamadas',{font:'tiny',align:'center',colors:['cyan','blue'],...options});
|
|
30
|
-
}
|
|
31
|
-
exports.say=say;
|
|
32
|
-
exports.showKageBanner=showKageBanner;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const test=require('node:test');const assert=require('node:assert/strict');
|
|
3
|
-
const {EventEmitter}=require('node:events');const {Readable}=require('node:stream');
|
|
4
|
-
const fs=require('node:fs');const os=require('node:os');const path=require('node:path');
|
|
5
|
-
const api=require('../lib');
|
|
6
|
-
const chat='51900000001@s.whatsapp.net';
|
|
7
|
-
const socket=()=>({ev:new EventEmitter(),user:{id:'51900000002:1@s.whatsapp.net'},statusStorageDir:false,sent:[],async sendMessage(jid,body){const sent={jid,body};this.sent.push(sent);return sent;}});
|
|
8
|
-
const raw=(type='imageMessage',wrapper='viewOnceMessageV2',route=chat)=>({key:{remoteJid:route,participant:route.endsWith('@g.us')?chat:undefined,id:'fresh',fromMe:false},messageTimestamp:Math.floor(Date.now()/1000),message:{[wrapper]:{message:{[type]:{viewOnce:true,mediaKey:Buffer.from('secret'),directPath:'/delivered',mimetype:type==='audioMessage'?'audio/ogg':'application/octet-stream',ptt:type==='audioMessage'}}}}});
|
|
9
|
-
const download=async()=>Readable.from([Buffer.from('delivered-content')]);
|
|
10
|
-
for(const type of ['imageMessage','videoMessage','audioMessage','documentMessage','stickerMessage'])for(const wrapper of ['viewOnceMessage','viewOnceMessageV2','viewOnceMessageV2Extension']){
|
|
11
|
-
test(`inspección conserva ${wrapper}/${type}`,()=>{
|
|
12
|
-
const message=raw(type,wrapper),before=JSON.stringify(message),detail=api.inspectMessage(message);
|
|
13
|
-
assert.equal(detail.type,type);assert.equal(detail.viewOnce,true);assert.equal(detail.downloadable,true);assert.equal(JSON.stringify(message),before);assert.equal(JSON.stringify(detail).includes('secret'),false);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
for(const route of [chat,'123456@g.us'])for(const type of ['imageMessage','videoMessage','audioMessage'])test(`automático usa misma salida manual: ${route}/${type}`,async()=>{
|
|
17
|
-
const a=socket(),b=socket(),message=raw(type,'viewOnceMessageV2Extension',route);
|
|
18
|
-
await api.resendViewOnceMessage(a,message,{downloader:download});
|
|
19
|
-
await api.handleAutoViewOnce(message,b,{autoViewOnceChats:[route]},{downloader:download});
|
|
20
|
-
assert.deepEqual(b.sent,a.sent);assert.equal(b.sent.length,1);
|
|
21
|
-
});
|
|
22
|
-
test('deviceSentMessage y efímero no ocultan la vista única',async()=>{
|
|
23
|
-
const c=socket(),message=raw();message.message={deviceSentMessage:{message:{ephemeralMessage:{message:message.message}}}};
|
|
24
|
-
await api.handleAutoViewOnce(message,c,{autoViewOnceChats:[chat]},{downloader:download});assert.equal(c.sent.length,1);assert.equal(api.inspectMessage(message).viewOnce,true);
|
|
25
|
-
});
|
|
26
|
-
test('permiso de privado PN funciona con LID confirmado',async()=>{
|
|
27
|
-
const c=socket();c.signalRepository={lidMapping:{getPNForLID:async()=>chat}};
|
|
28
|
-
await api.handleAutoViewOnce(raw('imageMessage','viewOnceMessageV2','999@lid'),c,{autoViewOnceChats:[chat]},{downloader:download});assert.equal(c.sent.length,1);assert.equal(c.sent[0].jid,'999@lid');
|
|
29
|
-
});
|
|
30
|
-
test('un LID sin mapeo no se inventa como teléfono ni obtiene permiso',async()=>{
|
|
31
|
-
const c=socket();await api.handleAutoViewOnce(raw('imageMessage','viewOnceMessageV2','999@lid'),c,{autoViewOnceChats:['999@s.whatsapp.net']},{downloader:download});assert.equal(c.sent.length,0);assert.equal(api.inspectMessage(raw('imageMessage','viewOnceMessageV2','999@lid')).chat,'999@lid');
|
|
32
|
-
});
|
|
33
|
-
test('medios propios solo se copian mediante autorización explícita',async()=>{
|
|
34
|
-
const c=socket(),message=raw();message.key.fromMe=true;
|
|
35
|
-
await api.handleAutoViewOnce(message,c,{autoViewOnceChats:[chat]},{downloader:download});assert.equal(c.sent.length,0);
|
|
36
|
-
await api.handleAutoViewOnce(message,c,{autoViewOnceChats:[chat],autoViewOnceOwn:true},{downloader:download});assert.equal(c.sent.length,1);
|
|
37
|
-
const normal={...message,key:{...message.key,id:'normal-output'},message:{imageMessage:{}}};await api.handleAutoViewOnce(normal,c,{autoViewOnceChats:[chat],autoViewOnceOwn:true},{downloader:download});assert.equal(c.sent.length,1);
|
|
38
|
-
});
|
|
39
|
-
test('fallo no deja el ID bloqueado para la siguiente entrega',async()=>{
|
|
40
|
-
const c=socket(),message=raw(),settings={autoViewOnceChats:[chat]};
|
|
41
|
-
await assert.rejects(api.handleAutoViewOnce(message,c,settings,{downloader:async()=>{throw new Error('fallo controlado');}}),/fallo controlado/);
|
|
42
|
-
await api.handleAutoViewOnce(message,c,settings,{downloader:download});assert.equal(c.sent.length,1);
|
|
43
|
-
});
|
|
44
|
-
test('descarga renueva referencia 404 y reintenta una vez',async()=>{
|
|
45
|
-
const c=socket();let downloads=0,reuploads=0;
|
|
46
|
-
c.updateMediaMessage=async message=>{reuploads++;return message;};
|
|
47
|
-
const result=await api.downloadViewOnceMessage(c,raw(),{downloader:async()=>{if(!downloads++){const e=new Error('CDN 404');e.response={status:404};throw e;}return download();}});
|
|
48
|
-
assert.equal(result.buffer.toString(),'delivered-content');assert.equal(downloads,2);assert.equal(reuploads,1);
|
|
49
|
-
});
|
|
50
|
-
test('fallo persistente se detiene tras dos intentos',async()=>{
|
|
51
|
-
let calls=0;await assert.rejects(api.downloadViewOnceMessage(socket(),raw(),{downloader:async()=>{calls++;const e=new Error('CDN 410');e.statusCode=410;throw e;}}),/CDN 410/);assert.equal(calls,2);
|
|
52
|
-
});
|
|
53
|
-
test('vista única automática no procesa historial antiguo',async()=>{
|
|
54
|
-
const c=socket(),message=raw();message.messageTimestamp=1;
|
|
55
|
-
await api.handleAutoViewOnce(message,c,{autoViewOnceChats:[chat]},{downloader:download});assert.equal(c.sent.length,0);
|
|
56
|
-
});
|
|
57
|
-
test('aviso cifrado no se clasifica como medio disponible',()=>{
|
|
58
|
-
const info=api.inspectMessage({key:{remoteJid:chat,id:'pending'},messageStubType:2});assert.equal(info.type,'ciphertext');assert.equal(info.encrypted,true);assert.match(info.label,/pendiente/);
|
|
59
|
-
});
|
|
60
|
-
test('monitor detecta llamadas, presencia, reacciones y cierra sin listeners duplicados',()=>{
|
|
61
|
-
const c=api.installKageTools(socket()),stop=c.enableEventTracking();assert.equal(c.enableEventTracking(),stop);
|
|
62
|
-
c.ev.emit('call',[{id:'call1',from:chat,status:'offer',isVideo:true}]);
|
|
63
|
-
c.ev.emit('presence.update',{id:chat,presences:{[chat]:{lastKnownPresence:'composing'}}});
|
|
64
|
-
c.ev.emit('messages.reaction',[{key:{id:'reaction',remoteJid:chat},reaction:{text:'👍'}}]);
|
|
65
|
-
const diag=c.getMessageDiagnostics();assert.equal(diag.counts.call,1);assert.equal(diag.counts.presence,1);assert.equal(diag.counts.reaction,1);assert.equal(diag.recent[0].isVideo,true);
|
|
66
|
-
stop();assert.equal(c.ev.listenerCount('call'),0);assert.equal(c.ev.listenerCount('messages.upsert'),0);assert.equal(c.getMessageDiagnostics().active,false);
|
|
67
|
-
});
|
|
68
|
-
test('diagnóstico acotado no almacena claves de medios ni texto',()=>{
|
|
69
|
-
const c=api.installKageTools(socket());const stop=c.enableEventTracking();
|
|
70
|
-
for(let i=0;i<120;i++){const m=raw();m.key.id='m'+i;c.ev.emit('messages.upsert',{messages:[m],type:'notify'});}
|
|
71
|
-
const diag=c.getMessageDiagnostics();assert.equal(diag.recent.length,100);assert.equal(diag.messages.length,120);assert.equal(JSON.stringify(diag).includes('secret'),false);stop();
|
|
72
|
-
});
|
|
73
|
-
test('estado de historial y actualización descifrada se reindexa',async()=>{
|
|
74
|
-
const c=api.installKageTools(socket());const stop=c.enableStatusTracking();
|
|
75
|
-
const key={remoteJid:'status@broadcast',participant:chat,id:'status1'};const timestamp=Math.floor(Date.now()/1000);
|
|
76
|
-
c.ev.emit('messaging-history.set',{messages:[{key,messageTimestamp:timestamp,message:{conversation:'publicado'}}]});
|
|
77
|
-
assert.equal(c.getReceivedStatuses().length,1);
|
|
78
|
-
c.ev.emit('messages.update',[{key,update:{messageTimestamp:timestamp,message:{imageMessage:{mimetype:'image/jpeg'}}}}]);
|
|
79
|
-
await new Promise(r=>setImmediate(r));assert.equal(c.getReceivedStatuses()[0].type,'imageMessage');stop();
|
|
80
|
-
});
|
|
81
|
-
test('actualización de estados usa solo claves conocidas, sin consultar estados ocultos',async()=>{
|
|
82
|
-
const c=socket();api.captureStatus({key:{remoteJid:'status@broadcast',participant:chat,id:'missing'},messageTimestamp:Math.floor(Date.now()/1000),message:{}},c);
|
|
83
|
-
const requests=[];c.requestPlaceholderResend=async key=>requests.push(key);
|
|
84
|
-
const report=await api.refreshReceivedStatuses(c);assert.equal(report.requested,1);assert.equal(requests[0].id,'missing');
|
|
85
|
-
assert.equal((await api.refreshReceivedStatuses(c)).throttled,true);
|
|
86
|
-
});
|
|
87
|
-
test('caché consultada antes del login carga su archivo al conocerse la cuenta',()=>{
|
|
88
|
-
const dir=fs.mkdtempSync(path.join(os.tmpdir(),'status-login-'));
|
|
89
|
-
try{
|
|
90
|
-
const a=socket();a.statusStorageDir=dir;api.captureStatus({key:{remoteJid:'status@broadcast',participant:chat,id:'saved'},messageTimestamp:Math.floor(Date.now()/1000),message:{conversation:'persistido'}},a);
|
|
91
|
-
const b={statusStorageDir:dir};assert.equal(api.listStatuses(b).length,0);b.user=a.user;assert.equal(api.listStatuses(b).length,1);
|
|
92
|
-
}finally{fs.rmSync(dir,{recursive:true,force:true});}
|
|
93
|
-
});
|
|
94
|
-
test('banner admite fuentes y colores sin efectos al importar',()=>{
|
|
95
|
-
const lines=[];const stream={columns:60,isTTY:false,write:text=>lines.push(text)};
|
|
96
|
-
for(const font of ['simple','console','tiny','block'])api.say('kage baileys',{font,align:'center',gradient:['red','magenta'],stream});
|
|
97
|
-
assert.equal(lines.length,4);assert.match(lines[0],/KAGE BAILEYS/);assert.match(lines[3],/▰/);
|
|
98
|
-
});
|