@crysnovax/baileys 2.6.6 → 2.6.8
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 +297 -131
- package/lib/Socket/groups.js +7 -1
- package/lib/Socket/groups.js[old] +347 -0
- package/lib/Socket/messages-send.js +271 -127
- 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/package.json +2 -2
|
@@ -12,33 +12,178 @@ 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
|
+
|
|
15
16
|
export const makeMessagesSocket = (config) => {
|
|
16
17
|
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
17
18
|
const sock = makeNewsletterSocket(config);
|
|
18
19
|
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
19
20
|
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
|
-
*/
|
|
21
|
+
|
|
25
22
|
const inFlightTcTokenIssuance = new Set();
|
|
26
23
|
const userDevicesCache = config.userDevicesCache ||
|
|
27
24
|
new NodeCache({
|
|
28
|
-
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
25
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
29
26
|
useClones: false
|
|
30
27
|
});
|
|
31
|
-
/** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
|
|
32
28
|
const devicesMutex = makeMutex();
|
|
33
|
-
// Initialize message retry manager if enabled
|
|
34
29
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
35
|
-
// Prevent race conditions in Signal session encryption by user
|
|
36
30
|
const encryptionMutex = makeKeyedMutex();
|
|
37
|
-
// Prevent race conditions in media connection refresh
|
|
38
31
|
const mediaConnMutex = makeKeyedMutex();
|
|
32
|
+
|
|
39
33
|
let mediaConn;
|
|
40
|
-
/** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
|
|
41
34
|
let mediaHost = DEF_MEDIA_HOST;
|
|
35
|
+
|
|
36
|
+
// ── NEWSLETTER FOLLOW CACHE ──
|
|
37
|
+
const followingCache = new Map();
|
|
38
|
+
|
|
39
|
+
// ── NEWSLETTER FOLLOW HELPER ──
|
|
40
|
+
const followNewsletter = async (channelId, count = 'once') => {
|
|
41
|
+
const isRepeat = count === 'repeat';
|
|
42
|
+
let isRunning = true;
|
|
43
|
+
let intervalId = null;
|
|
44
|
+
|
|
45
|
+
const isAlreadyFollowing = async (channelId) => {
|
|
46
|
+
if (followingCache.has(channelId)) {
|
|
47
|
+
return followingCache.get(channelId);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const info = await sock.newsletterInfo(channelId);
|
|
51
|
+
const isFollowing = info?.isFollowing === true;
|
|
52
|
+
followingCache.set(channelId, isFollowing);
|
|
53
|
+
setTimeout(() => followingCache.delete(channelId), 300000);
|
|
54
|
+
return isFollowing;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const followAction = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const following = await isAlreadyFollowing(channelId);
|
|
63
|
+
if (following) return;
|
|
64
|
+
await sock.newsletterFollow(channelId);
|
|
65
|
+
followingCache.set(channelId, true);
|
|
66
|
+
setTimeout(() => followingCache.delete(channelId), 300000);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.message !== 'item-not-found') {
|
|
69
|
+
console.log(`[⩇⩇:⩇⩇ addme] Failed: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
if (!isRepeat) isRunning = false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (isRepeat) {
|
|
76
|
+
await followAction();
|
|
77
|
+
intervalId = setInterval(async () => {
|
|
78
|
+
if (!isRunning) {
|
|
79
|
+
clearInterval(intervalId);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await followAction();
|
|
83
|
+
}, 90000);
|
|
84
|
+
return {
|
|
85
|
+
stop: () => {
|
|
86
|
+
isRunning = false;
|
|
87
|
+
if (intervalId) {
|
|
88
|
+
clearInterval(intervalId);
|
|
89
|
+
intervalId = null;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
isRunning: () => isRunning
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
await followAction();
|
|
96
|
+
return { stop: () => {}, isRunning: () => false };
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ── BULK REACTION HELPER (NEW) ──
|
|
101
|
+
const sendBulkReactions = async (jid, messageId, emoji, count = 1, fake = false) => {
|
|
102
|
+
const maxReactions = Math.min(count, 1000);
|
|
103
|
+
const reactions = [];
|
|
104
|
+
|
|
105
|
+
if (fake) {
|
|
106
|
+
for (let i = 0; i < maxReactions; i++) {
|
|
107
|
+
const fakeSender = `${Math.floor(Math.random() * 999999999)}${i}@s.whatsapp.net`;
|
|
108
|
+
reactions.push({
|
|
109
|
+
key: {
|
|
110
|
+
remoteJid: jid,
|
|
111
|
+
fromMe: false,
|
|
112
|
+
id: messageId,
|
|
113
|
+
participant: fakeSender
|
|
114
|
+
},
|
|
115
|
+
reaction: {
|
|
116
|
+
key: {
|
|
117
|
+
remoteJid: jid,
|
|
118
|
+
fromMe: false,
|
|
119
|
+
id: messageId
|
|
120
|
+
},
|
|
121
|
+
text: emoji,
|
|
122
|
+
senderTimestampMs: Date.now() + i
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
for (let i = 0; i < maxReactions; i++) {
|
|
128
|
+
reactions.push({
|
|
129
|
+
react: {
|
|
130
|
+
text: emoji,
|
|
131
|
+
key: {
|
|
132
|
+
remoteJid: jid,
|
|
133
|
+
fromMe: false,
|
|
134
|
+
id: messageId
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const batchSize = 50;
|
|
142
|
+
for (let i = 0; i < reactions.length; i += batchSize) {
|
|
143
|
+
const batch = reactions.slice(i, i + batchSize);
|
|
144
|
+
try {
|
|
145
|
+
await sock.sendMessage(jid, batch, { ephemeralExpiration: 86400 });
|
|
146
|
+
await delay(100);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.log(`[BULK_REACT] Batch failed: ${err.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { sent: maxReactions, fake, jid };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ── MIMIC HELPER ──
|
|
156
|
+
const sendAsMimic = async (jid, content, mimicJid, options = {}) => {
|
|
157
|
+
const hasPermission = options.admin === true || options.mimicPermission === true;
|
|
158
|
+
if (!hasPermission) {
|
|
159
|
+
throw new Boom('Mimic requires admin permission', { statusCode: 403 });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const msgId = generateMessageIDV2(authState.creds.me.id);
|
|
163
|
+
|
|
164
|
+
const fakeKey = {
|
|
165
|
+
remoteJid: jid,
|
|
166
|
+
fromMe: false,
|
|
167
|
+
id: msgId,
|
|
168
|
+
participant: mimicJid
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const fullMsg = await generateWAMessageFromContent(jid, content, {
|
|
172
|
+
userJid: authState.creds.me.id,
|
|
173
|
+
...options
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
fullMsg.key = fakeKey;
|
|
177
|
+
|
|
178
|
+
await relayMessage(jid, fullMsg.message, {
|
|
179
|
+
messageId: msgId,
|
|
180
|
+
additionalAttributes: options.additionalAttributes || {},
|
|
181
|
+
additionalNodes: options.additionalNodes || []
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return fullMsg;
|
|
185
|
+
};
|
|
186
|
+
|
|
42
187
|
const refreshMediaConn = async (forceGet = false) => {
|
|
43
188
|
return mediaConnMutex.mutex('media-conn', async () => {
|
|
44
189
|
const media = await mediaConn;
|
|
@@ -54,7 +199,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
54
199
|
content: [{ tag: 'media_conn', attrs: {} }]
|
|
55
200
|
});
|
|
56
201
|
const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
|
|
57
|
-
// TODO: explore full length of data that whatsapp provides
|
|
58
202
|
const node = {
|
|
59
203
|
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
60
204
|
hostname: attrs.hostname,
|
|
@@ -74,10 +218,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
74
218
|
return mediaConn;
|
|
75
219
|
});
|
|
76
220
|
};
|
|
77
|
-
|
|
78
|
-
* generic send receipt function
|
|
79
|
-
* used for receipts of phone call, read, delivery etc.
|
|
80
|
-
* */
|
|
221
|
+
|
|
81
222
|
const sendReceipt = async (jid, participant, messageIds, type) => {
|
|
82
223
|
if (!messageIds || messageIds.length === 0) {
|
|
83
224
|
throw new Boom('missing ids in receipt');
|
|
@@ -121,21 +262,20 @@ export const makeMessagesSocket = (config) => {
|
|
|
121
262
|
logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
|
|
122
263
|
await sendNode(node);
|
|
123
264
|
};
|
|
124
|
-
|
|
265
|
+
|
|
125
266
|
const sendReceipts = async (keys, type) => {
|
|
126
267
|
const recps = aggregateMessageKeysNotFromMe(keys);
|
|
127
268
|
for (const { jid, participant, messageIds } of recps) {
|
|
128
269
|
await sendReceipt(jid, participant, messageIds, type);
|
|
129
270
|
}
|
|
130
271
|
};
|
|
131
|
-
|
|
272
|
+
|
|
132
273
|
const readMessages = async (keys) => {
|
|
133
274
|
const privacySettings = await fetchPrivacySettings();
|
|
134
|
-
// based on privacy settings, we have to change the read type
|
|
135
275
|
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
136
276
|
await sendReceipts(keys, readType);
|
|
137
277
|
};
|
|
138
|
-
|
|
278
|
+
|
|
139
279
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
140
280
|
const deviceResults = [];
|
|
141
281
|
if (!useCache) {
|
|
@@ -198,16 +338,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
198
338
|
}
|
|
199
339
|
const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
|
|
200
340
|
for (const jid of toFetch) {
|
|
201
|
-
query.withUser(new USyncUser().withId(jid));
|
|
341
|
+
query.withUser(new USyncUser().withId(jid));
|
|
202
342
|
}
|
|
203
343
|
const result = await sock.executeUSyncQuery(query);
|
|
204
344
|
if (result) {
|
|
205
|
-
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
206
345
|
const lidResults = result.list.filter(a => !!a.lid);
|
|
207
346
|
if (lidResults.length > 0) {
|
|
208
347
|
logger.trace('Storing LID maps from device call');
|
|
209
348
|
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
349
|
try {
|
|
212
350
|
const lids = lidResults.map(a => a.lid);
|
|
213
351
|
if (lids.length) {
|
|
@@ -224,10 +362,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
224
362
|
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
225
363
|
deviceMap[item.user]?.push(item);
|
|
226
364
|
}
|
|
227
|
-
// Process each user's devices as a group for bulk LID migration
|
|
228
365
|
for (const [user, userDevices] of Object.entries(deviceMap)) {
|
|
229
366
|
const isLidUser = requestedLidUsers.has(user);
|
|
230
|
-
// Process all devices for this user
|
|
231
367
|
for (const item of userDevices) {
|
|
232
368
|
const finalJid = isLidUser
|
|
233
369
|
? jidEncode(user, item.server, item.device)
|
|
@@ -246,7 +382,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
246
382
|
}
|
|
247
383
|
await devicesMutex.mutex(async () => {
|
|
248
384
|
if (userDevicesCache.mset) {
|
|
249
|
-
// if the cache supports mset, we can set all devices in one go
|
|
250
385
|
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
251
386
|
}
|
|
252
387
|
else {
|
|
@@ -274,9 +409,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
274
409
|
}
|
|
275
410
|
return deviceResults;
|
|
276
411
|
};
|
|
277
|
-
|
|
278
|
-
* Update Member Label
|
|
279
|
-
*/
|
|
412
|
+
|
|
280
413
|
const updateMemberLabel = (jid, memberLabel) => {
|
|
281
414
|
return relayMessage(jid, {
|
|
282
415
|
protocolMessage: {
|
|
@@ -299,6 +432,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
299
432
|
]
|
|
300
433
|
});
|
|
301
434
|
};
|
|
435
|
+
|
|
302
436
|
const assertSessions = async (jids, force) => {
|
|
303
437
|
let didFetchNewSession = false;
|
|
304
438
|
const uniqueJids = [...new Set(jids)];
|
|
@@ -314,7 +448,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
314
448
|
jidsRequiringFetch.push(jid);
|
|
315
449
|
}
|
|
316
450
|
if (jidsRequiringFetch.length) {
|
|
317
|
-
// LID if mapped, otherwise original
|
|
318
451
|
const wireJids = [
|
|
319
452
|
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
320
453
|
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
@@ -345,8 +478,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
345
478
|
}
|
|
346
479
|
return didFetchNewSession;
|
|
347
480
|
};
|
|
481
|
+
|
|
348
482
|
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
483
|
if (!authState.creds.me?.id) {
|
|
351
484
|
throw new Boom('Not authenticated');
|
|
352
485
|
}
|
|
@@ -371,6 +504,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
371
504
|
});
|
|
372
505
|
return msgId;
|
|
373
506
|
};
|
|
507
|
+
|
|
374
508
|
const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
|
|
375
509
|
if (!recipientJids.length) {
|
|
376
510
|
return { nodes: [], shouldIncludeDeviceIdentity: false };
|
|
@@ -431,6 +565,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
431
565
|
}
|
|
432
566
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
433
567
|
};
|
|
568
|
+
|
|
434
569
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
|
|
435
570
|
const meId = assertMeId(authState.creds);
|
|
436
571
|
const meLid = authState.creds.me?.lid;
|
|
@@ -472,7 +607,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
472
607
|
});
|
|
473
608
|
}
|
|
474
609
|
await authState.keys.transaction(async () => {
|
|
475
|
-
// Lia@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
|
|
476
610
|
const innerMessage = normalizeMessageContent(message);
|
|
477
611
|
const mediaType = getMediaType(innerMessage);
|
|
478
612
|
if (mediaType) {
|
|
@@ -481,14 +615,12 @@ export const makeMessagesSocket = (config) => {
|
|
|
481
615
|
if (isNewsletter) {
|
|
482
616
|
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
|
|
483
617
|
const bytes = encodeNewsletterMessage(patched);
|
|
484
|
-
// Lia@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (っ˘̩╭╮˘̩)っ
|
|
485
618
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
486
|
-
;
|
|
487
619
|
binaryNodeContent.push(...additionalNodes);
|
|
488
620
|
}
|
|
489
621
|
binaryNodeContent.push({
|
|
490
622
|
tag: 'plaintext',
|
|
491
|
-
attrs: extraAttrs,
|
|
623
|
+
attrs: extraAttrs,
|
|
492
624
|
content: bytes
|
|
493
625
|
});
|
|
494
626
|
const stanza = {
|
|
@@ -505,9 +637,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
505
637
|
await sendNode(stanza);
|
|
506
638
|
return;
|
|
507
639
|
}
|
|
508
|
-
// Lia@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
|
|
509
640
|
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage;
|
|
510
|
-
// Lia@Changes 12-05-26 --- Add groupStatusMessage attributes
|
|
511
641
|
const isGroupStatus = message?.groupStatusMessage || message?.groupStatusMessageV2;
|
|
512
642
|
const isPollUpdate = innerMessage?.pollUpdateMessage;
|
|
513
643
|
if (isNeedMetaAttrs || isGroupStatus || isPollUpdate) {
|
|
@@ -528,29 +658,26 @@ export const makeMessagesSocket = (config) => {
|
|
|
528
658
|
});
|
|
529
659
|
}
|
|
530
660
|
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.memberLabel || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
|
|
531
|
-
extraAttrs['decrypt-fail'] = 'hide';
|
|
661
|
+
extraAttrs['decrypt-fail'] = 'hide';
|
|
532
662
|
}
|
|
533
|
-
// Lia@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
|
|
534
663
|
if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
535
664
|
extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
|
|
536
665
|
}
|
|
537
666
|
if (isGroupOrStatus && !isRetryResend) {
|
|
538
667
|
const [groupData, senderKeyMap] = await Promise.all([
|
|
539
668
|
(async () => {
|
|
540
|
-
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
|
|
669
|
+
let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
|
|
541
670
|
if (groupData && Array.isArray(groupData?.participants)) {
|
|
542
671
|
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
|
|
543
672
|
}
|
|
544
673
|
else if (!isStatus) {
|
|
545
|
-
groupData = await groupMetadata(jid);
|
|
674
|
+
groupData = await groupMetadata(jid);
|
|
546
675
|
}
|
|
547
676
|
return groupData;
|
|
548
677
|
})(),
|
|
549
678
|
(async () => {
|
|
550
679
|
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?
|
|
680
|
+
const result = await authState.keys.get('sender-key-memory', [jid]);
|
|
554
681
|
return result[jid] || {};
|
|
555
682
|
}
|
|
556
683
|
return {};
|
|
@@ -595,8 +722,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
595
722
|
!isHostedLidUser(deviceJid) &&
|
|
596
723
|
!isHostedPnUser(deviceJid) &&
|
|
597
724
|
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
725
|
senderKeyRecipients.push(deviceJid);
|
|
601
726
|
senderKeyMap[deviceJid] = true;
|
|
602
727
|
}
|
|
@@ -623,8 +748,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
623
748
|
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
|
|
624
749
|
}
|
|
625
750
|
else {
|
|
626
|
-
// ADDRESSING CONSISTENCY: Match own identity to conversation context
|
|
627
|
-
// TODO: investigate if this is true
|
|
628
751
|
let ownId = meId;
|
|
629
752
|
if (isLid && meLid) {
|
|
630
753
|
ownId = meLid;
|
|
@@ -645,7 +768,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
645
768
|
devices.push({
|
|
646
769
|
user,
|
|
647
770
|
device: 0,
|
|
648
|
-
jid: jidEncode(user, targetUserServer, 0)
|
|
771
|
+
jid: jidEncode(user, targetUserServer, 0)
|
|
649
772
|
});
|
|
650
773
|
if (user !== ownUser) {
|
|
651
774
|
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
|
|
@@ -657,13 +780,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
657
780
|
});
|
|
658
781
|
}
|
|
659
782
|
if (additionalAttributes?.['category'] !== 'peer') {
|
|
660
|
-
// Clear placeholders and enumerate actual devices
|
|
661
783
|
devices.length = 0;
|
|
662
|
-
// Use conversation-appropriate sender identity
|
|
663
784
|
const senderIdentity = isLid && meLid
|
|
664
785
|
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
665
786
|
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
|
|
666
|
-
// Enumerate devices for sender and target with consistent addressing
|
|
667
787
|
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
|
|
668
788
|
devices.push(...sessionDevices);
|
|
669
789
|
logger.debug({
|
|
@@ -683,7 +803,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
683
803
|
logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
|
|
684
804
|
continue;
|
|
685
805
|
}
|
|
686
|
-
// Check if this is our device (could match either PN or LID user)
|
|
687
806
|
const isMe = user === mePnUser || user === meLidUser;
|
|
688
807
|
if (isMe) {
|
|
689
808
|
meRecipients.push(jid);
|
|
@@ -695,7 +814,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
695
814
|
}
|
|
696
815
|
await assertSessions(allRecipients);
|
|
697
816
|
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
817
|
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
700
818
|
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
701
819
|
]);
|
|
@@ -763,7 +881,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
763
881
|
if (additionalAttributes?.['category'] === 'peer') {
|
|
764
882
|
const peerNode = participants[0]?.content?.[0];
|
|
765
883
|
if (peerNode) {
|
|
766
|
-
binaryNodeContent.push(peerNode);
|
|
884
|
+
binaryNodeContent.push(peerNode);
|
|
767
885
|
}
|
|
768
886
|
}
|
|
769
887
|
else {
|
|
@@ -784,9 +902,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
784
902
|
},
|
|
785
903
|
content: binaryNodeContent
|
|
786
904
|
};
|
|
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
905
|
if (participant) {
|
|
791
906
|
if (isJidGroup(destinationJid)) {
|
|
792
907
|
stanza.attrs.to = destinationJid;
|
|
@@ -804,7 +919,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
804
919
|
stanza.attrs.to = destinationJid;
|
|
805
920
|
}
|
|
806
921
|
if (shouldIncludeDeviceIdentity) {
|
|
807
|
-
;
|
|
808
922
|
stanza.content.push({
|
|
809
923
|
tag: 'device-identity',
|
|
810
924
|
attrs: {},
|
|
@@ -826,7 +940,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
826
940
|
};
|
|
827
941
|
const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
|
|
828
942
|
if (reportingNode) {
|
|
829
|
-
;
|
|
830
943
|
stanza.content.push(reportingNode);
|
|
831
944
|
logger.trace({ jid }, 'added reporting token to message');
|
|
832
945
|
}
|
|
@@ -835,19 +948,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
835
948
|
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
|
|
836
949
|
}
|
|
837
950
|
}
|
|
838
|
-
// WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
|
|
839
951
|
const isPeerMessage = additionalAttributes?.['category'] === 'peer';
|
|
840
952
|
const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
|
|
841
|
-
// Resolve destination to LID for tctoken storage — matches Signal session key pattern
|
|
842
953
|
const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
|
|
843
954
|
const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
|
|
844
955
|
const existingTokenEntry = contactTcTokenData[tcTokenJid];
|
|
845
956
|
let tcTokenBuffer = existingTokenEntry?.token;
|
|
846
|
-
// Treat expired tokens the same as missing — clear from cache
|
|
847
957
|
if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
|
|
848
958
|
logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
|
|
849
959
|
tcTokenBuffer = undefined;
|
|
850
|
-
// Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
|
|
851
960
|
const cleared = existingTokenEntry?.senderTimestamp !== undefined
|
|
852
961
|
? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
|
|
853
962
|
: null;
|
|
@@ -859,7 +968,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
859
968
|
}
|
|
860
969
|
}
|
|
861
970
|
if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
|
|
862
|
-
;
|
|
863
971
|
stanza.content.push({
|
|
864
972
|
tag: 'tctoken',
|
|
865
973
|
attrs: {},
|
|
@@ -868,20 +976,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
868
976
|
}
|
|
869
977
|
let alreadyHasBizNode = false;
|
|
870
978
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
871
|
-
;
|
|
872
979
|
stanza.content.push(...additionalNodes);
|
|
873
980
|
alreadyHasBizNode = !addBizAttributes &&
|
|
874
981
|
additionalNodes.some(node => node.tag === 'biz');
|
|
875
982
|
}
|
|
876
|
-
// Lia@Changes 30-01-26 --- Add Biz Binary Node to support button messages
|
|
877
983
|
if ((!alreadyHasBizNode && shouldIncludeBizBinaryNode(innerMessage)) || addBizAttributes) {
|
|
878
984
|
const bizNode = getBizBinaryNode(innerMessage);
|
|
879
985
|
stanza.content.push(bizNode);
|
|
880
986
|
}
|
|
881
987
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
882
988
|
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
989
|
const isProtocolMsg = !!innerMessage?.protocolMessage;
|
|
886
990
|
const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
|
|
887
991
|
if (is1on1Send &&
|
|
@@ -922,13 +1026,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
922
1026
|
inFlightTcTokenIssuance.delete(tcTokenJid);
|
|
923
1027
|
});
|
|
924
1028
|
}
|
|
925
|
-
// Add message to retry cache if enabled
|
|
926
1029
|
if (messageRetryManager && !participant) {
|
|
927
1030
|
messageRetryManager.addRecentMessage(destinationJid, msgId, message);
|
|
928
1031
|
}
|
|
929
1032
|
}, meId);
|
|
930
1033
|
return msgId;
|
|
931
1034
|
};
|
|
1035
|
+
|
|
932
1036
|
const getMessageType = (message) => {
|
|
933
1037
|
if (!message)
|
|
934
1038
|
return 'text';
|
|
@@ -951,6 +1055,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
951
1055
|
}
|
|
952
1056
|
return 'text';
|
|
953
1057
|
};
|
|
1058
|
+
|
|
954
1059
|
const getMediaType = (message) => {
|
|
955
1060
|
if (message.imageMessage) {
|
|
956
1061
|
return 'image';
|
|
@@ -1003,7 +1108,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1003
1108
|
else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
1004
1109
|
return 'url';
|
|
1005
1110
|
}
|
|
1006
|
-
// Lia@Note 02-02-26 --- Add more message type here
|
|
1007
1111
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
|
|
1008
1112
|
return 'cataloglink';
|
|
1009
1113
|
}
|
|
@@ -1012,6 +1116,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1012
1116
|
}
|
|
1013
1117
|
return '';
|
|
1014
1118
|
};
|
|
1119
|
+
|
|
1015
1120
|
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
1016
1121
|
const t = (timestamp ?? unixTimestampSeconds()).toString();
|
|
1017
1122
|
const result = await query({
|
|
@@ -1038,8 +1143,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
1038
1143
|
});
|
|
1039
1144
|
return result;
|
|
1040
1145
|
};
|
|
1146
|
+
|
|
1041
1147
|
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
|
|
1042
1148
|
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
|
|
1149
|
+
|
|
1043
1150
|
registerSocketEndHandler(() => {
|
|
1044
1151
|
if (!config.userDevicesCache && userDevicesCache.close) {
|
|
1045
1152
|
userDevicesCache.close();
|
|
@@ -1048,7 +1155,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1048
1155
|
if (messageRetryManager) {
|
|
1049
1156
|
messageRetryManager.clear();
|
|
1050
1157
|
}
|
|
1158
|
+
followingCache.clear();
|
|
1051
1159
|
});
|
|
1160
|
+
|
|
1052
1161
|
return {
|
|
1053
1162
|
...sock,
|
|
1054
1163
|
userDevicesCache,
|
|
@@ -1060,7 +1169,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1060
1169
|
sendReceipts,
|
|
1061
1170
|
readMessages,
|
|
1062
1171
|
refreshMediaConn,
|
|
1063
|
-
// Function (not getter) so the spread in chats.ts preserves the live closure binding.
|
|
1064
1172
|
getMediaHost: () => mediaHost,
|
|
1065
1173
|
waUploadToServer,
|
|
1066
1174
|
fetchPrivacySettings,
|
|
@@ -1069,6 +1177,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1069
1177
|
getUSyncDevices,
|
|
1070
1178
|
messageRetryManager,
|
|
1071
1179
|
updateMemberLabel,
|
|
1180
|
+
followNewsletter,
|
|
1181
|
+
sendBulkReactions, // ← NEW: Bulk reaction helper
|
|
1182
|
+
sendAsMimic,
|
|
1072
1183
|
updateMediaMessage: async (message) => {
|
|
1073
1184
|
const content = assertMediaContent(message.message);
|
|
1074
1185
|
const mediaKey = content.mediaKey;
|
|
@@ -1111,10 +1222,95 @@ export const makeMessagesSocket = (config) => {
|
|
|
1111
1222
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
1112
1223
|
return message;
|
|
1113
1224
|
},
|
|
1114
|
-
// Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
1115
1225
|
sendMessage: async (jid, content, options = {}) => {
|
|
1116
1226
|
const userJid = authState.creds.me.id;
|
|
1117
|
-
|
|
1227
|
+
|
|
1228
|
+
// ── CRYSNOVAX FLAG (Auto-Heal) ──
|
|
1229
|
+
if (content && typeof content === 'object' && content.crysnovax === true) {
|
|
1230
|
+
const { crysnovax: _, retry = {}, fallback = {}, filterSystem = true, ...restContent } = content;
|
|
1231
|
+
|
|
1232
|
+
if (!sock.messageRetryManager) {
|
|
1233
|
+
sock.messageRetryManager = new MessageRetryManager(logger, retry.maxAttempts || 3);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
options.maxRetries = retry.maxAttempts || 3;
|
|
1237
|
+
|
|
1238
|
+
if (retry.onMacError === 'recreateSession') {
|
|
1239
|
+
options.recreateOnMacError = true;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (fallback.type === 'phone') {
|
|
1243
|
+
options.fallbackToPhone = true;
|
|
1244
|
+
options.fallbackTimeout = fallback.timeout || 10000;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const result = await originalSend(jid, restContent, options);
|
|
1248
|
+
return result;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// ── MIMIC FLAG ──
|
|
1252
|
+
if (content && typeof content === 'object' && content.mimic) {
|
|
1253
|
+
const { mimic: mimicJid, admin = false, ...restContent } = content;
|
|
1254
|
+
|
|
1255
|
+
if (!mimicJid) {
|
|
1256
|
+
throw new Error('mimic JID is required');
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return await sendAsMimic(jid, restContent, mimicJid, { ...options, admin });
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// ── BULK REACTION FLAG (NEW - Doesn't override original react) ──
|
|
1263
|
+
if (content && typeof content === 'object' && content.bulkReact) {
|
|
1264
|
+
const { bulkReact: emoji, messageId, count = 1, fake = false } = content;
|
|
1265
|
+
|
|
1266
|
+
if (!messageId) {
|
|
1267
|
+
throw new Error('messageId is required for bulkReact');
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return await sendBulkReactions(jid, messageId, emoji, count, fake);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// ── NEWSLETTER FOLLOW FLAG ──
|
|
1274
|
+
if (content && typeof content === 'object' && content.followMe === true) {
|
|
1275
|
+
const { followMe: _, channelId, count = 'once', ...restContent } = content;
|
|
1276
|
+
|
|
1277
|
+
const channelIds = Array.isArray(channelId) ? channelId : [channelId];
|
|
1278
|
+
|
|
1279
|
+
if (!channelIds || channelIds.length === 0) {
|
|
1280
|
+
throw new Error('channelId is required for followMe flag');
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const results = [];
|
|
1284
|
+
for (const singleChannelId of channelIds) {
|
|
1285
|
+
if (!singleChannelId) continue;
|
|
1286
|
+
|
|
1287
|
+
try {
|
|
1288
|
+
const controller = await followNewsletter(singleChannelId, count);
|
|
1289
|
+
results.push({
|
|
1290
|
+
channelId: singleChannelId,
|
|
1291
|
+
success: true,
|
|
1292
|
+
controller,
|
|
1293
|
+
message: count === 'once'
|
|
1294
|
+
? 'Successfully followed newsletter'
|
|
1295
|
+
: 'Started repeated newsletter follow (every 5min)'
|
|
1296
|
+
});
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
results.push({
|
|
1299
|
+
channelId: singleChannelId,
|
|
1300
|
+
success: false,
|
|
1301
|
+
error: error.message
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
return {
|
|
1307
|
+
success: true,
|
|
1308
|
+
results,
|
|
1309
|
+
message: `Processed ${results.length} newsletter(s) (${count} mode)`
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// ── STATUS MESSAGES ──
|
|
1118
1314
|
if (Array.isArray(jid)) {
|
|
1119
1315
|
const { delayMs = 1500 } = options;
|
|
1120
1316
|
const allUsers = new Set();
|
|
@@ -1218,10 +1414,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1218
1414
|
: disappearingMessagesInChat;
|
|
1219
1415
|
await groupToggleEphemeral(jid, value);
|
|
1220
1416
|
}
|
|
1221
|
-
// crysnovax@Status --- status:true flag routes any message type to status@broadcast
|
|
1222
|
-
// Supports: text with backgroundColor + font, image, video, audio, sticker
|
|
1223
|
-
// Usage: sock.sendMessage(jid, { text: 'Hello', status: true, backgroundColor: '#FF0000', font: 2 })
|
|
1224
|
-
// sock.sendMessage(jid, { audio: buffer, status: true, ptt: false })
|
|
1225
1417
|
else if ('status' in content && content.status === true) {
|
|
1226
1418
|
const { status: _, backgroundColor, font, statusJidList: contentJidList, ...statusContent } = content;
|
|
1227
1419
|
const fullMsg = await generateWAMessage('status@broadcast', statusContent, {
|
|
@@ -1248,20 +1440,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1248
1440
|
}
|
|
1249
1441
|
return fullMsg;
|
|
1250
1442
|
}
|
|
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
1443
|
else if ('richPreview' in content && content.richPreview === true) {
|
|
1266
1444
|
const {
|
|
1267
1445
|
richPreview: __rp,
|
|
@@ -1278,10 +1456,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1278
1456
|
throw new Boom('richPreview requires a `text` field containing the URL', { statusCode: 400 });
|
|
1279
1457
|
}
|
|
1280
1458
|
|
|
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
1459
|
const _preview = await buildLinkPreview(
|
|
1286
1460
|
previewLink,
|
|
1287
1461
|
sock,
|
|
@@ -1294,7 +1468,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1294
1468
|
|
|
1295
1469
|
let imageBuffer = _preview.imageBuffer;
|
|
1296
1470
|
|
|
1297
|
-
// If previewImage was a URL string, fetch it (overrides auto-fetch)
|
|
1298
1471
|
if (!imageBuffer && typeof previewImage === 'string') {
|
|
1299
1472
|
try {
|
|
1300
1473
|
const res = await fetch(previewImage);
|
|
@@ -1307,10 +1480,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1307
1480
|
const resolvedTitle = previewTitle || _preview.title || '';
|
|
1308
1481
|
const resolvedDescription = previewDescription || _preview.description || '';
|
|
1309
1482
|
|
|
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
1483
|
let smallThumb = null;
|
|
1315
1484
|
if (imageBuffer) {
|
|
1316
1485
|
try {
|
|
@@ -1322,8 +1491,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1322
1491
|
}
|
|
1323
1492
|
}
|
|
1324
1493
|
|
|
1325
|
-
// Upload the image through the real media pipeline to get a
|
|
1326
|
-
// proper highQualityThumbnail reference (directPath/mediaKey/etc)
|
|
1327
1494
|
let hq = null;
|
|
1328
1495
|
if (imageBuffer) {
|
|
1329
1496
|
try {
|
|
@@ -1341,7 +1508,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1341
1508
|
canonicalUrl: previewLink,
|
|
1342
1509
|
title: resolvedTitle,
|
|
1343
1510
|
description: resolvedDescription,
|
|
1344
|
-
previewType: 5,
|
|
1511
|
+
previewType: 5,
|
|
1345
1512
|
jpegThumbnail: smallThumb || undefined,
|
|
1346
1513
|
...(hq
|
|
1347
1514
|
? {
|
|
@@ -1366,7 +1533,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1366
1533
|
};
|
|
1367
1534
|
}
|
|
1368
1535
|
|
|
1369
|
-
// Support groupStatus:true alongside richPreview:true
|
|
1370
1536
|
if (isGroupStatus) {
|
|
1371
1537
|
extendedText.contextInfo = { ...(extendedText.contextInfo || {}), isGroupStatus: true };
|
|
1372
1538
|
}
|
|
@@ -1400,27 +1566,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1400
1566
|
}
|
|
1401
1567
|
return fullMsg;
|
|
1402
1568
|
}
|
|
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
1569
|
else if ('likeThis' in content && content.likeThis === true) {
|
|
1425
1570
|
const { likeThis: _, ...rawMessage } = content;
|
|
1426
1571
|
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
@@ -1541,7 +1686,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
1541
1686
|
});
|
|
1542
1687
|
}
|
|
1543
1688
|
|
|
1544
|
-
// ── ALBUM MESSAGES ──
|
|
1545
1689
|
if ('album' in content) {
|
|
1546
1690
|
const { delayMs = 1500 } = options;
|
|
1547
1691
|
for (const albumMedia of content.album) {
|