plogme 2.0.7 → 2.0.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 +583 -36
- package/WAProto/WAProto.proto +934 -3
- package/WAProto/index.d.ts +33887 -21445
- package/WAProto/index.js +114447 -78804
- package/lib/Defaults/index.js +1 -1
- package/lib/Socket/messages-send.js +135 -113
- package/lib/Types/Message.js +2 -1
- package/lib/Utils/bot-planning-replay.js +6 -9
- package/lib/Utils/chat-utils.js +8 -0
- package/lib/Utils/history.js +21 -1
- package/lib/Utils/messages.js +84 -11
- package/lib/Utils/meta-ai-msmsg.js +75 -2
- package/lib/Utils/meta-compositing.js +38 -9
- package/lib/Utils/rich-message-utils.js +123 -13
- package/lib/Utils/validate-connection.js +4 -2
- package/lib/index.d.ts +45 -0
- package/package.json +1 -1
package/lib/Defaults/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
2
2
|
import { makeLibSignalRepository } from '../Signal/libsignal.js';
|
|
3
3
|
import { Browsers } from '../Utils/browser-utils.js';
|
|
4
4
|
import logger from '../Utils/logger.js';
|
|
5
|
-
const version = [2, 3000,
|
|
5
|
+
const version = [2, 3000, 1049983640];
|
|
6
6
|
export const UNAUTHORIZED_CODES = [401, 403, 419];
|
|
7
7
|
export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
|
|
8
8
|
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache';
|
|
2
2
|
import { Boom } from '@hapi/boom';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
3
|
+
import { randomBytes, randomUUID } 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
6
|
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, buildLinkPreview, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, makeWhatsAppFlowButton, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
8
|
-
import { prepareRichMenuMessage, prepareA2UIMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareSocialEntityMessage, prepareInstagramProfileMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareSocialProfileMessage, prepareUnifiedResponseMessage, prepareRichMapMessage, prepareBotPromptSuggestionsMessage, prepareBotSourcesMessage, prepareRichDynamicMediaMessage, prepareBotPttTranscriptMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
|
|
8
|
+
import { prepareRichMenuMessage, prepareA2UIMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareSocialEntityMessage, prepareInstagramProfileMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareSocialProfileMessage, prepareUnifiedResponseMessage, prepareRichMapMessage, prepareBotPromptSuggestionsMessage, prepareBotSourcesMessage, prepareRichDynamicMediaMessage, prepareBotPttTranscriptMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, buildRichLabelMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
|
|
9
9
|
import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
|
|
10
10
|
import { generateMiniAppHtml } from '../Utils/games/miniapp.js';
|
|
11
11
|
import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/games/website-preview.js';
|
|
@@ -37,6 +37,7 @@ export const normalizeStatusJidList = (value) => {
|
|
|
37
37
|
export const makeMessagesSocket = (config) => {
|
|
38
38
|
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
39
39
|
const sock = makeUsernameSocket(config);
|
|
40
|
+
let socket;
|
|
40
41
|
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
41
42
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
42
43
|
|
|
@@ -56,57 +57,45 @@ export const makeMessagesSocket = (config) => {
|
|
|
56
57
|
|
|
57
58
|
// ── BULK REACTION HELPER (NEW) ──
|
|
58
59
|
const sendBulkReactions = async (jid, messageId, emoji, count = 1, fake = false) => {
|
|
59
|
-
const maxReactions = Math.min(count, 1000);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (let i = 0; i < maxReactions; i++) {
|
|
64
|
-
const fakeSender = `${Math.floor(Math.random() * 999999999)}${i}@s.whatsapp.net`;
|
|
65
|
-
reactions.push({
|
|
66
|
-
key: {
|
|
67
|
-
remoteJid: jid,
|
|
68
|
-
fromMe: false,
|
|
69
|
-
id: messageId,
|
|
70
|
-
participant: fakeSender
|
|
71
|
-
},
|
|
72
|
-
reaction: {
|
|
73
|
-
key: {
|
|
74
|
-
remoteJid: jid,
|
|
75
|
-
fromMe: false,
|
|
76
|
-
id: messageId
|
|
77
|
-
},
|
|
78
|
-
text: emoji,
|
|
79
|
-
senderTimestampMs: Date.now() + i
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
for (let i = 0; i < maxReactions; i++) {
|
|
85
|
-
reactions.push({
|
|
86
|
-
react: {
|
|
87
|
-
text: emoji,
|
|
88
|
-
key: {
|
|
89
|
-
remoteJid: jid,
|
|
90
|
-
fromMe: false,
|
|
91
|
-
id: messageId
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
60
|
+
const maxReactions = Math.min(Math.max(Number(count) || 1, 1), 1000);
|
|
61
|
+
if (isJidNewsletter(jid)) {
|
|
62
|
+
if (typeof socket.newsletterReactMessage !== 'function') {
|
|
63
|
+
throw new Boom('newsletter reaction API is unavailable', { statusCode: 501 });
|
|
95
64
|
}
|
|
65
|
+
await socket.newsletterReactMessage(jid, messageId, String(emoji || ''));
|
|
66
|
+
return { sent: 1, requested: maxReactions, fake: false, channel: true, jid };
|
|
96
67
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log(`[BULK_REACT] Batch failed: ${err.message}`);
|
|
68
|
+
const makeReaction = () => ({
|
|
69
|
+
react: {
|
|
70
|
+
text: String(emoji || ''),
|
|
71
|
+
key: {
|
|
72
|
+
remoteJid: jid,
|
|
73
|
+
fromMe: false,
|
|
74
|
+
id: messageId
|
|
75
|
+
}
|
|
106
76
|
}
|
|
77
|
+
});
|
|
78
|
+
let sent = 0;
|
|
79
|
+
const batchSize = 10;
|
|
80
|
+
|
|
81
|
+
// sendMessage accepts one content object, not an array of messages.
|
|
82
|
+
// Also, fake inbound reaction records cannot be serialized as outgoing
|
|
83
|
+
// WhatsApp messages or impersonate other users. Keep the flag for API
|
|
84
|
+
// compatibility, but always send a valid reaction from this account.
|
|
85
|
+
if (fake) {
|
|
86
|
+
logger.warn('[BULK_REACT] fake=true cannot impersonate other WhatsApp users; sending valid reactions from this account');
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < maxReactions; i += batchSize) {
|
|
89
|
+
const batch = Array.from({ length: Math.min(batchSize, maxReactions - i) }, () =>
|
|
90
|
+
socket.sendMessage(jid, makeReaction(), { ephemeralExpiration: 86400 })
|
|
91
|
+
.then(() => { sent += 1; })
|
|
92
|
+
.catch(error => logger.warn(`[BULK_REACT] Reaction failed: ${error.message}`))
|
|
93
|
+
);
|
|
94
|
+
await Promise.all(batch);
|
|
95
|
+
if (i + batchSize < maxReactions) await delay(100);
|
|
107
96
|
}
|
|
108
97
|
|
|
109
|
-
return { sent: maxReactions, fake, jid };
|
|
98
|
+
return { sent, requested: maxReactions, fake: false, jid };
|
|
110
99
|
};
|
|
111
100
|
|
|
112
101
|
// ── MIMIC HELPER ──
|
|
@@ -244,6 +233,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
244
233
|
const decoded = jidDecode(jid);
|
|
245
234
|
const user = decoded?.user;
|
|
246
235
|
const device = decoded?.device;
|
|
236
|
+
// Meta AI/bot JIDs are single device-0 endpoints. USync does not
|
|
237
|
+
// enumerate them, so inject the endpoint instead of dropping it.
|
|
238
|
+
if (user && (isJidMetaAI(jid) || isJidBot(jid))) {
|
|
239
|
+
deviceResults.push({
|
|
240
|
+
user,
|
|
241
|
+
device: typeof device === 'number' ? device : 0,
|
|
242
|
+
jid
|
|
243
|
+
});
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
247
246
|
const isExplicitDevice = typeof device === 'number' && device >= 0;
|
|
248
247
|
if (isExplicitDevice && user) {
|
|
249
248
|
deviceResults.push({
|
|
@@ -407,7 +406,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
407
406
|
if (jidsRequiringFetch.length) {
|
|
408
407
|
const wireJids = [
|
|
409
408
|
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
410
|
-
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
409
|
+
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid),
|
|
410
|
+
// @bot endpoints are not represented by LID/PN USync results,
|
|
411
|
+
// but still require a pre-key fetch to build the Signal session.
|
|
412
|
+
...jidsRequiringFetch.filter(jid => isJidMetaAI(jid) || isJidBot(jid))
|
|
411
413
|
];
|
|
412
414
|
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
|
|
413
415
|
const result = await query({
|
|
@@ -688,6 +690,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
688
690
|
if (isStatus && statusJidList) {
|
|
689
691
|
participantsList.push(...statusJidList);
|
|
690
692
|
}
|
|
693
|
+
// Bots invoked through botGroupMetadata are not group members,
|
|
694
|
+
// so include their @bot endpoints in sender-key distribution.
|
|
695
|
+
const botGroupParticipants = message?.messageContextInfo?.botMetadata?.botGroupMetadata?.participantsMetadata || [];
|
|
696
|
+
for (const participant of botGroupParticipants) {
|
|
697
|
+
if (!participant?.botFbid)
|
|
698
|
+
continue;
|
|
699
|
+
const botJid = `${participant.botFbid}@bot`;
|
|
700
|
+
if (!participantsList.includes(botJid))
|
|
701
|
+
participantsList.push(botJid);
|
|
702
|
+
}
|
|
691
703
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
|
|
692
704
|
devices.push(...additionalDevices);
|
|
693
705
|
if (isGroup) {
|
|
@@ -713,7 +725,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
713
725
|
for (const device of devices) {
|
|
714
726
|
const deviceJid = device.jid;
|
|
715
727
|
const hasKey = !!senderKeyMap[deviceJid];
|
|
716
|
-
|
|
728
|
+
const isBotRecipient = isJidMetaAI(deviceJid) || isJidBot(deviceJid);
|
|
729
|
+
if ((!hasKey || !!participant || isBotRecipient) &&
|
|
717
730
|
!isHostedLidUser(deviceJid) &&
|
|
718
731
|
!isHostedPnUser(deviceJid) &&
|
|
719
732
|
device.device !== 99) {
|
|
@@ -1232,7 +1245,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1232
1245
|
}
|
|
1233
1246
|
});
|
|
1234
1247
|
|
|
1235
|
-
|
|
1248
|
+
socket = {
|
|
1236
1249
|
...sock,
|
|
1237
1250
|
userDevicesCache,
|
|
1238
1251
|
devicesMutex,
|
|
@@ -1312,7 +1325,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1312
1325
|
},
|
|
1313
1326
|
sendCarousel: async (jid, cards, options = {}) => {
|
|
1314
1327
|
if (!Array.isArray(cards) || cards.length === 0) {
|
|
1315
|
-
throw new Boom('sendCarousel requires a non-empty
|
|
1328
|
+
throw new Boom('sendCarousel requires a non-empty card array', { statusCode: 400 });
|
|
1316
1329
|
}
|
|
1317
1330
|
const normalizedCards = cards.map(card => ({
|
|
1318
1331
|
...card,
|
|
@@ -1328,6 +1341,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1328
1341
|
const fullMsg = await generateWAMessage(jid, {
|
|
1329
1342
|
...messageOptions,
|
|
1330
1343
|
cards: normalizedCards,
|
|
1344
|
+
...(options.carouselCardType != null ? { carouselCardType: options.carouselCardType } : {}),
|
|
1331
1345
|
...(text !== undefined ? { text } : {}),
|
|
1332
1346
|
...(footer !== undefined ? { footer } : {})
|
|
1333
1347
|
}, {
|
|
@@ -1390,76 +1404,71 @@ export const makeMessagesSocket = (config) => {
|
|
|
1390
1404
|
return fullMsg;
|
|
1391
1405
|
},
|
|
1392
1406
|
sendMetaAI: async (a, b, c = {}) => {
|
|
1393
|
-
//
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
else {
|
|
1400
|
-
text = a;
|
|
1401
|
-
opts = b || {};
|
|
1402
|
-
}
|
|
1403
|
-
const yourJid = typeof b === 'string' ? a : (opts.yourJid || '');
|
|
1407
|
+
// Supports both (text, opts) and the historical (jid, text, opts)
|
|
1408
|
+
// call shape. The payload below mirrors the verified WA-Web Hatch send.
|
|
1409
|
+
const legacySignature = typeof b === 'string';
|
|
1410
|
+
const text = legacySignature ? b : a;
|
|
1411
|
+
const opts = legacySignature ? c : (b || {});
|
|
1404
1412
|
const META_AI_BOT_JID = '867051314767696@bot';
|
|
1413
|
+
const META_AI_FBID = '867051314767696';
|
|
1405
1414
|
const jid = opts.jid || META_AI_BOT_JID;
|
|
1406
|
-
const
|
|
1407
|
-
const
|
|
1408
|
-
const senderTimestamp = opts.senderTimestamp || String(Math.floor(now / 1000));
|
|
1415
|
+
const isGroup = jid.endsWith('@g.us');
|
|
1416
|
+
const invokerJid = opts.invokerJid || authState.creds.me?.id;
|
|
1409
1417
|
const messageSecret = opts.messageSecret || randomBytes(32);
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
forwardScore: 0
|
|
1419
|
-
}
|
|
1420
|
-
}),
|
|
1421
|
-
inviteLinkGroupTypeV2: 'DEFAULT'
|
|
1422
|
-
}),
|
|
1423
|
-
messageContextInfo: proto.MessageContextInfo.fromObject({
|
|
1424
|
-
deviceListMetadata: {
|
|
1425
|
-
senderKeyHash,
|
|
1426
|
-
senderTimestamp
|
|
1418
|
+
const rendering = {
|
|
1419
|
+
bloksVersioningId: 'd7c89519fd412464e4a4ddd60b2e5fa57c5000766a697d60835246ef0952daba',
|
|
1420
|
+
pixelDensity: 2.625
|
|
1421
|
+
};
|
|
1422
|
+
const botMetadata = isGroup
|
|
1423
|
+
? {
|
|
1424
|
+
botGroupMetadata: {
|
|
1425
|
+
participantsMetadata: [{ botFbid: opts.botFbid || META_AI_FBID }]
|
|
1427
1426
|
},
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
clientInfo: { type: 'DEFAULT' }
|
|
1437
|
-
},
|
|
1438
|
-
botRenderingConfigMetadata: {
|
|
1439
|
-
bloksVersioningId: '1eb86e6f4117d052e6bab62fe758a2e2af43747b85c5c1a886c8262bac462ea4',
|
|
1440
|
-
pixelDensity: 2.625
|
|
1441
|
-
},
|
|
1442
|
-
...(opts.conversationContext?.length ? { aiConversationContext: opts.conversationContext } : {})
|
|
1427
|
+
botRenderingConfigMetadata: rendering
|
|
1428
|
+
}
|
|
1429
|
+
: {
|
|
1430
|
+
personaId: jid,
|
|
1431
|
+
invokerJid,
|
|
1432
|
+
sessionMetadata: {
|
|
1433
|
+
sessionId: opts.sessionId || randomUUID(),
|
|
1434
|
+
sessionSource: 3
|
|
1443
1435
|
},
|
|
1444
|
-
|
|
1445
|
-
{
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1436
|
+
capabilityMetadata: {
|
|
1437
|
+
capabilities: opts.capabilities || Array.from({ length: 72 }, (_, i) => i + 1)
|
|
1438
|
+
},
|
|
1439
|
+
botMetricsMetadata: {
|
|
1440
|
+
destinationId: jid,
|
|
1441
|
+
destinationEntryPoint: 2
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
const content = isGroup
|
|
1445
|
+
? (opts.mention
|
|
1446
|
+
? { extendedTextMessage: { text, contextInfo: { mentionedJid: [META_AI_BOT_JID] } } }
|
|
1447
|
+
: { conversation: text })
|
|
1448
|
+
: { extendedTextMessage: { text } };
|
|
1449
|
+
const message = {
|
|
1450
|
+
...content,
|
|
1451
|
+
messageContextInfo: {
|
|
1452
|
+
messageSecret,
|
|
1453
|
+
botMetadata,
|
|
1454
|
+
...(opts.conversationContext?.length ? { aiConversationContext: opts.conversationContext } : {})
|
|
1455
|
+
}
|
|
1455
1456
|
};
|
|
1456
|
-
const msgId = generateMessageIDV2(
|
|
1457
|
-
const
|
|
1457
|
+
const msgId = generateMessageIDV2(invokerJid || '');
|
|
1458
|
+
const relayOptions = {
|
|
1458
1459
|
messageId: msgId,
|
|
1459
1460
|
...(opts.quoted ? { quoted: opts.quoted } : {}),
|
|
1460
|
-
...(opts.links ? { links: opts.links } : {})
|
|
1461
|
+
...(opts.links ? { links: opts.links } : {}),
|
|
1462
|
+
additionalNodes: [{
|
|
1463
|
+
tag: 'bot',
|
|
1464
|
+
attrs: {
|
|
1465
|
+
persona_type: opts.personaType || 'default',
|
|
1466
|
+
agent_engagement_type: opts.agentEngagementType || (isGroup ? 'group_chat' : 'direct_chat'),
|
|
1467
|
+
mode_selected: opts.modeSelected || '0'
|
|
1468
|
+
}
|
|
1469
|
+
}]
|
|
1461
1470
|
};
|
|
1462
|
-
await relayMessage(jid, message,
|
|
1471
|
+
await relayMessage(jid, message, relayOptions);
|
|
1463
1472
|
return msgId;
|
|
1464
1473
|
},
|
|
1465
1474
|
// Additive Nexus-compatible AI-rich helpers. Existing Gen4/table/grid/carousel APIs remain unchanged.
|
|
@@ -1599,6 +1608,19 @@ export const makeMessagesSocket = (config) => {
|
|
|
1599
1608
|
}
|
|
1600
1609
|
return fullMsg;
|
|
1601
1610
|
},
|
|
1611
|
+
/**
|
|
1612
|
+
* Send a compact forwarded AIRich label bubble. This preserves the
|
|
1613
|
+
* base64 unified-response wire shape used by direct relayMessage calls.
|
|
1614
|
+
*/
|
|
1615
|
+
sendRichLabelMessage: async (jid, content = {}, options = {}) => {
|
|
1616
|
+
if (!jid) throw new Boom('sendRichLabelMessage requires a target JID', { statusCode: 400 });
|
|
1617
|
+
if (!content || typeof content !== 'object' || Array.isArray(content)) {
|
|
1618
|
+
throw new Boom('sendRichLabelMessage content must be an object', { statusCode: 400 });
|
|
1619
|
+
}
|
|
1620
|
+
const message = buildRichLabelMessage(content);
|
|
1621
|
+
await relayMessage(jid, message, options);
|
|
1622
|
+
return message;
|
|
1623
|
+
},
|
|
1602
1624
|
captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted, options = {}) => {
|
|
1603
1625
|
const captured = captureUnifiedResponse(metaAiMsg);
|
|
1604
1626
|
if (!captured) {
|
package/lib/Types/Message.js
CHANGED
|
@@ -8,10 +8,11 @@ export const ButtonType = proto.Message.ButtonsMessage.Button.Type;
|
|
|
8
8
|
export const CarouselCardType = proto.Message.InteractiveMessage.CarouselMessage.CarouselCardType;
|
|
9
9
|
export const ListType = proto.Message.ListMessage.ListType;
|
|
10
10
|
export const ProtocolType = proto.Message.ProtocolMessage.Type;
|
|
11
|
+
export const EventInviteStatus = proto.Message.EventInviteMessage.EventStatus;
|
|
11
12
|
export const WAMessageStubType = proto.WebMessageInfo.StubType;
|
|
12
13
|
export const WAMessageStatus = proto.WebMessageInfo.Status;
|
|
13
14
|
export var WAMessageAddressingMode;
|
|
14
15
|
(function (WAMessageAddressingMode) {
|
|
15
16
|
WAMessageAddressingMode["PN"] = "pn";
|
|
16
17
|
WAMessageAddressingMode["LID"] = "lid";
|
|
17
|
-
})(WAMessageAddressingMode || (WAMessageAddressingMode = {}));
|
|
18
|
+
})(WAMessageAddressingMode || (WAMessageAddressingMode = {}));
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* → edit: step[0] → DONE
|
|
12
12
|
* → edit: step[1] → DONE
|
|
13
13
|
* → ...
|
|
14
|
-
* →
|
|
15
|
-
* → send final message fresh
|
|
14
|
+
* → MESSAGE_EDIT: final answer on the same message key
|
|
16
15
|
*
|
|
17
16
|
* If you use or copy this code, please credit @crysnovax/bailey.
|
|
18
17
|
*/
|
|
@@ -119,19 +118,17 @@ export const replayPlanning = async (sock, jid, steps, finalContent, {
|
|
|
119
118
|
await delay(finalPauseMs);
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
if (key && !aborted) {
|
|
123
|
-
await sock.sendMessage(jid, { delete: key });
|
|
124
|
-
}
|
|
125
121
|
} catch (err) {
|
|
126
|
-
|
|
122
|
+
// Keep the latest successfully rendered frame if an intermediate edit fails.
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
await sock.sendPresenceUpdate('paused', jid);
|
|
130
126
|
|
|
131
|
-
// Skip final
|
|
132
|
-
if (sendOptions._skipFinalSend) return placeholder;
|
|
127
|
+
// Skip the final edit if _skipFinalSend is set (for replayPlanningOnly).
|
|
128
|
+
if (sendOptions._skipFinalSend || !key || aborted) return placeholder;
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
const { _skipFinalSend: _ignored, ...finalSendOptions } = sendOptions;
|
|
131
|
+
return sock.sendMessage(jid, { ...finalContent, edit: key }, finalSendOptions);
|
|
135
132
|
};
|
|
136
133
|
|
|
137
134
|
/**
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -832,6 +832,14 @@ export const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) =
|
|
|
832
832
|
else if (action?.lockChatAction) {
|
|
833
833
|
ev.emit('chats.lock', { id: id, locked: !!action.lockChatAction.locked });
|
|
834
834
|
}
|
|
835
|
+
else if (action?.communityNestingStateAction) {
|
|
836
|
+
const nestingState = action.communityNestingStateAction.nestingState;
|
|
837
|
+
ev.emit('groups.update', [{
|
|
838
|
+
id,
|
|
839
|
+
nestingState,
|
|
840
|
+
nested: nestingState === proto.SyncActionValue.CommunityNestingStateAction.NestingState.NESTING_STATE_NESTED
|
|
841
|
+
}]);
|
|
842
|
+
}
|
|
835
843
|
else if (action?.privacySettingDisableLinkPreviewsAction) {
|
|
836
844
|
ev.emit('settings.update', {
|
|
837
845
|
setting: 'disableLinkPreviews',
|
package/lib/Utils/history.js
CHANGED
|
@@ -39,6 +39,8 @@ export const processHistoryMessage = (item, logger) => {
|
|
|
39
39
|
const contacts = [];
|
|
40
40
|
const chats = [];
|
|
41
41
|
const lidPnMappings = [];
|
|
42
|
+
const favoriteStickers = [];
|
|
43
|
+
const omittedConversations = [];
|
|
42
44
|
logger?.trace({ progress: item.progress }, 'processing history of type ' + item.syncType?.toString());
|
|
43
45
|
// Extract LID-PN mappings for all sync types
|
|
44
46
|
for (const m of item.phoneNumberToLidMappings || []) {
|
|
@@ -46,6 +48,21 @@ export const processHistoryMessage = (item, logger) => {
|
|
|
46
48
|
lidPnMappings.push({ lid: m.lidJid, pn: m.pnJid });
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
const favoriteStickerKeys = new Set();
|
|
52
|
+
for (const sticker of item.favoriteStickers || []) {
|
|
53
|
+
const key = sticker.fileSha256 ? Buffer.from(sticker.fileSha256).toString('base64') : undefined;
|
|
54
|
+
if (!key || !favoriteStickerKeys.has(key)) {
|
|
55
|
+
if (key)
|
|
56
|
+
favoriteStickerKeys.add(key);
|
|
57
|
+
favoriteStickers.push(sticker);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const omitted of item.omittedConversations || []) {
|
|
61
|
+
omittedConversations.push({
|
|
62
|
+
id: omitted.historyChatId,
|
|
63
|
+
accountLid: omitted.accountLid || undefined
|
|
64
|
+
});
|
|
65
|
+
}
|
|
49
66
|
switch (item.syncType) {
|
|
50
67
|
case proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP:
|
|
51
68
|
case proto.HistorySync.HistorySyncType.RECENT:
|
|
@@ -109,6 +126,9 @@ export const processHistoryMessage = (item, logger) => {
|
|
|
109
126
|
contacts,
|
|
110
127
|
messages,
|
|
111
128
|
lidPnMappings,
|
|
129
|
+
favoriteStickers,
|
|
130
|
+
omittedConversations,
|
|
131
|
+
omittedConversationsProvided: item.omittedConversationsProvided ?? false,
|
|
112
132
|
syncType: item.syncType,
|
|
113
133
|
progress: item.progress
|
|
114
134
|
};
|
|
@@ -127,4 +147,4 @@ export const getHistoryMsg = (message) => {
|
|
|
127
147
|
const normalizedContent = !!message ? normalizeMessageContent(message) : undefined;
|
|
128
148
|
const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification;
|
|
129
149
|
return anyHistoryMsg;
|
|
130
|
-
};
|
|
150
|
+
};
|
package/lib/Utils/messages.js
CHANGED
|
@@ -835,6 +835,33 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
835
835
|
else if (hasNonNullishProperty(message, 'product')) {
|
|
836
836
|
m.productMessage = await prepareProductMessage(message, options);
|
|
837
837
|
}
|
|
838
|
+
else if (hasNonNullishProperty(message, 'eventInvite') || hasNonNullishProperty(message, 'eventInviteMessage')) {
|
|
839
|
+
const invite = message.eventInvite || message.eventInviteMessage;
|
|
840
|
+
if (!invite.eventId && !invite.id) {
|
|
841
|
+
throw new Boom('eventInvite requires an eventId', { statusCode: 400 });
|
|
842
|
+
}
|
|
843
|
+
const startTime = invite.startDate instanceof Date
|
|
844
|
+
? Math.floor(invite.startDate.getTime() / 1000)
|
|
845
|
+
: invite.startTime != null ? Number(invite.startTime) : undefined;
|
|
846
|
+
const endTime = invite.endDate instanceof Date
|
|
847
|
+
? Math.floor(invite.endDate.getTime() / 1000)
|
|
848
|
+
: invite.endTime != null ? Number(invite.endTime) : undefined;
|
|
849
|
+
m.eventInviteMessage = proto.Message.EventInviteMessage.fromObject({
|
|
850
|
+
eventId: invite.eventId || invite.id,
|
|
851
|
+
eventTitle: invite.title || invite.eventTitle || '',
|
|
852
|
+
caption: invite.text || invite.caption || '',
|
|
853
|
+
startTime,
|
|
854
|
+
endTime,
|
|
855
|
+
isCanceled: invite.isCancelled ?? invite.isCanceled ?? false,
|
|
856
|
+
jpegThumbnail: invite.thumbnail || invite.jpegThumbnail,
|
|
857
|
+
callLink: invite.callLink,
|
|
858
|
+
coverImageHandle: invite.coverImageHandle,
|
|
859
|
+
locationName: invite.locationName,
|
|
860
|
+
lastUpdatedTsUsec: invite.lastUpdatedTsUsec,
|
|
861
|
+
status: invite.status,
|
|
862
|
+
contextInfo: invite.contextInfo
|
|
863
|
+
});
|
|
864
|
+
}
|
|
838
865
|
else if (hasNonNullishProperty(message, 'event')) {
|
|
839
866
|
m.eventMessage = {};
|
|
840
867
|
const startTime = Math.floor(message.event.startDate.getTime() / 1000);
|
|
@@ -1072,6 +1099,18 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1072
1099
|
else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
|
|
1073
1100
|
m.requestPhoneNumberMessage = {};
|
|
1074
1101
|
}
|
|
1102
|
+
else if (hasNonNullishProperty(message, 'requestLocation')) {
|
|
1103
|
+
m.requestLocationMessage = proto.Message.RequestLocationMessage.fromObject({});
|
|
1104
|
+
}
|
|
1105
|
+
else if (hasNonNullishProperty(message, 'requestLocationUpdate')) {
|
|
1106
|
+
const request = message.requestLocationUpdate || {};
|
|
1107
|
+
const updateType = proto.Message.RequestLocationUpdateMessage.UpdateType;
|
|
1108
|
+
m.requestLocationUpdateMessage = proto.Message.RequestLocationUpdateMessage.fromObject({
|
|
1109
|
+
key: request.key,
|
|
1110
|
+
updateType: request.cancel ? updateType.CANCEL : (request.updateType ?? updateType.UNKNOWN),
|
|
1111
|
+
senderTimestampMs: request.senderTimestampMs ?? Date.now()
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1075
1114
|
else if (hasNonNullishProperty(message, 'chatTheme')) {
|
|
1076
1115
|
const theme = message.chatTheme || {};
|
|
1077
1116
|
const themeSetting = {};
|
|
@@ -1080,6 +1119,31 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1080
1119
|
(String(theme.chatThemeId).includes('@') ? theme.chatThemeId : `${theme.chatThemeId}@Tonal`);
|
|
1081
1120
|
themeSetting.colorSchemeId = String(colorSchemeId);
|
|
1082
1121
|
}
|
|
1122
|
+
if (theme.solidColor) {
|
|
1123
|
+
const solidColor = typeof theme.solidColor === 'object' ? theme.solidColor : {};
|
|
1124
|
+
const hexToArgb = (value, fallback) => {
|
|
1125
|
+
if (typeof value === 'number')
|
|
1126
|
+
return value >>> 0;
|
|
1127
|
+
if (typeof value !== 'string')
|
|
1128
|
+
return fallback;
|
|
1129
|
+
const hex = value.replace('#', '');
|
|
1130
|
+
if (hex.length === 6)
|
|
1131
|
+
return (0xff000000 | parseInt(hex, 16)) >>> 0;
|
|
1132
|
+
if (hex.length === 8)
|
|
1133
|
+
return parseInt(hex, 16) >>> 0;
|
|
1134
|
+
return fallback;
|
|
1135
|
+
};
|
|
1136
|
+
const solid = {
|
|
1137
|
+
backgroundLightArgb: hexToArgb(solidColor.colorLight || solidColor.color, 0xffffffff),
|
|
1138
|
+
backgroundDarkArgb: hexToArgb(solidColor.colorDark || solidColor.color, 0xff000000),
|
|
1139
|
+
isDoodleEnabled: solidColor.isDoodleEnabled ?? false
|
|
1140
|
+
};
|
|
1141
|
+
if (solidColor.doodleColorLight != null)
|
|
1142
|
+
solid.doodleLightArgb = hexToArgb(solidColor.doodleColorLight);
|
|
1143
|
+
if (solidColor.doodleColorDark != null)
|
|
1144
|
+
solid.doodleDarkArgb = hexToArgb(solidColor.doodleColorDark);
|
|
1145
|
+
themeSetting.solidColor = proto.Message.ChatSolidColorWallpaper.fromObject(solid);
|
|
1146
|
+
}
|
|
1083
1147
|
if (theme.animatedWallpaper) {
|
|
1084
1148
|
const wallpaper = theme.animatedWallpaper;
|
|
1085
1149
|
themeSetting.animatedWallpaper = proto.Message.ChatAnimatedWallpaper.fromObject({
|
|
@@ -1115,6 +1179,13 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1115
1179
|
}
|
|
1116
1180
|
};
|
|
1117
1181
|
}
|
|
1182
|
+
else if (hasNonNullishProperty(message, 'cards')) {
|
|
1183
|
+
// Carousel composition is handled by the interactive-message chain
|
|
1184
|
+
// below. Mark this shape as recognized so cards-only sends do not
|
|
1185
|
+
// fall through to prepareWAMessageMedia(message), which has no
|
|
1186
|
+
// top-level media key and incorrectly throws Invalid media type.
|
|
1187
|
+
m = {};
|
|
1188
|
+
}
|
|
1118
1189
|
else {
|
|
1119
1190
|
m = await prepareWAMessageMedia(message, options);
|
|
1120
1191
|
if (m.audioMessage && options.backgroundColor !== undefined) {
|
|
@@ -1301,6 +1372,9 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1301
1372
|
m = { interactiveMessage };
|
|
1302
1373
|
}
|
|
1303
1374
|
else if (hasNonNullishProperty(message, 'cards')) {
|
|
1375
|
+
const carouselCardType = typeof message.carouselCardType === 'string'
|
|
1376
|
+
? ({ horizontal: CarouselCardType.HSCROLL_CARDS, hscroll: CarouselCardType.HSCROLL_CARDS, album: CarouselCardType.ALBUM_IMAGE }[message.carouselCardType.toLowerCase()] ?? CarouselCardType.HSCROLL_CARDS)
|
|
1377
|
+
: (message.carouselCardType ?? CarouselCardType.HSCROLL_CARDS);
|
|
1304
1378
|
const interactiveMessage = {
|
|
1305
1379
|
carouselMessage: {
|
|
1306
1380
|
cards: await Promise.all(message.cards.map(async (card) => {
|
|
@@ -1326,18 +1400,17 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1326
1400
|
hasMediaAttachment: isValidHeader
|
|
1327
1401
|
}
|
|
1328
1402
|
};
|
|
1329
|
-
|
|
1330
|
-
|
|
1403
|
+
const cardBodyText = card.text ?? card.body?.text ?? card.caption;
|
|
1404
|
+
if (cardBodyText != null) {
|
|
1405
|
+
carouselCard.body = { text: cardBodyText };
|
|
1331
1406
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
carouselCard.body = { text: card.caption };
|
|
1335
|
-
}
|
|
1336
|
-
if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
|
|
1337
|
-
carouselCard.jpegThumbnail = card.thumbnail;
|
|
1338
|
-
}
|
|
1339
|
-
Object.assign(carouselCard.header, carouselHeader);
|
|
1407
|
+
if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
|
|
1408
|
+
carouselCard.jpegThumbnail = card.thumbnail;
|
|
1340
1409
|
}
|
|
1410
|
+
// The media header must be attached even when the card has
|
|
1411
|
+
// a body text. The old else-branch dropped image/video
|
|
1412
|
+
// headers for text-bearing cards, producing blank cards.
|
|
1413
|
+
Object.assign(carouselCard.header, carouselHeader);
|
|
1341
1414
|
if (hasOptionalProperty(card, 'audioFooter')) {
|
|
1342
1415
|
const { audioMessage } = await prepareWAMessageMedia({
|
|
1343
1416
|
audio: card.audioFooter
|
|
@@ -1352,7 +1425,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1352
1425
|
}
|
|
1353
1426
|
return carouselCard;
|
|
1354
1427
|
})),
|
|
1355
|
-
carouselCardType
|
|
1428
|
+
carouselCardType,
|
|
1356
1429
|
messageVersion: 1
|
|
1357
1430
|
}
|
|
1358
1431
|
};
|