@charape/baileys 1.0.8 → 1.0.10
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/lib/Defaults/index.js +4 -2
- package/lib/Socket/chats.js +39 -6
- package/lib/Socket/index.js +2 -1
- package/lib/Socket/message-builder.js +20 -8
- package/lib/Socket/messages-recv.js +44 -9
- package/lib/Socket/messages-send.js +139 -189
- package/lib/Socket/socket.js +41 -20
- package/lib/Store/make-cache-manager-store.js +22 -3
- package/lib/Utils/charape-runtime.js +230 -0
- package/lib/Utils/decode-wa-message.js +9 -3
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/tc-token-utils.js +1 -1
- package/lib/index.cjs +494 -217
- package/lib/index.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { jidNormalizedUser, isLidUser, isHostedLidUser, isPnUser, isHostedPnUser } from '../WABinary/index.js';
|
|
2
|
+
|
|
3
|
+
const ANSI = {
|
|
4
|
+
reset: '\x1b[0m',
|
|
5
|
+
bold: '\x1b[1m',
|
|
6
|
+
dim: '\x1b[2m',
|
|
7
|
+
cyan: '\x1b[36m',
|
|
8
|
+
blue: '\x1b[34m',
|
|
9
|
+
green: '\x1b[32m',
|
|
10
|
+
magenta: '\x1b[35m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
white: '\x1b[37m'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Insert or refresh an entry in a Map while enforcing an upper bound (FIFO/LRU eviction).
|
|
17
|
+
*/
|
|
18
|
+
export const setBoundedMapEntry = (map, key, value, maxEntries = 2000) => {
|
|
19
|
+
if (!map || key === undefined || key === null) return map;
|
|
20
|
+
if (map.has(key)) {
|
|
21
|
+
map.delete(key);
|
|
22
|
+
}
|
|
23
|
+
map.set(key, value);
|
|
24
|
+
while (map.size > maxEntries) {
|
|
25
|
+
const oldestKey = map.keys().next().value;
|
|
26
|
+
if (oldestKey === undefined) break;
|
|
27
|
+
map.delete(oldestKey);
|
|
28
|
+
}
|
|
29
|
+
return map;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Insert or refresh an entry in a Set while enforcing an upper bound.
|
|
34
|
+
*/
|
|
35
|
+
export const addBoundedSetEntry = (set, value, maxEntries = 2000) => {
|
|
36
|
+
if (!set || value === undefined || value === null) return set;
|
|
37
|
+
if (set.has(value)) {
|
|
38
|
+
set.delete(value);
|
|
39
|
+
}
|
|
40
|
+
set.add(value);
|
|
41
|
+
while (set.size > maxEntries) {
|
|
42
|
+
const oldestValue = set.values().next().value;
|
|
43
|
+
if (oldestValue === undefined) break;
|
|
44
|
+
set.delete(oldestValue);
|
|
45
|
+
}
|
|
46
|
+
return set;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve PN (@s.whatsapp.net) or LID (@lid) JID safely through lidMapping without throwing.
|
|
51
|
+
*/
|
|
52
|
+
export const resolveJidWithMapping = async (jid, lidMapping, target = 'pn') => {
|
|
53
|
+
if (!jid || typeof jid !== 'string') return jid;
|
|
54
|
+
const normalized = jidNormalizedUser(jid);
|
|
55
|
+
if (!lidMapping) return normalized;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (target === 'pn') {
|
|
59
|
+
if (isPnUser(normalized) || isHostedPnUser(normalized)) return normalized;
|
|
60
|
+
if ((isLidUser(normalized) || isHostedLidUser(normalized)) && typeof lidMapping.getPNForLID === 'function') {
|
|
61
|
+
const pn = await lidMapping.getPNForLID(normalized);
|
|
62
|
+
return pn ? jidNormalizedUser(pn) : normalized;
|
|
63
|
+
}
|
|
64
|
+
} else if (target === 'lid') {
|
|
65
|
+
if (isLidUser(normalized) || isHostedLidUser(normalized)) return normalized;
|
|
66
|
+
if ((isPnUser(normalized) || isHostedPnUser(normalized)) && typeof lidMapping.getLIDForPN === 'function') {
|
|
67
|
+
const lid = await lidMapping.getLIDForPN(normalized);
|
|
68
|
+
return lid ? jidNormalizedUser(lid) : normalized;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Fallback silently to normalized JID so caller handlers never crash
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const SKIPPED_LOG_CONTENT_TYPES = new Set([
|
|
78
|
+
'protocolMessage',
|
|
79
|
+
'senderKeyDistributionMessage',
|
|
80
|
+
'fastRatchetSenderKeyDistributionMessage',
|
|
81
|
+
'messageContextInfo',
|
|
82
|
+
'historySyncNotification',
|
|
83
|
+
'initialSecurityNotificationSettingSync',
|
|
84
|
+
'appStateSyncKeyShare',
|
|
85
|
+
'appStateFatalExceptionNotification'
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const WRAPPER_KEYS = [
|
|
89
|
+
'ephemeralMessage',
|
|
90
|
+
'viewOnceMessage',
|
|
91
|
+
'viewOnceMessageV2',
|
|
92
|
+
'viewOnceMessageV2Extension',
|
|
93
|
+
'documentWithCaptionMessage',
|
|
94
|
+
'editedMessage'
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const unwrapMessageShape = (message) => {
|
|
98
|
+
let curr = message;
|
|
99
|
+
for (let depth = 0; depth < 4 && curr && typeof curr === 'object'; depth++) {
|
|
100
|
+
let unwrapped = false;
|
|
101
|
+
for (const key of WRAPPER_KEYS) {
|
|
102
|
+
if (curr[key]?.message) {
|
|
103
|
+
curr = curr[key].message;
|
|
104
|
+
unwrapped = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!unwrapped) break;
|
|
109
|
+
}
|
|
110
|
+
return curr;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const detectDisplayTypeAndPreview = (message) => {
|
|
114
|
+
const content = unwrapMessageShape(message);
|
|
115
|
+
if (!content || typeof content !== 'object') return null;
|
|
116
|
+
|
|
117
|
+
const keys = Object.keys(content).filter(k => k !== 'messageContextInfo' && content[k] !== undefined && content[k] !== null);
|
|
118
|
+
if (keys.length === 0) return null;
|
|
119
|
+
|
|
120
|
+
const primaryKey = keys.find(k => !SKIPPED_LOG_CONTENT_TYPES.has(k)) || keys[0];
|
|
121
|
+
if (SKIPPED_LOG_CONTENT_TYPES.has(primaryKey)) return null;
|
|
122
|
+
|
|
123
|
+
const val = content[primaryKey];
|
|
124
|
+
let textPreview = '';
|
|
125
|
+
if (primaryKey === 'conversation' && typeof val === 'string') {
|
|
126
|
+
textPreview = val;
|
|
127
|
+
} else if (val && typeof val === 'object') {
|
|
128
|
+
textPreview = val.text || val.caption || val.fileName || val.title || val.body?.text || val.header?.title || val.name || '';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cleanPreview = String(textPreview || '').replace(/[\r\n\t]+/g, ' ').trim();
|
|
132
|
+
const byteSize = cleanPreview ? Buffer.byteLength(cleanPreview, 'utf8') : 0;
|
|
133
|
+
const typeLabel = primaryKey
|
|
134
|
+
.replace(/Message$/i, '')
|
|
135
|
+
.replace(/([A-Z])/g, ' $1')
|
|
136
|
+
.replace(/^./, s => s.toUpperCase())
|
|
137
|
+
.trim();
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
rawType: primaryKey,
|
|
141
|
+
typeLabel: typeLabel || 'Message',
|
|
142
|
+
preview: cleanPreview.length > 48 ? cleanPreview.slice(0, 45) + '...' : cleanPreview,
|
|
143
|
+
byteSize
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let totalLoggedMessages = 0;
|
|
148
|
+
let burstWindowStart = 0;
|
|
149
|
+
let burstCountInWindow = 0;
|
|
150
|
+
const printedLogIds = new Set();
|
|
151
|
+
const MAX_PRINTS_PER_SECOND = 25;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Format an iconic CHARAPE terminal chat card inspired by the clean MESSAGE INFO layout.
|
|
155
|
+
*/
|
|
156
|
+
export const formatCharapeChatLog = (direction = 'IN', msg = {}, count = totalLoggedMessages) => {
|
|
157
|
+
const info = detectDisplayTypeAndPreview(msg?.message);
|
|
158
|
+
if (!info) return null;
|
|
159
|
+
|
|
160
|
+
const isOut = String(direction).toUpperCase().startsWith('OUT');
|
|
161
|
+
const key = msg?.key || {};
|
|
162
|
+
const remoteJid = key.remoteJid || '-';
|
|
163
|
+
const senderRaw = key.participantAlt || key.remoteJidAlt || key.participant || key.remoteJid || '-';
|
|
164
|
+
const senderId = String(senderRaw).split('@')[0].split(':')[0] || '-';
|
|
165
|
+
const senderName = msg.pushName || (isOut ? 'CHARAPE BOT' : senderId);
|
|
166
|
+
const tsRaw = Number(msg.messageTimestamp || 0);
|
|
167
|
+
const tsDate = tsRaw > 0 ? new Date(tsRaw > 1e12 ? tsRaw : tsRaw * 1000) : new Date();
|
|
168
|
+
const sentTime = Number.isNaN(tsDate.getTime()) ? new Date().toLocaleString() : tsDate.toLocaleString();
|
|
169
|
+
const sizeInfo = info.preview ? `${info.byteSize} bytes (${info.preview})` : `${info.typeLabel}`;
|
|
170
|
+
|
|
171
|
+
const border = `${ANSI.blue}════════════════════════════════════════════════════${ANSI.reset}`;
|
|
172
|
+
const badgeColor = isOut ? ANSI.magenta : ANSI.green;
|
|
173
|
+
const dirLabel = isOut ? 'OUTGOING CHAT' : 'INCOMING CHAT';
|
|
174
|
+
|
|
175
|
+
return [
|
|
176
|
+
border,
|
|
177
|
+
` ${badgeColor}${ANSI.bold}⚡ CHARAPE${ANSI.reset} ${ANSI.dim}•${ANSI.reset} ${ANSI.magenta}${ANSI.bold}${dirLabel}${ANSI.reset}`,
|
|
178
|
+
border,
|
|
179
|
+
` ${ANSI.cyan}📌 Message Type ${ANSI.reset}: ${ANSI.white}${info.typeLabel}${ANSI.reset}`,
|
|
180
|
+
` ${ANSI.cyan}🆔 Message ID ${ANSI.reset}: ${ANSI.white}${key.id || '-'}${ANSI.reset}`,
|
|
181
|
+
` ${ANSI.cyan}⏰ Sent Time ${ANSI.reset}: ${ANSI.white}${sentTime}${ANSI.reset}`,
|
|
182
|
+
` ${ANSI.cyan}📁 Content/Size ${ANSI.reset}: ${ANSI.white}${sizeInfo}${ANSI.reset}`,
|
|
183
|
+
` ${ANSI.cyan}👤 Sender ID ${ANSI.reset}: ${ANSI.white}${senderId}${ANSI.reset}`,
|
|
184
|
+
` ${ANSI.cyan}🎭 Sender Name ${ANSI.reset}: ${ANSI.white}${senderName}${ANSI.reset}`,
|
|
185
|
+
` ${ANSI.cyan}💬 Chat ID ${ANSI.reset}: ${ANSI.white}${remoteJid}${ANSI.reset}`,
|
|
186
|
+
` ${ANSI.cyan}📊 Total Logs ${ANSI.reset}: ${ANSI.yellow}${count}${ANSI.reset}`,
|
|
187
|
+
border
|
|
188
|
+
].join('\n');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Safely print iconic CHARAPE terminal chat card without blocking or flooding stdout.
|
|
193
|
+
*/
|
|
194
|
+
export const printCharapeChatLog = (direction = 'IN', msg = {}, options = {}) => {
|
|
195
|
+
try {
|
|
196
|
+
if (options.enabled === false) return false;
|
|
197
|
+
if (!options.ignoreEnvSilent && process.env.CHARAPE_SILENT === 'true') return false;
|
|
198
|
+
if (!msg || typeof msg !== 'object' || !msg.message) return false;
|
|
199
|
+
|
|
200
|
+
const dir = String(direction || 'IN').toUpperCase().startsWith('OUT') ? 'OUT' : 'IN';
|
|
201
|
+
const msgId = msg.key?.id;
|
|
202
|
+
if (msgId && !options.force) {
|
|
203
|
+
const dedupeKey = `${dir}:${msgId}`;
|
|
204
|
+
if (printedLogIds.has(dedupeKey)) return false;
|
|
205
|
+
addBoundedSetEntry(printedLogIds, dedupeKey, 600);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
if (now - burstWindowStart >= 1000) {
|
|
210
|
+
burstWindowStart = now;
|
|
211
|
+
burstCountInWindow = 0;
|
|
212
|
+
}
|
|
213
|
+
if (!options.force && burstCountInWindow >= MAX_PRINTS_PER_SECOND) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const nextCount = totalLoggedMessages + 1;
|
|
218
|
+
const formatted = formatCharapeChatLog(dir, msg, nextCount);
|
|
219
|
+
if (!formatted) return false;
|
|
220
|
+
|
|
221
|
+
totalLoggedMessages = nextCount;
|
|
222
|
+
burstCountInWindow++;
|
|
223
|
+
|
|
224
|
+
const writer = typeof options.writer === 'function' ? options.writer : console.log;
|
|
225
|
+
writer(formatted);
|
|
226
|
+
return true;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
@@ -4,6 +4,7 @@ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJid
|
|
|
4
4
|
// transferDevice
|
|
5
5
|
} from '../WABinary/index.js';
|
|
6
6
|
import { unpadRandomMax16 } from './generics.js';
|
|
7
|
+
import { setBoundedMapEntry } from './charape-runtime.js';
|
|
7
8
|
export const getDecryptionJid = async (sender, repository) => {
|
|
8
9
|
if (isLidUser(sender) || isHostedLidUser(sender)) {
|
|
9
10
|
return sender;
|
|
@@ -97,6 +98,8 @@ const MAX_SECRETS_PER_CHAT = 20
|
|
|
97
98
|
// Module-level map: outgoing @bot message ID → messageSecret
|
|
98
99
|
// Populated when we receive the outgoing pkmsg/msg to @bot (which contains messageContextInfo.messageSecret)
|
|
99
100
|
// Consumed when the msmsg response from @bot arrives and needs decryption
|
|
101
|
+
const MAX_BOT_MESSAGE_SECRETS = 5000
|
|
102
|
+
const MAX_BOT_RECENT_CHATS = 1000
|
|
100
103
|
const botMessageSecrets = new Map()
|
|
101
104
|
const botRecentSecretsByChat = new Map()
|
|
102
105
|
const pushRecentChatSecret = (chatJid, id, secretBuf) => {
|
|
@@ -107,7 +110,7 @@ const pushRecentChatSecret = (chatJid, id, secretBuf) => {
|
|
|
107
110
|
if (filtered.length > MAX_SECRETS_PER_CHAT) {
|
|
108
111
|
filtered.length = MAX_SECRETS_PER_CHAT
|
|
109
112
|
}
|
|
110
|
-
botRecentSecretsByChat
|
|
113
|
+
setBoundedMapEntry(botRecentSecretsByChat, chatJid, filtered, MAX_BOT_RECENT_CHATS)
|
|
111
114
|
}
|
|
112
115
|
export const setBotMessageSecret = (id, secret, chatJid) => {
|
|
113
116
|
if (!id || !secret) return
|
|
@@ -121,7 +124,7 @@ export const setBotMessageSecret = (id, secret, chatJid) => {
|
|
|
121
124
|
} else {
|
|
122
125
|
return
|
|
123
126
|
}
|
|
124
|
-
botMessageSecrets
|
|
127
|
+
setBoundedMapEntry(botMessageSecrets, id, buf, MAX_BOT_MESSAGE_SECRETS)
|
|
125
128
|
if (chatJid) {
|
|
126
129
|
pushRecentChatSecret(chatJid, id, buf)
|
|
127
130
|
}
|
|
@@ -208,9 +211,12 @@ export function decodeMessageNode(stanza, meId, meLid) {
|
|
|
208
211
|
throw new Boom('Unknown message type', { data: stanza });
|
|
209
212
|
}
|
|
210
213
|
const pushname = stanza?.attrs?.notify;
|
|
214
|
+
const isOtherRecipient = recipient && !isJidMetaAI(recipient) && !isMe(recipient) && !isMeLid(recipient);
|
|
211
215
|
const key = {
|
|
212
216
|
remoteJid: chatId,
|
|
213
|
-
remoteJidAlt: !isJidGroup(chatId)
|
|
217
|
+
remoteJidAlt: !isJidGroup(chatId)
|
|
218
|
+
? (isOtherRecipient ? addressingContext.recipientAlt : (addressingContext.recipientAlt || addressingContext.senderAlt))
|
|
219
|
+
: undefined,
|
|
214
220
|
remoteJidUsername: !isJidGroup(chatId)
|
|
215
221
|
? stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username
|
|
216
222
|
: undefined,
|
package/lib/Utils/index.js
CHANGED