@crysnovax/baileys 2.6.4 → 2.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/lib/Socket/messages-send.js +108 -49
- package/lib/Socket/messages-send.js[old] +134 -1
- package/lib/Utils/messages-media.js +90 -0
- package/lib/Utils/{messages-media.js[past] → messages-media.js[old]} +74 -0
- package/lib/Utils/messages.js +2 -2
- package/lib/Utils/messages.js[old] +38 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -732,6 +732,42 @@ await sock.sendMessage(jid, {
|
|
|
732
732
|
})
|
|
733
733
|
```
|
|
734
734
|
|
|
735
|
+
### Rich preview for group status
|
|
736
|
+
```
|
|
737
|
+
// Fully auto — fetches title, description, image from the URL
|
|
738
|
+
await sock.sendMessage(jid, {
|
|
739
|
+
text: 'https://facebook.com/share/v/14i...',
|
|
740
|
+
richPreview: true
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
// Group status with auto-fetched preview
|
|
744
|
+
await sock.sendMessage(jid, {
|
|
745
|
+
text: 'https://chat.whatsapp.com/CODE',
|
|
746
|
+
richPreview: true,
|
|
747
|
+
groupStatus: true
|
|
748
|
+
})
|
|
749
|
+
```
|
|
750
|
+
### SEND MESSAGES EXACTLY AS IS NO PROCESSING
|
|
751
|
+
```
|
|
752
|
+
// Relay a received message exactly as-is
|
|
753
|
+
await sock.sendMessage(jid, {
|
|
754
|
+
likeThis: true,
|
|
755
|
+
...m.message // spread the captured message directly
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
// Send a manually constructed proto without normalization
|
|
759
|
+
await sock.sendMessage(jid, {
|
|
760
|
+
likeThis: true,
|
|
761
|
+
imageMessage: { ...rawFields }
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
// Re-forward with original quality intact
|
|
765
|
+
await sock.sendMessage(jid, {
|
|
766
|
+
likeThis: true,
|
|
767
|
+
...capturedMsg.message
|
|
768
|
+
})
|
|
769
|
+
```
|
|
770
|
+
|
|
735
771
|
### WHATSAPP VERIFIED ☑️ ⚉
|
|
736
772
|
send Media messages with verified WhatsApp check ✔️
|
|
737
773
|
```
|
|
@@ -741,6 +777,7 @@ await sock.sendMessage(jid, {
|
|
|
741
777
|
verifiedMe: true
|
|
742
778
|
})
|
|
743
779
|
```
|
|
780
|
+
|
|
744
781
|
---
|
|
745
782
|
|
|
746
783
|
## Meta AI Features
|
|
@@ -3,7 +3,7 @@ import { Boom } from '@hapi/boom';
|
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import { proto } from '../../WAProto/index.js';
|
|
5
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
6
|
-
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
6
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, buildLinkPreview, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { AssociationType } from '../Types/index.js';
|
|
8
8
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
9
9
|
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
@@ -1269,6 +1269,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1269
1269
|
previewTitle,
|
|
1270
1270
|
previewDescription,
|
|
1271
1271
|
previewImage,
|
|
1272
|
+
groupStatus: isGroupStatus,
|
|
1272
1273
|
quoted,
|
|
1273
1274
|
...restContent
|
|
1274
1275
|
} = content;
|
|
@@ -1277,24 +1278,35 @@ export const makeMessagesSocket = (config) => {
|
|
|
1277
1278
|
throw new Boom('richPreview requires a `text` field containing the URL', { statusCode: 400 });
|
|
1278
1279
|
}
|
|
1279
1280
|
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
const _preview = await buildLinkPreview(
|
|
1286
|
+
previewLink,
|
|
1287
|
+
sock,
|
|
1288
|
+
{
|
|
1289
|
+
customTitle: previewTitle || '',
|
|
1290
|
+
customDesc: previewDescription || '',
|
|
1291
|
+
customImage: Buffer.isBuffer(previewImage) ? previewImage : null
|
|
1286
1292
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
let imageBuffer = _preview.imageBuffer;
|
|
1296
|
+
|
|
1297
|
+
// If previewImage was a URL string, fetch it (overrides auto-fetch)
|
|
1298
|
+
if (!imageBuffer && typeof previewImage === 'string') {
|
|
1299
|
+
try {
|
|
1300
|
+
const res = await fetch(previewImage);
|
|
1301
|
+
imageBuffer = Buffer.from(await res.arrayBuffer());
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
logger?.warn({ err }, 'richPreview: failed to fetch previewImage URL');
|
|
1295
1304
|
}
|
|
1296
1305
|
}
|
|
1297
1306
|
|
|
1307
|
+
const resolvedTitle = previewTitle || _preview.title || '';
|
|
1308
|
+
const resolvedDescription = previewDescription || _preview.description || '';
|
|
1309
|
+
|
|
1298
1310
|
// Small embedded jpegThumbnail — kept under the ~7KB ceiling
|
|
1299
1311
|
// that causes WA clients to render a blank/black preview if exceeded.
|
|
1300
1312
|
// 296px @ quality 50 (fixed internally by extractImageThumb) sits
|
|
@@ -1323,41 +1335,48 @@ export const makeMessagesSocket = (config) => {
|
|
|
1323
1335
|
}
|
|
1324
1336
|
}
|
|
1325
1337
|
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
...restContent
|
|
1347
|
-
}
|
|
1338
|
+
const extendedText = {
|
|
1339
|
+
text: previewLink,
|
|
1340
|
+
matchedText: previewLink,
|
|
1341
|
+
canonicalUrl: previewLink,
|
|
1342
|
+
title: resolvedTitle,
|
|
1343
|
+
description: resolvedDescription,
|
|
1344
|
+
previewType: 5, // IMAGE
|
|
1345
|
+
jpegThumbnail: smallThumb || undefined,
|
|
1346
|
+
...(hq
|
|
1347
|
+
? {
|
|
1348
|
+
thumbnailDirectPath: hq.directPath,
|
|
1349
|
+
mediaKey: hq.mediaKey,
|
|
1350
|
+
mediaKeyTimestamp: hq.mediaKeyTimestamp,
|
|
1351
|
+
thumbnailWidth: hq.width,
|
|
1352
|
+
thumbnailHeight: hq.height,
|
|
1353
|
+
thumbnailSha256: hq.fileSha256,
|
|
1354
|
+
thumbnailEncSha256: hq.fileEncSha256
|
|
1355
|
+
}
|
|
1356
|
+
: {}),
|
|
1357
|
+
...restContent
|
|
1348
1358
|
};
|
|
1349
1359
|
|
|
1350
|
-
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
1351
|
-
|
|
1352
1360
|
if (quoted) {
|
|
1353
|
-
|
|
1354
|
-
...(
|
|
1361
|
+
extendedText.contextInfo = {
|
|
1362
|
+
...(extendedText.contextInfo || {}),
|
|
1355
1363
|
stanzaId: quoted.key.id,
|
|
1356
1364
|
participant: jidNormalizedUser(quoted.key.fromMe ? userJid : (quoted.key.participant || quoted.key.remoteJid)),
|
|
1357
1365
|
quotedMessage: quoted.message
|
|
1358
1366
|
};
|
|
1359
1367
|
}
|
|
1360
1368
|
|
|
1369
|
+
// Support groupStatus:true alongside richPreview:true
|
|
1370
|
+
if (isGroupStatus) {
|
|
1371
|
+
extendedText.contextInfo = { ...(extendedText.contextInfo || {}), isGroupStatus: true };
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const previewMessage = isGroupStatus
|
|
1375
|
+
? { groupStatusMessageV2: { message: { extendedTextMessage: extendedText } } }
|
|
1376
|
+
: { extendedTextMessage: extendedText };
|
|
1377
|
+
|
|
1378
|
+
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
1379
|
+
|
|
1361
1380
|
await relayMessage(jid, previewMessage, {
|
|
1362
1381
|
messageId: msgId,
|
|
1363
1382
|
additionalAttributes: options.additionalAttributes || {},
|
|
@@ -1381,14 +1400,54 @@ export const makeMessagesSocket = (config) => {
|
|
|
1381
1400
|
}
|
|
1382
1401
|
return fullMsg;
|
|
1383
1402
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
+
else if ('likeThis' in content && content.likeThis === true) {
|
|
1425
|
+
const { likeThis: _, ...rawMessage } = content;
|
|
1426
|
+
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
1427
|
+
|
|
1428
|
+
await relayMessage(jid, rawMessage, {
|
|
1429
|
+
messageId: msgId,
|
|
1430
|
+
additionalAttributes: options.additionalAttributes || {},
|
|
1431
|
+
additionalNodes: options.additionalNodes || []
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
const fullMsg = {
|
|
1435
|
+
key: {
|
|
1436
|
+
remoteJid: jid,
|
|
1437
|
+
fromMe: true,
|
|
1438
|
+
id: msgId
|
|
1439
|
+
},
|
|
1440
|
+
message: rawMessage,
|
|
1441
|
+
messageTimestamp: unixTimestampSeconds()
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
if (config.emitOwnEvents) {
|
|
1445
|
+
process.nextTick(async () => {
|
|
1446
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
return fullMsg;
|
|
1450
|
+
}
|
|
1392
1451
|
...(httpRequestOptions || {})
|
|
1393
1452
|
},
|
|
1394
1453
|
logger,
|
|
@@ -3,7 +3,7 @@ import { Boom } from '@hapi/boom';
|
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import { proto } from '../../WAProto/index.js';
|
|
5
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
6
|
-
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
6
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { AssociationType } from '../Types/index.js';
|
|
8
8
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
9
9
|
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
@@ -1248,6 +1248,139 @@ export const makeMessagesSocket = (config) => {
|
|
|
1248
1248
|
}
|
|
1249
1249
|
return fullMsg;
|
|
1250
1250
|
}
|
|
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
|
+
else if ('richPreview' in content && content.richPreview === true) {
|
|
1266
|
+
const {
|
|
1267
|
+
richPreview: __rp,
|
|
1268
|
+
text: previewLink,
|
|
1269
|
+
previewTitle,
|
|
1270
|
+
previewDescription,
|
|
1271
|
+
previewImage,
|
|
1272
|
+
quoted,
|
|
1273
|
+
...restContent
|
|
1274
|
+
} = content;
|
|
1275
|
+
|
|
1276
|
+
if (!previewLink || typeof previewLink !== 'string') {
|
|
1277
|
+
throw new Boom('richPreview requires a `text` field containing the URL', { statusCode: 400 });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Resolve previewImage into a raw Buffer, whether given as
|
|
1281
|
+
// a Buffer directly or a URL string to fetch
|
|
1282
|
+
let imageBuffer = null;
|
|
1283
|
+
if (previewImage) {
|
|
1284
|
+
if (Buffer.isBuffer(previewImage)) {
|
|
1285
|
+
imageBuffer = previewImage;
|
|
1286
|
+
}
|
|
1287
|
+
else if (typeof previewImage === 'string') {
|
|
1288
|
+
try {
|
|
1289
|
+
const res = await fetch(previewImage);
|
|
1290
|
+
imageBuffer = Buffer.from(await res.arrayBuffer());
|
|
1291
|
+
}
|
|
1292
|
+
catch (err) {
|
|
1293
|
+
logger?.warn({ err }, 'richPreview: failed to fetch previewImage URL');
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Small embedded jpegThumbnail — kept under the ~7KB ceiling
|
|
1299
|
+
// that causes WA clients to render a blank/black preview if exceeded.
|
|
1300
|
+
// 296px @ quality 50 (fixed internally by extractImageThumb) sits
|
|
1301
|
+
// safely under that threshold while staying as sharp as reliably possible.
|
|
1302
|
+
let smallThumb = null;
|
|
1303
|
+
if (imageBuffer) {
|
|
1304
|
+
try {
|
|
1305
|
+
const { buffer } = await extractImageThumb(imageBuffer, 296);
|
|
1306
|
+
smallThumb = buffer;
|
|
1307
|
+
}
|
|
1308
|
+
catch (err) {
|
|
1309
|
+
logger?.warn({ err }, 'richPreview: failed to generate small thumbnail');
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Upload the image through the real media pipeline to get a
|
|
1314
|
+
// proper highQualityThumbnail reference (directPath/mediaKey/etc)
|
|
1315
|
+
let hq = null;
|
|
1316
|
+
if (imageBuffer) {
|
|
1317
|
+
try {
|
|
1318
|
+
const prepared = await prepareWAMessageMedia({ image: imageBuffer }, { upload: waUploadToServer });
|
|
1319
|
+
hq = prepared.imageMessage;
|
|
1320
|
+
}
|
|
1321
|
+
catch (err) {
|
|
1322
|
+
logger?.warn({ err }, 'richPreview: failed to upload HQ thumbnail');
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const previewMessage = {
|
|
1327
|
+
extendedTextMessage: {
|
|
1328
|
+
text: previewLink,
|
|
1329
|
+
matchedText: previewLink,
|
|
1330
|
+
canonicalUrl: previewLink,
|
|
1331
|
+
title: previewTitle || '',
|
|
1332
|
+
description: previewDescription || '',
|
|
1333
|
+
previewType: 5, // IMAGE
|
|
1334
|
+
jpegThumbnail: smallThumb || undefined,
|
|
1335
|
+
...(hq
|
|
1336
|
+
? {
|
|
1337
|
+
thumbnailDirectPath: hq.directPath,
|
|
1338
|
+
mediaKey: hq.mediaKey,
|
|
1339
|
+
mediaKeyTimestamp: hq.mediaKeyTimestamp,
|
|
1340
|
+
thumbnailWidth: hq.width,
|
|
1341
|
+
thumbnailHeight: hq.height,
|
|
1342
|
+
thumbnailSha256: hq.fileSha256,
|
|
1343
|
+
thumbnailEncSha256: hq.fileEncSha256
|
|
1344
|
+
}
|
|
1345
|
+
: {}),
|
|
1346
|
+
...restContent
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
const msgId = options.messageId || generateMessageIDV2(userJid);
|
|
1351
|
+
|
|
1352
|
+
if (quoted) {
|
|
1353
|
+
previewMessage.extendedTextMessage.contextInfo = {
|
|
1354
|
+
...(previewMessage.extendedTextMessage.contextInfo || {}),
|
|
1355
|
+
stanzaId: quoted.key.id,
|
|
1356
|
+
participant: jidNormalizedUser(quoted.key.fromMe ? userJid : (quoted.key.participant || quoted.key.remoteJid)),
|
|
1357
|
+
quotedMessage: quoted.message
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
await relayMessage(jid, previewMessage, {
|
|
1362
|
+
messageId: msgId,
|
|
1363
|
+
additionalAttributes: options.additionalAttributes || {},
|
|
1364
|
+
additionalNodes: options.additionalNodes || []
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const fullMsg = {
|
|
1368
|
+
key: {
|
|
1369
|
+
remoteJid: jid,
|
|
1370
|
+
fromMe: true,
|
|
1371
|
+
id: msgId
|
|
1372
|
+
},
|
|
1373
|
+
message: previewMessage,
|
|
1374
|
+
messageTimestamp: unixTimestampSeconds()
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
if (config.emitOwnEvents) {
|
|
1378
|
+
process.nextTick(async () => {
|
|
1379
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
return fullMsg;
|
|
1383
|
+
}
|
|
1251
1384
|
else {
|
|
1252
1385
|
const fullMsg = await generateWAMessage(jid, content, {
|
|
1253
1386
|
logger,
|
|
@@ -294,6 +294,96 @@ export const generateProfilePictureHD = async (mediaUpload) => {
|
|
|
294
294
|
console.log(`[HD-Profile] Output: ${result.length} bytes, dims preserved (max 720px)`);
|
|
295
295
|
return { img: result };
|
|
296
296
|
};
|
|
297
|
+
// crysnovax@LinkPreview --- Helper functions for building rich link previews
|
|
298
|
+
// Used internally by the richPreview flag in messages-send.js
|
|
299
|
+
// Can be imported directly for custom preview building in bot commands
|
|
300
|
+
const PREVIEW_USER_AGENTS = [
|
|
301
|
+
'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
|
|
302
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
303
|
+
'Twitterbot/1.0'
|
|
304
|
+
];
|
|
305
|
+
export const decodeHtmlEntities = (str = '') => str
|
|
306
|
+
.replace(/&/g, '&')
|
|
307
|
+
.replace(/</g, '<')
|
|
308
|
+
.replace(/>/g, '>')
|
|
309
|
+
.replace(/"/g, '"')
|
|
310
|
+
.replace(/'/g, "'")
|
|
311
|
+
.replace(/ /g, ' ')
|
|
312
|
+
.replace(/ /gi, ' ')
|
|
313
|
+
.replace(/·/gi, '·')
|
|
314
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(Number(dec)))
|
|
315
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
316
|
+
.trim();
|
|
317
|
+
export const fetchLinkMeta = async (url) => {
|
|
318
|
+
for (const ua of PREVIEW_USER_AGENTS) {
|
|
319
|
+
try {
|
|
320
|
+
const controller = new AbortController();
|
|
321
|
+
const t = setTimeout(() => controller.abort(), 5000);
|
|
322
|
+
const res = await fetch(url, { headers: { 'User-Agent': ua }, signal: controller.signal });
|
|
323
|
+
clearTimeout(t);
|
|
324
|
+
if (!res.ok) continue;
|
|
325
|
+
const html = await res.text();
|
|
326
|
+
const ogTitle = html.match(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']*)["'][^>]*>/i);
|
|
327
|
+
const titleTag = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
328
|
+
const ogDesc = html.match(/<meta[^>]*property=["']og:description["'][^>]*content=["']([^"']*)["'][^>]*>/i);
|
|
329
|
+
const metaDesc = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["'][^>]*>/i);
|
|
330
|
+
const ogImg = html.match(/<meta[^>]*property=["']og:image(?::secure_url)?["'][^>]*content=["']([^"']*)["'][^>]*>/i);
|
|
331
|
+
return {
|
|
332
|
+
title: decodeHtmlEntities(ogTitle?.[1] || titleTag?.[1] || ''),
|
|
333
|
+
description: decodeHtmlEntities(ogDesc?.[1] || metaDesc?.[1] || ''),
|
|
334
|
+
imageUrl: decodeHtmlEntities(ogImg?.[1] || '')
|
|
335
|
+
};
|
|
336
|
+
} catch {}
|
|
337
|
+
}
|
|
338
|
+
return { title: '', description: '', imageUrl: '' };
|
|
339
|
+
};
|
|
340
|
+
export const resolvePreviewImage = async (imageUrl) => {
|
|
341
|
+
if (!imageUrl) return null;
|
|
342
|
+
for (const ua of PREVIEW_USER_AGENTS) {
|
|
343
|
+
try {
|
|
344
|
+
const res = await fetch(imageUrl, { headers: { 'User-Agent': ua } });
|
|
345
|
+
if (res.ok) return Buffer.from(await res.arrayBuffer());
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
};
|
|
350
|
+
export const resolvePreviewMeta = async (url, sock, customTitle, customDesc) => {
|
|
351
|
+
if (customTitle && customDesc) return { title: customTitle, description: customDesc };
|
|
352
|
+
if (url.includes('chat.whatsapp.com')) {
|
|
353
|
+
try {
|
|
354
|
+
const code = url.split('chat.whatsapp.com/')[1]?.split('?')[0];
|
|
355
|
+
const info = await sock?.groupGetInviteInfo(code);
|
|
356
|
+
return {
|
|
357
|
+
title: customTitle || info?.subject || '',
|
|
358
|
+
description: customDesc || `${info?.size || 0} members · WhatsApp Group`
|
|
359
|
+
};
|
|
360
|
+
} catch {}
|
|
361
|
+
return { title: customTitle || '', description: customDesc || 'WhatsApp Group Invite' };
|
|
362
|
+
}
|
|
363
|
+
const meta = await fetchLinkMeta(url);
|
|
364
|
+
return { title: customTitle || meta.title, description: customDesc || meta.description };
|
|
365
|
+
};
|
|
366
|
+
export const buildLinkPreview = async (url, sock, options = {}) => {
|
|
367
|
+
const { customTitle = '', customDesc = '', customImage } = options;
|
|
368
|
+
const { title, description } = await resolvePreviewMeta(url, sock, customTitle, customDesc);
|
|
369
|
+
let imageBuffer = Buffer.isBuffer(customImage) ? customImage : null;
|
|
370
|
+
if (!imageBuffer) {
|
|
371
|
+
if (url.includes('chat.whatsapp.com')) {
|
|
372
|
+
try {
|
|
373
|
+
const code = url.split('chat.whatsapp.com/')[1]?.split('?')[0];
|
|
374
|
+
const info = await sock?.groupGetInviteInfo(code);
|
|
375
|
+
if (info?.id) {
|
|
376
|
+
const ppUrl = await sock?.profilePictureUrl(info.id, 'image');
|
|
377
|
+
imageBuffer = await resolvePreviewImage(ppUrl);
|
|
378
|
+
}
|
|
379
|
+
} catch {}
|
|
380
|
+
} else {
|
|
381
|
+
const meta = await fetchLinkMeta(url);
|
|
382
|
+
if (meta.imageUrl) imageBuffer = await resolvePreviewImage(meta.imageUrl);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return { url, title, description, imageBuffer };
|
|
386
|
+
};
|
|
297
387
|
/** gets the SHA256 of the given media message */
|
|
298
388
|
export const mediaMessageSHA256B64 = (message) => {
|
|
299
389
|
const media = Object.values(message)[0];
|
|
@@ -220,6 +220,80 @@ export const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
220
220
|
img: await img
|
|
221
221
|
};
|
|
222
222
|
};
|
|
223
|
+
// crysnovax@HD-Profile --- Like generateProfilePicture but skips resize/crop.
|
|
224
|
+
// Re-encodes to JPEG so WA always gets valid image bytes.
|
|
225
|
+
// crysnovax@HD-Profile --- HD mode: preserves original aspect ratio, no crop.
|
|
226
|
+
// WhatsApp server accepts max 720px on any side. Images under 720px pass through
|
|
227
|
+
// unchanged. Larger images are scaled down proportionally to fit within 720px.
|
|
228
|
+
export const generateProfilePictureHD = async (mediaUpload) => {
|
|
229
|
+
let buffer;
|
|
230
|
+
if (Buffer.isBuffer(mediaUpload)) {
|
|
231
|
+
buffer = mediaUpload;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const { stream } = await getStream(mediaUpload);
|
|
235
|
+
buffer = await toBuffer(stream);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const lib = await getImageProcessingLibrary();
|
|
239
|
+
let img;
|
|
240
|
+
|
|
241
|
+
if ('sharp' in lib && lib.sharp?.default) {
|
|
242
|
+
const metadata = await lib.sharp.default(buffer).metadata();
|
|
243
|
+
const origWidth = metadata.width || 720;
|
|
244
|
+
const origHeight = metadata.height || 720;
|
|
245
|
+
const maxDim = Math.max(origWidth, origHeight);
|
|
246
|
+
|
|
247
|
+
// WhatsApp server limit: 720px max dimension
|
|
248
|
+
// If already under 720, send as-is. If larger, scale down proportionally.
|
|
249
|
+
const needsResize = maxDim > 720;
|
|
250
|
+
|
|
251
|
+
let pipeline = lib.sharp.default(buffer);
|
|
252
|
+
|
|
253
|
+
if (needsResize) {
|
|
254
|
+
pipeline = pipeline.resize(720, 720, {
|
|
255
|
+
fit: 'inside',
|
|
256
|
+
withoutEnlargement: true
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
img = pipeline
|
|
261
|
+
.jpeg({ quality: 85, mozjpeg: true })
|
|
262
|
+
.toBuffer();
|
|
263
|
+
}
|
|
264
|
+
else if ('image' in lib && lib.image?.Transformer) {
|
|
265
|
+
// @napi-rs/image — cap at 720 via resize
|
|
266
|
+
img = new lib.image
|
|
267
|
+
.Transformer(buffer)
|
|
268
|
+
.resize(720, 720)
|
|
269
|
+
.jpeg(85);
|
|
270
|
+
}
|
|
271
|
+
else if ('jimp' in lib && lib.jimp?.Jimp) {
|
|
272
|
+
const jimp = await lib.jimp.Jimp.read(buffer);
|
|
273
|
+
const origWidth = jimp.width;
|
|
274
|
+
const origHeight = jimp.height;
|
|
275
|
+
const maxDim = Math.max(origWidth, origHeight);
|
|
276
|
+
|
|
277
|
+
let processed = jimp;
|
|
278
|
+
if (maxDim > 720) {
|
|
279
|
+
const scale = 720 / maxDim;
|
|
280
|
+
processed = jimp.resize({
|
|
281
|
+
w: Math.round(origWidth * scale),
|
|
282
|
+
h: Math.round(origHeight * scale),
|
|
283
|
+
mode: lib.jimp.ResizeStrategy.BILINEAR
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
img = processed.getBuffer('image/jpeg', { quality: 85 });
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
throw new Boom('No image processing library available');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = await img;
|
|
294
|
+
console.log(`[HD-Profile] Output: ${result.length} bytes, dims preserved (max 720px)`);
|
|
295
|
+
return { img: result };
|
|
296
|
+
};
|
|
223
297
|
/** gets the SHA256 of the given media message */
|
|
224
298
|
export const mediaMessageSHA256B64 = (message) => {
|
|
225
299
|
const media = Object.values(message)[0];
|
package/lib/Utils/messages.js
CHANGED
|
@@ -45,7 +45,7 @@ const mediaAnnotation = [
|
|
|
45
45
|
'crysnovax',
|
|
46
46
|
contentType: proto.ContextInfo.ForwardedNewsletterMessageInfo.ContentType.UPDATE,
|
|
47
47
|
accessibilityText: process.env.NEWSLETTER_ACCESSIBILITY_TEXT ||
|
|
48
|
-
'@crysnovax/
|
|
48
|
+
'@crysnovax/baileys'
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
];
|
|
@@ -1394,7 +1394,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1394
1394
|
id: '3EB0' + Math.random().toString(16).substring(2, 10).toUpperCase()
|
|
1395
1395
|
},
|
|
1396
1396
|
message: {
|
|
1397
|
-
conversation: '
|
|
1397
|
+
conversation: '```𖠃么 AI ⚉```'
|
|
1398
1398
|
}
|
|
1399
1399
|
};
|
|
1400
1400
|
}
|
|
@@ -1363,6 +1363,44 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1363
1363
|
m = { spoilerMessage: { message: m } };
|
|
1364
1364
|
delete message.spoiler;
|
|
1365
1365
|
}
|
|
1366
|
+
// crysnovax@VerifiedMe --- Add "verifiedMe" boolean to attach a verified
|
|
1367
|
+
// forward badge (✓) on image/video messages only. Uses forwardingScore +
|
|
1368
|
+
// isForwarded + the 0@s.whatsapp.net participant/remoteJid trick (no
|
|
1369
|
+
// newsletter context needed) — confirmed working pattern. Also injects a
|
|
1370
|
+
// synthetic quoted message fallback if none is already present, matching
|
|
1371
|
+
// the same pattern.
|
|
1372
|
+
if (hasOptionalProperty(message, 'verifiedMe') && !!message.verifiedMe) {
|
|
1373
|
+
const messageType = Object.keys(m)[0];
|
|
1374
|
+
const isMediaMessage = messageType === 'imageMessage' || messageType === 'videoMessage';
|
|
1375
|
+
if (isMediaMessage) {
|
|
1376
|
+
const key = m[messageType];
|
|
1377
|
+
const verifiedContext = {
|
|
1378
|
+
isForwarded: true,
|
|
1379
|
+
participant: '0@s.whatsapp.net',
|
|
1380
|
+
remoteJid: '0@s.whatsapp.net'
|
|
1381
|
+
};
|
|
1382
|
+
if ('contextInfo' in key && !!key.contextInfo) {
|
|
1383
|
+
key.contextInfo = { ...key.contextInfo, ...verifiedContext };
|
|
1384
|
+
}
|
|
1385
|
+
else if (key) {
|
|
1386
|
+
key.contextInfo = verifiedContext;
|
|
1387
|
+
}
|
|
1388
|
+
if (options && !options.quoted) {
|
|
1389
|
+
options.quoted = {
|
|
1390
|
+
key: {
|
|
1391
|
+
remoteJid: '0@s.whatsapp.net',
|
|
1392
|
+
fromMe: false,
|
|
1393
|
+
participant: '0@s.whatsapp.net',
|
|
1394
|
+
id: '3EB0' + Math.random().toString(16).substring(2, 10).toUpperCase()
|
|
1395
|
+
},
|
|
1396
|
+
message: {
|
|
1397
|
+
conversation: '```ஃ𖠃 CRYSN⚉VA AI🜲```'
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
delete message.verifiedMe;
|
|
1403
|
+
}
|
|
1366
1404
|
// Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
|
|
1367
1405
|
else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
|
|
1368
1406
|
if (!m.interactiveMessage) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crysnovax/baileys",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.5",
|
|
4
4
|
"description": "Premium Baileys fork ⚉ Meta compositing, bot planning replay, welcome flow, rich messages (code, table, LaTeX, reels), interactive messages, albums, and more.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|