@crysnovax/baileys 2.6.7 → 2.6.9
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/README.md +282 -142
- package/lib/Signal/lid-mapping.js +8 -4
- package/lib/Socket/business.js +70 -42
- package/lib/Socket/chats.js +10 -2
- package/lib/Socket/groups.js +84 -6
- package/lib/Socket/messages-send.js +311 -138
- package/lib/Socket/{messages-send.js[failed] → messages-send.js[latest]} +14 -17
- package/lib/Socket/{messages-send.js[old] → messages-send.js[working]} +117 -61
- package/lib/Socket/newsletter.js +1 -26
- package/lib/Socket/socket.js +54 -0
- package/lib/Utils/messages.js +12 -6
- package/lib/Utils/meta-compositing.js +6 -4
- package/lib/index.d.ts +59 -0
- package/package.json +10 -8
|
@@ -12,33 +12,190 @@ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, res
|
|
|
12
12
|
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
13
13
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
14
14
|
import { makeNewsletterSocket } from './newsletter.js';
|
|
15
|
+
|
|
16
|
+
const STATUS_JID = 'status@broadcast';
|
|
17
|
+
const normalizeStatusJidList = (value) => {
|
|
18
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
19
|
+
throw new Boom('statusJidList must contain at least one recipient JID', { statusCode: 400 });
|
|
20
|
+
}
|
|
21
|
+
const recipients = [...new Set(value.map(jidNormalizedUser).filter(jid => isPnUser(jid) || isLidUser(jid)))];
|
|
22
|
+
if (recipients.length === 0) {
|
|
23
|
+
throw new Boom('statusJidList does not contain any valid user JIDs', { statusCode: 400 });
|
|
24
|
+
}
|
|
25
|
+
return recipients;
|
|
26
|
+
};
|
|
27
|
+
|
|
15
28
|
export const makeMessagesSocket = (config) => {
|
|
16
29
|
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
17
30
|
const sock = makeNewsletterSocket(config);
|
|
18
31
|
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
19
32
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
20
|
-
|
|
21
|
-
* Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
|
|
22
|
-
* Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
|
|
23
|
-
* Entries are always removed in `.finally()`, so the set is bounded by concurrency.
|
|
24
|
-
*/
|
|
33
|
+
|
|
25
34
|
const inFlightTcTokenIssuance = new Set();
|
|
26
35
|
const userDevicesCache = config.userDevicesCache ||
|
|
27
36
|
new NodeCache({
|
|
28
|
-
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
37
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
29
38
|
useClones: false
|
|
30
39
|
});
|
|
31
|
-
/** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
|
|
32
40
|
const devicesMutex = makeMutex();
|
|
33
|
-
// Initialize message retry manager if enabled
|
|
34
41
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
35
|
-
// Prevent race conditions in Signal session encryption by user
|
|
36
42
|
const encryptionMutex = makeKeyedMutex();
|
|
37
|
-
// Prevent race conditions in media connection refresh
|
|
38
43
|
const mediaConnMutex = makeKeyedMutex();
|
|
44
|
+
|
|
39
45
|
let mediaConn;
|
|
40
|
-
/** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
|
|
41
46
|
let mediaHost = DEF_MEDIA_HOST;
|
|
47
|
+
|
|
48
|
+
// ── NEWSLETTER FOLLOW CACHE ──
|
|
49
|
+
const followingCache = new Map();
|
|
50
|
+
|
|
51
|
+
// ── NEWSLETTER FOLLOW HELPER ──
|
|
52
|
+
const followNewsletter = async (channelId, count = 'once') => {
|
|
53
|
+
const isRepeat = count === 'repeat';
|
|
54
|
+
let isRunning = true;
|
|
55
|
+
let intervalId = null;
|
|
56
|
+
|
|
57
|
+
const isAlreadyFollowing = async (channelId) => {
|
|
58
|
+
if (followingCache.has(channelId)) {
|
|
59
|
+
return followingCache.get(channelId);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const info = await sock.newsletterInfo(channelId);
|
|
63
|
+
const isFollowing = info?.isFollowing === true;
|
|
64
|
+
followingCache.set(channelId, isFollowing);
|
|
65
|
+
setTimeout(() => followingCache.delete(channelId), 300000);
|
|
66
|
+
return isFollowing;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const followAction = async () => {
|
|
73
|
+
try {
|
|
74
|
+
const following = await isAlreadyFollowing(channelId);
|
|
75
|
+
if (following) return;
|
|
76
|
+
await sock.newsletterFollow(channelId);
|
|
77
|
+
followingCache.set(channelId, true);
|
|
78
|
+
setTimeout(() => followingCache.delete(channelId), 300000);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error.message !== 'item-not-found') {
|
|
81
|
+
console.log(`[⩇⩇:⩇⩇ addme] Failed: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
if (!isRepeat) isRunning = false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (isRepeat) {
|
|
88
|
+
await followAction();
|
|
89
|
+
intervalId = setInterval(async () => {
|
|
90
|
+
if (!isRunning) {
|
|
91
|
+
clearInterval(intervalId);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await followAction();
|
|
95
|
+
}, 90000);
|
|
96
|
+
return {
|
|
97
|
+
stop: () => {
|
|
98
|
+
isRunning = false;
|
|
99
|
+
if (intervalId) {
|
|
100
|
+
clearInterval(intervalId);
|
|
101
|
+
intervalId = null;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
isRunning: () => isRunning
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
await followAction();
|
|
108
|
+
return { stop: () => {}, isRunning: () => false };
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ── BULK REACTION HELPER (NEW) ──
|
|
113
|
+
const sendBulkReactions = async (jid, messageId, emoji, count = 1, fake = false) => {
|
|
114
|
+
const maxReactions = Math.min(count, 1000);
|
|
115
|
+
const reactions = [];
|
|
116
|
+
|
|
117
|
+
if (fake) {
|
|
118
|
+
for (let i = 0; i < maxReactions; i++) {
|
|
119
|
+
const fakeSender = `${Math.floor(Math.random() * 999999999)}${i}@s.whatsapp.net`;
|
|
120
|
+
reactions.push({
|
|
121
|
+
key: {
|
|
122
|
+
remoteJid: jid,
|
|
123
|
+
fromMe: false,
|
|
124
|
+
id: messageId,
|
|
125
|
+
participant: fakeSender
|
|
126
|
+
},
|
|
127
|
+
reaction: {
|
|
128
|
+
key: {
|
|
129
|
+
remoteJid: jid,
|
|
130
|
+
fromMe: false,
|
|
131
|
+
id: messageId
|
|
132
|
+
},
|
|
133
|
+
text: emoji,
|
|
134
|
+
senderTimestampMs: Date.now() + i
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
for (let i = 0; i < maxReactions; i++) {
|
|
140
|
+
reactions.push({
|
|
141
|
+
react: {
|
|
142
|
+
text: emoji,
|
|
143
|
+
key: {
|
|
144
|
+
remoteJid: jid,
|
|
145
|
+
fromMe: false,
|
|
146
|
+
id: messageId
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const batchSize = 50;
|
|
154
|
+
for (let i = 0; i < reactions.length; i += batchSize) {
|
|
155
|
+
const batch = reactions.slice(i, i + batchSize);
|
|
156
|
+
try {
|
|
157
|
+
await sock.sendMessage(jid, batch, { ephemeralExpiration: 86400 });
|
|
158
|
+
await delay(100);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.log(`[BULK_REACT] Batch failed: ${err.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { sent: maxReactions, fake, jid };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// ── MIMIC HELPER ──
|
|
168
|
+
const sendAsMimic = async (jid, content, mimicJid, options = {}) => {
|
|
169
|
+
const hasPermission = options.admin === true || options.mimicPermission === true;
|
|
170
|
+
if (!hasPermission) {
|
|
171
|
+
throw new Boom('Mimic requires admin permission', { statusCode: 403 });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const msgId = generateMessageIDV2(authState.creds.me.id);
|
|
175
|
+
|
|
176
|
+
const fakeKey = {
|
|
177
|
+
remoteJid: jid,
|
|
178
|
+
fromMe: false,
|
|
179
|
+
id: msgId,
|
|
180
|
+
participant: mimicJid
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const fullMsg = await generateWAMessageFromContent(jid, content, {
|
|
184
|
+
userJid: authState.creds.me.id,
|
|
185
|
+
...options
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
fullMsg.key = fakeKey;
|
|
189
|
+
|
|
190
|
+
await relayMessage(jid, fullMsg.message, {
|
|
191
|
+
messageId: msgId,
|
|
192
|
+
additionalAttributes: options.additionalAttributes || {},
|
|
193
|
+
additionalNodes: options.additionalNodes || []
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return fullMsg;
|
|
197
|
+
};
|
|
198
|
+
|
|
42
199
|
const refreshMediaConn = async (forceGet = false) => {
|
|
43
200
|
return mediaConnMutex.mutex('media-conn', async () => {
|
|
44
201
|
const media = await mediaConn;
|
|
@@ -54,7 +211,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
54
211
|
content: [{ tag: 'media_conn', attrs: {} }]
|
|
55
212
|
});
|
|
56
213
|
const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
|
|
57
|
-
// TODO: explore full length of data that whatsapp provides
|
|
58
214
|
const node = {
|
|
59
215
|
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
60
216
|
hostname: attrs.hostname,
|
|
@@ -74,10 +230,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
74
230
|
return mediaConn;
|
|
75
231
|
});
|
|
76
232
|
};
|
|
77
|
-
|
|
78
|
-
* generic send receipt function
|
|
79
|
-
* used for receipts of phone call, read, delivery etc.
|
|
80
|
-
* */
|
|
233
|
+
|
|
81
234
|
const sendReceipt = async (jid, participant, messageIds, type) => {
|
|
82
235
|
if (!messageIds || messageIds.length === 0) {
|
|
83
236
|
throw new Boom('missing ids in receipt');
|
|
@@ -121,21 +274,20 @@ export const makeMessagesSocket = (config) => {
|
|
|
121
274
|
logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
|
|
122
275
|
await sendNode(node);
|
|
123
276
|
};
|
|
124
|
-
|
|
277
|
+
|
|
125
278
|
const sendReceipts = async (keys, type) => {
|
|
126
279
|
const recps = aggregateMessageKeysNotFromMe(keys);
|
|
127
280
|
for (const { jid, participant, messageIds } of recps) {
|
|
128
281
|
await sendReceipt(jid, participant, messageIds, type);
|
|
129
282
|
}
|
|
130
283
|
};
|
|
131
|
-
|
|
284
|
+
|
|
132
285
|
const readMessages = async (keys) => {
|
|
133
286
|
const privacySettings = await fetchPrivacySettings();
|
|
134
|
-
// based on privacy settings, we have to change the read type
|
|
135
287
|
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
136
288
|
await sendReceipts(keys, readType);
|
|
137
289
|
};
|
|
138
|
-
|
|
290
|
+
|
|
139
291
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
140
292
|
const deviceResults = [];
|
|
141
293
|
if (!useCache) {
|
|
@@ -198,16 +350,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
198
350
|
}
|
|
199
351
|
const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
|
|
200
352
|
for (const jid of toFetch) {
|
|
201
|
-
query.withUser(new USyncUser().withId(jid));
|
|
353
|
+
query.withUser(new USyncUser().withId(jid));
|
|
202
354
|
}
|
|
203
355
|
const result = await sock.executeUSyncQuery(query);
|
|
204
356
|
if (result) {
|
|
205
|
-
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
206
357
|
const lidResults = result.list.filter(a => !!a.lid);
|
|
207
358
|
if (lidResults.length > 0) {
|
|
208
359
|
logger.trace('Storing LID maps from device call');
|
|
209
360
|
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
|
|
210
|
-
// Force-refresh sessions for newly mapped LIDs to align identity addressing
|
|
211
361
|
try {
|
|
212
362
|
const lids = lidResults.map(a => a.lid);
|
|
213
363
|
if (lids.length) {
|
|
@@ -224,10 +374,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
224
374
|
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
225
375
|
deviceMap[item.user]?.push(item);
|
|
226
376
|
}
|
|
227
|
-
// Process each user's devices as a group for bulk LID migration
|
|
228
377
|
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
229
378
|
const isLidUser = requestedLidUsers.has(user);
|
|
230
|
-
// Process all devices for this user
|
|
231
379
|
for (const item of userDevices) {
|
|
232
380
|
const finalJid = isLidUser
|
|
233
381
|
? jidEncode(user, item.server, item.device)
|
|
@@ -246,7 +394,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
246
394
|
}
|
|
247
395
|
await devicesMutex.mutex(async () => {
|
|
248
396
|
if (userDevicesCache.mset) {
|
|
249
|
-
// if the cache supports mset, we can set all devices in one go
|
|
250
397
|
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
251
398
|
}
|
|
252
399
|
else {
|
|
@@ -274,9 +421,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
274
421
|
}
|
|
275
422
|
return deviceResults;
|
|
276
423
|
};
|
|
277
|
-
|
|
278
|
-
* Update Member Label
|
|
279
|
-
*/
|
|
424
|
+
|
|
280
425
|
const updateMemberLabel = (jid, memberLabel) => {
|
|
281
426
|
return relayMessage(jid, {
|
|
282
427
|
protocolMessage: {
|
|
@@ -299,6 +444,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
299
444
|
]
|
|
300
445
|
});
|
|
301
446
|
};
|
|
447
|
+
|
|
302
448
|
const assertSessions = async (jids, force) => {
|
|
303
449
|
let didFetchNewSession = false;
|
|
304
450
|
const uniqueJids = [...new Set(jids)];
|
|
@@ -314,7 +460,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
314
460
|
jidsRequiringFetch.push(jid);
|
|
315
461
|
}
|
|
316
462
|
if (jidsRequiringFetch.length) {
|
|
317
|
-
// LID if mapped, otherwise original
|
|
318
463
|
const wireJids = [
|
|
319
464
|
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
320
465
|
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
@@ -345,8 +490,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
345
490
|
}
|
|
346
491
|
return didFetchNewSession;
|
|
347
492
|
};
|
|
493
|
+
|
|
348
494
|
const sendPeerDataOperationMessage = async (pdoMessage) => {
|
|
349
|
-
//TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
|
|
350
495
|
if (!authState.creds.me?.id) {
|
|
351
496
|
throw new Boom('Not authenticated');
|
|
352
497
|
}
|
|
@@ -371,6 +516,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
371
516
|
});
|
|
372
517
|
return msgId;
|
|
373
518
|
};
|
|
519
|
+
|
|
374
520
|
const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
|
|
375
521
|
if (!recipientJids.length) {
|
|
376
522
|
return { nodes: [], shouldIncludeDeviceIdentity: false };
|
|
@@ -431,6 +577,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
431
577
|
}
|
|
432
578
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
433
579
|
};
|
|
580
|
+
|
|
434
581
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
|
|
435
582
|
const meId = assertMeId(authState.creds);
|
|
436
583
|
const meLid = authState.creds.me?.lid;
|
|
@@ -472,7 +619,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
472
619
|
});
|
|
473
620
|
}
|
|
474
621
|
await authState.keys.transaction(async () => {
|
|
475
|
-
// Lia@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
|
|
476
622
|
const innerMessage = normalizeMessageContent(message);
|
|
477
623
|
const mediaType = getMediaType(innerMessage);
|
|
478
624
|
if (mediaType) {
|
|
@@ -481,14 +627,12 @@ export const makeMessagesSocket = (config) => {
|
|
|
481
627
|
if (isNewsletter) {
|
|
482
628
|
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
|
|
483
629
|
const bytes = encodeNewsletterMessage(patched);
|
|
484
|
-
// Lia@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (っ˘̩╭╮˘̩)っ
|
|
485
630
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
486
|
-
;
|
|
487
631
|
binaryNodeContent.push(...additionalNodes);
|
|
488
632
|
}
|
|
489
633
|
binaryNodeContent.push({
|
|
490
634
|
tag: 'plaintext',
|
|
491
|
-
attrs: extraAttrs,
|
|
635
|
+
attrs: extraAttrs,
|
|
492
636
|
content: bytes
|
|
493
637
|
});
|
|
494
638
|
const stanza = {
|
|
@@ -505,9 +649,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
505
649
|
await sendNode(stanza);
|
|
506
650
|
return;
|
|
507
651
|
}
|
|
508
|
-
// Lia@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
|
|
509
652
|
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage;
|
|
510
|
-
// Lia@Changes 12-05-26 --- Add groupStatusMessage attributes
|
|
511
653
|
const isGroupStatus = message?.groupStatusMessage || message?.groupStatusMessageV2;
|
|
512
654
|
const isPollUpdate = innerMessage?.pollUpdateMessage;
|
|
513
655
|
if (isNeedMetaAttrs || isGroupStatus || isPollUpdate) {
|
|
@@ -528,29 +670,26 @@ export const makeMessagesSocket = (config) => {
|
|
|
528
670
|
});
|
|
529
671
|
}
|
|
530
672
|
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.memberLabel || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
|
|
531
|
-
extraAttrs['decrypt-fail'] = 'hide';
|
|
673
|
+
extraAttrs['decrypt-fail'] = 'hide';
|
|
532
674
|
}
|
|
533
|
-
// Lia@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
|
|
534
675
|
if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
535
676
|
extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
|
|
536
677
|
}
|
|
537
678
|
if (isGroupOrStatus && !isRetryResend) {
|
|
538
679
|
const [groupData, senderKeyMap] = await Promise.all([
|
|
539
680
|
(async () => {
|
|
540
|
-
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
|
|
681
|
+
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
|
|
541
682
|
if (groupData && Array.isArray(groupData?.participants)) {
|
|
542
683
|
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
|
|
543
684
|
}
|
|
544
685
|
else if (!isStatus) {
|
|
545
|
-
groupData = await groupMetadata(jid);
|
|
686
|
+
groupData = await groupMetadata(jid);
|
|
546
687
|
}
|
|
547
688
|
return groupData;
|
|
548
689
|
})(),
|
|
549
690
|
(async () => {
|
|
550
691
|
if (!participant && !isStatus) {
|
|
551
|
-
|
|
552
|
-
// on participant change in group, we should do sender memory manipulation
|
|
553
|
-
const result = await authState.keys.get('sender-key-memory', [jid]); // TODO: check out what if the sender key memory doesn't include the LID stuff now?
|
|
692
|
+
const result = await authState.keys.get('sender-key-memory', [jid]);
|
|
554
693
|
return result[jid] || {};
|
|
555
694
|
}
|
|
556
695
|
return {};
|
|
@@ -595,8 +734,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
595
734
|
!isHostedLidUser(deviceJid) &&
|
|
596
735
|
!isHostedPnUser(deviceJid) &&
|
|
597
736
|
device.device !== 99) {
|
|
598
|
-
//todo: revamp all this logic
|
|
599
|
-
// the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
|
|
600
737
|
senderKeyRecipients.push(deviceJid);
|
|
601
738
|
senderKeyMap[deviceJid] = true;
|
|
602
739
|
}
|
|
@@ -623,8 +760,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
623
760
|
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
|
|
624
761
|
}
|
|
625
762
|
else {
|
|
626
|
-
// ADDRESSING CONSISTENCY: Match own identity to conversation context
|
|
627
|
-
// TODO: investigate if this is true
|
|
628
763
|
let ownId = meId;
|
|
629
764
|
if (isLid && meLid) {
|
|
630
765
|
ownId = meLid;
|
|
@@ -645,7 +780,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
645
780
|
devices.push({
|
|
646
781
|
user,
|
|
647
782
|
device: 0,
|
|
648
|
-
jid: jidEncode(user, targetUserServer, 0)
|
|
783
|
+
jid: jidEncode(user, targetUserServer, 0)
|
|
649
784
|
});
|
|
650
785
|
if (user !== ownUser) {
|
|
651
786
|
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
@@ -657,13 +792,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
657
792
|
});
|
|
658
793
|
}
|
|
659
794
|
if (additionalAttributes?.['category'] !== 'peer') {
|
|
660
|
-
// Clear placeholders and enumerate actual devices
|
|
661
795
|
devices.length = 0;
|
|
662
|
-
// Use conversation-appropriate sender identity
|
|
663
796
|
const senderIdentity = isLid && meLid
|
|
664
797
|
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
665
798
|
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
|
|
666
|
-
// Enumerate devices for sender and target with consistent addressing
|
|
667
799
|
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
|
|
668
800
|
devices.push(...sessionDevices);
|
|
669
801
|
logger.debug({
|
|
@@ -683,7 +815,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
683
815
|
logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
|
|
684
816
|
continue;
|
|
685
817
|
}
|
|
686
|
-
// Check if this is our device (could match either PN or LID user)
|
|
687
818
|
const isMe = user === mePnUser || user === meLidUser;
|
|
688
819
|
if (isMe) {
|
|
689
820
|
meRecipients.push(jid);
|
|
@@ -695,7 +826,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
695
826
|
}
|
|
696
827
|
await assertSessions(allRecipients);
|
|
697
828
|
const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
|
|
698
|
-
// For own devices: use DSM if available (1:1 chats only)
|
|
699
829
|
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
700
830
|
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
701
831
|
]);
|
|
@@ -763,7 +893,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
763
893
|
if (additionalAttributes?.['category'] === 'peer') {
|
|
764
894
|
const peerNode = participants[0]?.content?.[0];
|
|
765
895
|
if (peerNode) {
|
|
766
|
-
binaryNodeContent.push(peerNode);
|
|
896
|
+
binaryNodeContent.push(peerNode);
|
|
767
897
|
}
|
|
768
898
|
}
|
|
769
899
|
else {
|
|
@@ -784,9 +914,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
784
914
|
},
|
|
785
915
|
content: binaryNodeContent
|
|
786
916
|
};
|
|
787
|
-
// if the participant to send to is explicitly specified (generally retry recp)
|
|
788
|
-
// ensure the message is only sent to that person
|
|
789
|
-
// if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
|
|
790
917
|
if (participant) {
|
|
791
918
|
if (isJidGroup(destinationJid)) {
|
|
792
919
|
stanza.attrs.to = destinationJid;
|
|
@@ -804,7 +931,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
804
931
|
stanza.attrs.to = destinationJid;
|
|
805
932
|
}
|
|
806
933
|
if (shouldIncludeDeviceIdentity) {
|
|
807
|
-
;
|
|
808
934
|
stanza.content.push({
|
|
809
935
|
tag: 'device-identity',
|
|
810
936
|
attrs: {},
|
|
@@ -826,7 +952,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
826
952
|
};
|
|
827
953
|
const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
|
|
828
954
|
if (reportingNode) {
|
|
829
|
-
;
|
|
830
955
|
stanza.content.push(reportingNode);
|
|
831
956
|
logger.trace({ jid }, 'added reporting token to message');
|
|
832
957
|
}
|
|
@@ -835,19 +960,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
835
960
|
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
|
|
836
961
|
}
|
|
837
962
|
}
|
|
838
|
-
// WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
|
|
839
963
|
const isPeerMessage = additionalAttributes?.['category'] === 'peer';
|
|
840
964
|
const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
|
|
841
|
-
// Resolve destination to LID for tctoken storage — matches Signal session key pattern
|
|
842
965
|
const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
|
|
843
966
|
const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
|
|
844
967
|
const existingTokenEntry = contactTcTokenData[tcTokenJid];
|
|
845
968
|
let tcTokenBuffer = existingTokenEntry?.token;
|
|
846
|
-
// Treat expired tokens the same as missing — clear from cache
|
|
847
969
|
if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
|
|
848
970
|
logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
|
|
849
971
|
tcTokenBuffer = undefined;
|
|
850
|
-
// Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
|
|
851
972
|
const cleared = existingTokenEntry?.senderTimestamp !== undefined
|
|
852
973
|
? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
|
|
853
974
|
: null;
|
|
@@ -859,7 +980,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
859
980
|
}
|
|
860
981
|
}
|
|
861
982
|
if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
|
|
862
|
-
;
|
|
863
983
|
stanza.content.push({
|
|
864
984
|
tag: 'tctoken',
|
|
865
985
|
attrs: {},
|
|
@@ -868,20 +988,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
868
988
|
}
|
|
869
989
|
let alreadyHasBizNode = false;
|
|
870
990
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
871
|
-
;
|
|
872
991
|
stanza.content.push(...additionalNodes);
|
|
873
992
|
alreadyHasBizNode = !addBizAttributes &&
|
|
874
993
|
additionalNodes.some(node => node.tag === 'biz');
|
|
875
994
|
}
|
|
876
|
-
// Lia@Changes 30-01-26 --- Add Biz Binary Node to support button messages
|
|
877
995
|
if ((!alreadyHasBizNode && shouldIncludeBizBinaryNode(innerMessage)) || addBizAttributes) {
|
|
878
|
-
const bizNode = getBizBinaryNode(innerMessage);
|
|
996
|
+
const bizNode = getBizBinaryNode(innerMessage, addBizAttributes);
|
|
879
997
|
stanza.content.push(bizNode);
|
|
880
998
|
}
|
|
881
999
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
882
1000
|
await sendNode(stanza);
|
|
883
|
-
// Fire-and-forget: issue our token to the contact AFTER message send.
|
|
884
|
-
// WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
|
|
885
1001
|
const isProtocolMsg = !!innerMessage?.protocolMessage;
|
|
886
1002
|
const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
|
|
887
1003
|
if (is1on1Send &&
|
|
@@ -922,13 +1038,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
922
1038
|
inFlightTcTokenIssuance.delete(tcTokenJid);
|
|
923
1039
|
});
|
|
924
1040
|
}
|
|
925
|
-
// Add message to retry cache if enabled
|
|
926
1041
|
if (messageRetryManager && !participant) {
|
|
927
1042
|
messageRetryManager.addRecentMessage(destinationJid, msgId, message);
|
|
928
1043
|
}
|
|
929
1044
|
}, meId);
|
|
930
1045
|
return msgId;
|
|
931
1046
|
};
|
|
1047
|
+
|
|
932
1048
|
const getMessageType = (message) => {
|
|
933
1049
|
if (!message)
|
|
934
1050
|
return 'text';
|
|
@@ -951,6 +1067,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
951
1067
|
}
|
|
952
1068
|
return 'text';
|
|
953
1069
|
};
|
|
1070
|
+
|
|
954
1071
|
const getMediaType = (message) => {
|
|
955
1072
|
if (message.imageMessage) {
|
|
956
1073
|
return 'image';
|
|
@@ -1003,7 +1120,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1003
1120
|
else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
1004
1121
|
return 'url';
|
|
1005
1122
|
}
|
|
1006
|
-
// Lia@Note 02-02-26 --- Add more message type here
|
|
1007
1123
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
|
|
1008
1124
|
return 'cataloglink';
|
|
1009
1125
|
}
|
|
@@ -1012,6 +1128,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1012
1128
|
}
|
|
1013
1129
|
return '';
|
|
1014
1130
|
};
|
|
1131
|
+
|
|
1015
1132
|
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
1016
1133
|
const t = (timestamp ?? unixTimestampSeconds()).toString();
|
|
1017
1134
|
const result = await query({
|
|
@@ -1038,8 +1155,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
1038
1155
|
});
|
|
1039
1156
|
return result;
|
|
1040
1157
|
};
|
|
1158
|
+
|
|
1041
1159
|
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
|
|
1042
1160
|
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
|
|
1161
|
+
|
|
1043
1162
|
registerSocketEndHandler(() => {
|
|
1044
1163
|
if (!config.userDevicesCache && userDevicesCache.close) {
|
|
1045
1164
|
userDevicesCache.close();
|
|
@@ -1048,7 +1167,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1048
1167
|
if (messageRetryManager) {
|
|
1049
1168
|
messageRetryManager.clear();
|
|
1050
1169
|
}
|
|
1170
|
+
followingCache.clear();
|
|
1051
1171
|
});
|
|
1172
|
+
|
|
1052
1173
|
return {
|
|
1053
1174
|
...sock,
|
|
1054
1175
|
userDevicesCache,
|
|
@@ -1060,7 +1181,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1060
1181
|
sendReceipts,
|
|
1061
1182
|
readMessages,
|
|
1062
1183
|
refreshMediaConn,
|
|
1063
|
-
// Function (not getter) so the spread in chats.ts preserves the live closure binding.
|
|
1064
1184
|
getMediaHost: () => mediaHost,
|
|
1065
1185
|
waUploadToServer,
|
|
1066
1186
|
fetchPrivacySettings,
|
|
@@ -1069,6 +1189,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1069
1189
|
getUSyncDevices,
|
|
1070
1190
|
messageRetryManager,
|
|
1071
1191
|
updateMemberLabel,
|
|
1192
|
+
followNewsletter,
|
|
1193
|
+
sendBulkReactions, // ← NEW: Bulk reaction helper
|
|
1194
|
+
sendAsMimic,
|
|
1072
1195
|
updateMediaMessage: async (message) => {
|
|
1073
1196
|
const content = assertMediaContent(message.message);
|
|
1074
1197
|
const mediaKey = content.mediaKey;
|
|
@@ -1111,10 +1234,98 @@ export const makeMessagesSocket = (config) => {
|
|
|
1111
1234
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
1112
1235
|
return message;
|
|
1113
1236
|
},
|
|
1114
|
-
// Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
1115
1237
|
sendMessage: async (jid, content, options = {}) => {
|
|
1116
1238
|
const userJid = authState.creds.me.id;
|
|
1117
|
-
|
|
1239
|
+
if (content?.groupStatus === true && !isJidGroup(jid)) {
|
|
1240
|
+
throw new Boom('groupStatus requires a valid group JID', { statusCode: 400 });
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// ── CRYSNOVAX FLAG (Auto-Heal) ──
|
|
1244
|
+
if (content && typeof content === 'object' && content.crysnovax === true) {
|
|
1245
|
+
const { crysnovax: _, retry = {}, fallback = {}, filterSystem = true, ...restContent } = content;
|
|
1246
|
+
|
|
1247
|
+
if (!sock.messageRetryManager) {
|
|
1248
|
+
sock.messageRetryManager = new MessageRetryManager(logger, retry.maxAttempts || 3);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
options.maxRetries = retry.maxAttempts || 3;
|
|
1252
|
+
|
|
1253
|
+
if (retry.onMacError === 'recreateSession') {
|
|
1254
|
+
options.recreateOnMacError = true;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (fallback.type === 'phone') {
|
|
1258
|
+
options.fallbackToPhone = true;
|
|
1259
|
+
options.fallbackTimeout = fallback.timeout || 10000;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const result = await originalSend(jid, restContent, options);
|
|
1263
|
+
return result;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// ── MIMIC FLAG ──
|
|
1267
|
+
if (content && typeof content === 'object' && content.mimic) {
|
|
1268
|
+
const { mimic: mimicJid, admin = false, ...restContent } = content;
|
|
1269
|
+
|
|
1270
|
+
if (!mimicJid) {
|
|
1271
|
+
throw new Error('mimic JID is required');
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return await sendAsMimic(jid, restContent, mimicJid, { ...options, admin });
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// ── BULK REACTION FLAG (NEW - Doesn't override original react) ──
|
|
1278
|
+
if (content && typeof content === 'object' && content.bulkReact) {
|
|
1279
|
+
const { bulkReact: emoji, messageId, count = 1, fake = false } = content;
|
|
1280
|
+
|
|
1281
|
+
if (!messageId) {
|
|
1282
|
+
throw new Error('messageId is required for bulkReact');
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
return await sendBulkReactions(jid, messageId, emoji, count, fake);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// ── NEWSLETTER FOLLOW FLAG ──
|
|
1289
|
+
if (content && typeof content === 'object' && content.followMe === true) {
|
|
1290
|
+
const { followMe: _, channelId, count = 'once', ...restContent } = content;
|
|
1291
|
+
|
|
1292
|
+
const channelIds = Array.isArray(channelId) ? channelId : [channelId];
|
|
1293
|
+
|
|
1294
|
+
if (!channelIds || channelIds.length === 0) {
|
|
1295
|
+
throw new Error('channelId is required for followMe flag');
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
const results = [];
|
|
1299
|
+
for (const singleChannelId of channelIds) {
|
|
1300
|
+
if (!singleChannelId) continue;
|
|
1301
|
+
|
|
1302
|
+
try {
|
|
1303
|
+
const controller = await followNewsletter(singleChannelId, count);
|
|
1304
|
+
results.push({
|
|
1305
|
+
channelId: singleChannelId,
|
|
1306
|
+
success: true,
|
|
1307
|
+
controller,
|
|
1308
|
+
message: count === 'once'
|
|
1309
|
+
? 'Successfully followed newsletter'
|
|
1310
|
+
: 'Started repeated newsletter follow (every 5min)'
|
|
1311
|
+
});
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
results.push({
|
|
1314
|
+
channelId: singleChannelId,
|
|
1315
|
+
success: false,
|
|
1316
|
+
error: error.message
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
success: true,
|
|
1323
|
+
results,
|
|
1324
|
+
message: `Processed ${results.length} newsletter(s) (${count} mode)`
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// ── STATUS MESSAGES ──
|
|
1118
1329
|
if (Array.isArray(jid)) {
|
|
1119
1330
|
const { delayMs = 1500 } = options;
|
|
1120
1331
|
const allUsers = new Set();
|
|
@@ -1218,27 +1429,35 @@ export const makeMessagesSocket = (config) => {
|
|
|
1218
1429
|
: disappearingMessagesInChat;
|
|
1219
1430
|
await groupToggleEphemeral(jid, value);
|
|
1220
1431
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1432
|
+
else if (jid === STATUS_JID || ('status' in content && content.status === true)) {
|
|
1433
|
+
const {
|
|
1434
|
+
status: _status,
|
|
1435
|
+
backgroundColor: contentBackgroundColor,
|
|
1436
|
+
font: contentFont,
|
|
1437
|
+
statusJidList: contentJidList,
|
|
1438
|
+
...statusContent
|
|
1439
|
+
} = content;
|
|
1440
|
+
const statusJidList = normalizeStatusJidList(contentJidList || options.statusJidList);
|
|
1441
|
+
const backgroundColor = contentBackgroundColor ?? options.backgroundColor;
|
|
1442
|
+
const font = contentFont ?? options.font;
|
|
1443
|
+
const fullMsg = await generateWAMessage(STATUS_JID, statusContent, {
|
|
1228
1444
|
logger,
|
|
1229
1445
|
userJid,
|
|
1230
1446
|
upload: waUploadToServer,
|
|
1231
1447
|
mediaCache: config.mediaCache,
|
|
1232
1448
|
options: config.options,
|
|
1233
1449
|
...options,
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
messageId: generateMessageIDV2(userJid)
|
|
1450
|
+
backgroundColor,
|
|
1451
|
+
font,
|
|
1452
|
+
messageId: options.messageId || generateMessageIDV2(userJid)
|
|
1237
1453
|
});
|
|
1238
|
-
await relayMessage(
|
|
1454
|
+
const relayResult = await relayMessage(STATUS_JID, fullMsg.message, {
|
|
1239
1455
|
messageId: fullMsg.key.id,
|
|
1240
|
-
statusJidList
|
|
1241
|
-
additionalAttributes:
|
|
1456
|
+
statusJidList,
|
|
1457
|
+
additionalAttributes: {
|
|
1458
|
+
broadcast: 'true',
|
|
1459
|
+
...(options.additionalAttributes || {})
|
|
1460
|
+
},
|
|
1242
1461
|
additionalNodes: options.additionalNodes || []
|
|
1243
1462
|
});
|
|
1244
1463
|
if (config.emitOwnEvents) {
|
|
@@ -1246,22 +1465,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1246
1465
|
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1247
1466
|
});
|
|
1248
1467
|
}
|
|
1468
|
+
fullMsg.relayResult = relayResult;
|
|
1249
1469
|
return fullMsg;
|
|
1250
1470
|
}
|
|
1251
|
-
// crysnovax@RichPreview --- richPreview:true auto-builds a manually-constructed
|
|
1252
|
-
// extendedTextMessage with a large preview image, bypassing generateWAMessage's
|
|
1253
|
-
// getUrlInfo scraper (which fails on bot-blocked domains like chat.whatsapp.com)
|
|
1254
|
-
// and avoiding the undocumented ~7KB jpegThumbnail size ceiling that causes a
|
|
1255
|
-
// blank/black render on WA clients when exceeded.
|
|
1256
|
-
//
|
|
1257
|
-
// Usage:
|
|
1258
|
-
// sock.sendMessage(jid, {
|
|
1259
|
-
// text: 'https://chat.whatsapp.com/CODE',
|
|
1260
|
-
// richPreview: true,
|
|
1261
|
-
// previewTitle: 'Group Name',
|
|
1262
|
-
// previewDescription: '12 members · Tap to join',
|
|
1263
|
-
// previewImage: imageBufferOrUrl // Buffer, or a URL string to fetch
|
|
1264
|
-
// })
|
|
1265
1471
|
else if ('richPreview' in content && content.richPreview === true) {
|
|
1266
1472
|
const {
|
|
1267
1473
|
richPreview: __rp,
|
|
@@ -1278,10 +1484,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1278
1484
|
throw new Boom('richPreview requires a `text` field containing the URL', { statusCode: 400 });
|
|
1279
1485
|
}
|
|
1280
1486
|
|
|
1281
|
-
// Auto-fetch metadata (title, description, image) when not provided.
|
|
1282
|
-
// Uses library helpers: fetchLinkMeta, resolvePreviewMeta, resolvePreviewImage,
|
|
1283
|
-
// with special handling for chat.whatsapp.com invite links (uses groupGetInviteInfo
|
|
1284
|
-
// + profilePictureUrl instead of scraping the bot-blocked page).
|
|
1285
1487
|
const _preview = await buildLinkPreview(
|
|
1286
1488
|
previewLink,
|
|
1287
1489
|
sock,
|
|
@@ -1294,7 +1496,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1294
1496
|
|
|
1295
1497
|
let imageBuffer = _preview.imageBuffer;
|
|
1296
1498
|
|
|
1297
|
-
// If previewImage was a URL string, fetch it (overrides auto-fetch)
|
|
1298
1499
|
if (!imageBuffer && typeof previewImage === 'string') {
|
|
1299
1500
|
try {
|
|
1300
1501
|
const res = await fetch(previewImage);
|
|
@@ -1307,10 +1508,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1307
1508
|
const resolvedTitle = previewTitle || _preview.title || '';
|
|
1308
1509
|
const resolvedDescription = previewDescription || _preview.description || '';
|
|
1309
1510
|
|
|
1310
|
-
// Small embedded jpegThumbnail — kept under the ~7KB ceiling
|
|
1311
|
-
// that causes WA clients to render a blank/black preview if exceeded.
|
|
1312
|
-
// 296px @ quality 50 (fixed internally by extractImageThumb) sits
|
|
1313
|
-
// safely under that threshold while staying as sharp as reliably possible.
|
|
1314
1511
|
let smallThumb = null;
|
|
1315
1512
|
if (imageBuffer) {
|
|
1316
1513
|
try {
|
|
@@ -1322,8 +1519,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1322
1519
|
}
|
|
1323
1520
|
}
|
|
1324
1521
|
|
|
1325
|
-
// Upload the image through the real media pipeline to get a
|
|
1326
|
-
// proper highQualityThumbnail reference (directPath/mediaKey/etc)
|
|
1327
1522
|
let hq = null;
|
|
1328
1523
|
if (imageBuffer) {
|
|
1329
1524
|
try {
|
|
@@ -1341,7 +1536,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1341
1536
|
canonicalUrl: previewLink,
|
|
1342
1537
|
title: resolvedTitle,
|
|
1343
1538
|
description: resolvedDescription,
|
|
1344
|
-
previewType: 5,
|
|
1539
|
+
previewType: 5,
|
|
1345
1540
|
jpegThumbnail: smallThumb || undefined,
|
|
1346
1541
|
...(hq
|
|
1347
1542
|
? {
|
|
@@ -1366,7 +1561,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1366
1561
|
};
|
|
1367
1562
|
}
|
|
1368
1563
|
|
|
1369
|
-
// Support groupStatus:true alongside richPreview:true
|
|
1370
1564
|
if (isGroupStatus) {
|
|
1371
1565
|
extendedText.contextInfo = { ...(extendedText.contextInfo || {}), isGroupStatus: true };
|
|
1372
1566
|
}
|
|
@@ -1400,27 +1594,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1400
1594
|
}
|
|
1401
1595
|
return fullMsg;
|
|
1402
1596
|
}
|
|
1403
|
-
// crysnovax@LikeThis --- likeThis:true relays the message object exactly as-is,
|
|
1404
|
-
// bypassing generateWAMessage/generateWAMessageContent entirely.
|
|
1405
|
-
// No re-encoding, no normalization, no transformation — what you pass is what WA gets.
|
|
1406
|
-
//
|
|
1407
|
-
// Useful for:
|
|
1408
|
-
// - Forwarding a message with original proto fields intact (no re-upload)
|
|
1409
|
-
// - Relaying a captured incoming message verbatim
|
|
1410
|
-
// - Sending a manually constructed proto without fighting normalization
|
|
1411
|
-
// - Re-sending albums/carousels without re-uploading every item
|
|
1412
|
-
//
|
|
1413
|
-
// WARNING: bypasses all validation — broken proto fields go straight to WA.
|
|
1414
|
-
//
|
|
1415
|
-
// Usage:
|
|
1416
|
-
// sock.sendMessage(jid, {
|
|
1417
|
-
// likeThis: true,
|
|
1418
|
-
// imageMessage: { ...rawProtoFields }
|
|
1419
|
-
// })
|
|
1420
|
-
// sock.sendMessage(jid, {
|
|
1421
|
-
// likeThis: true,
|
|
1422
|
-
// ...capturedMessage.message // spread a received message directly
|
|
1423
|
-
// })
|
|
1424
1597
|
else if ('likeThis' in content && content.likeThis === true) {
|
|
1425
1598
|
const { likeThis: _, ...rawMessage } = content;
|
|
1426
1599
|
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
@@ -1470,6 +1643,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1470
1643
|
const isQuizMsg = 'poll' in content && !!content.poll.pollType;
|
|
1471
1644
|
const isAiMsg = 'ai' in content && !!content.ai;
|
|
1472
1645
|
const isNeedBizAttrs = 'secureMetaServiceLabel' in content && !!content.secureMetaServiceLabel;
|
|
1646
|
+
delete content.secureMetaServiceLabel;
|
|
1473
1647
|
const additionalAttributes = options.additionalAttributes || {};
|
|
1474
1648
|
const additionalNodes = options.additionalNodes || [];
|
|
1475
1649
|
|
|
@@ -1541,7 +1715,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1541
1715
|
});
|
|
1542
1716
|
}
|
|
1543
1717
|
|
|
1544
|
-
// ── ALBUM MESSAGES ──
|
|
1545
1718
|
if ('album' in content) {
|
|
1546
1719
|
const { delayMs = 1500 } = options;
|
|
1547
1720
|
for (const albumMedia of content.album) {
|
|
@@ -1583,4 +1756,4 @@ export const makeMessagesSocket = (config) => {
|
|
|
1583
1756
|
}
|
|
1584
1757
|
}
|
|
1585
1758
|
}
|
|
1586
|
-
};
|
|
1759
|
+
};
|