plogme 2.0.8 → 2.1.0
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 +565 -18
- 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 +99 -66
- 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 +88 -8
- 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';
|
|
@@ -233,6 +233,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
233
233
|
const decoded = jidDecode(jid);
|
|
234
234
|
const user = decoded?.user;
|
|
235
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
|
+
}
|
|
236
246
|
const isExplicitDevice = typeof device === 'number' && device >= 0;
|
|
237
247
|
if (isExplicitDevice && user) {
|
|
238
248
|
deviceResults.push({
|
|
@@ -396,7 +406,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
396
406
|
if (jidsRequiringFetch.length) {
|
|
397
407
|
const wireJids = [
|
|
398
408
|
...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
|
|
399
|
-
...((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))
|
|
400
413
|
];
|
|
401
414
|
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
|
|
402
415
|
const result = await query({
|
|
@@ -677,6 +690,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
677
690
|
if (isStatus && statusJidList) {
|
|
678
691
|
participantsList.push(...statusJidList);
|
|
679
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
|
+
}
|
|
680
703
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
|
|
681
704
|
devices.push(...additionalDevices);
|
|
682
705
|
if (isGroup) {
|
|
@@ -702,7 +725,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
702
725
|
for (const device of devices) {
|
|
703
726
|
const deviceJid = device.jid;
|
|
704
727
|
const hasKey = !!senderKeyMap[deviceJid];
|
|
705
|
-
|
|
728
|
+
const isBotRecipient = isJidMetaAI(deviceJid) || isJidBot(deviceJid);
|
|
729
|
+
if ((!hasKey || !!participant || isBotRecipient) &&
|
|
706
730
|
!isHostedLidUser(deviceJid) &&
|
|
707
731
|
!isHostedPnUser(deviceJid) &&
|
|
708
732
|
device.device !== 99) {
|
|
@@ -1301,7 +1325,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1301
1325
|
},
|
|
1302
1326
|
sendCarousel: async (jid, cards, options = {}) => {
|
|
1303
1327
|
if (!Array.isArray(cards) || cards.length === 0) {
|
|
1304
|
-
throw new Boom('sendCarousel requires a non-empty
|
|
1328
|
+
throw new Boom('sendCarousel requires a non-empty card array', { statusCode: 400 });
|
|
1305
1329
|
}
|
|
1306
1330
|
const normalizedCards = cards.map(card => ({
|
|
1307
1331
|
...card,
|
|
@@ -1317,6 +1341,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1317
1341
|
const fullMsg = await generateWAMessage(jid, {
|
|
1318
1342
|
...messageOptions,
|
|
1319
1343
|
cards: normalizedCards,
|
|
1344
|
+
...(options.carouselCardType != null ? { carouselCardType: options.carouselCardType } : {}),
|
|
1320
1345
|
...(text !== undefined ? { text } : {}),
|
|
1321
1346
|
...(footer !== undefined ? { footer } : {})
|
|
1322
1347
|
}, {
|
|
@@ -1379,76 +1404,71 @@ export const makeMessagesSocket = (config) => {
|
|
|
1379
1404
|
return fullMsg;
|
|
1380
1405
|
},
|
|
1381
1406
|
sendMetaAI: async (a, b, c = {}) => {
|
|
1382
|
-
//
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
else {
|
|
1389
|
-
text = a;
|
|
1390
|
-
opts = b || {};
|
|
1391
|
-
}
|
|
1392
|
-
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 || {});
|
|
1393
1412
|
const META_AI_BOT_JID = '867051314767696@bot';
|
|
1413
|
+
const META_AI_FBID = '867051314767696';
|
|
1394
1414
|
const jid = opts.jid || META_AI_BOT_JID;
|
|
1395
|
-
const
|
|
1396
|
-
const
|
|
1397
|
-
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;
|
|
1398
1417
|
const messageSecret = opts.messageSecret || randomBytes(32);
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
forwardScore: 0
|
|
1408
|
-
}
|
|
1409
|
-
}),
|
|
1410
|
-
inviteLinkGroupTypeV2: 'DEFAULT'
|
|
1411
|
-
}),
|
|
1412
|
-
messageContextInfo: proto.MessageContextInfo.fromObject({
|
|
1413
|
-
deviceListMetadata: {
|
|
1414
|
-
senderKeyHash,
|
|
1415
|
-
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 }]
|
|
1416
1426
|
},
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
clientInfo: { type: 'DEFAULT' }
|
|
1426
|
-
},
|
|
1427
|
-
botRenderingConfigMetadata: {
|
|
1428
|
-
bloksVersioningId: '1eb86e6f4117d052e6bab62fe758a2e2af43747b85c5c1a886c8262bac462ea4',
|
|
1429
|
-
pixelDensity: 2.625
|
|
1430
|
-
},
|
|
1431
|
-
...(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
|
|
1432
1435
|
},
|
|
1433
|
-
|
|
1434
|
-
{
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
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
|
+
}
|
|
1444
1456
|
};
|
|
1445
|
-
const msgId = generateMessageIDV2(
|
|
1446
|
-
const
|
|
1457
|
+
const msgId = generateMessageIDV2(invokerJid || '');
|
|
1458
|
+
const relayOptions = {
|
|
1447
1459
|
messageId: msgId,
|
|
1448
1460
|
...(opts.quoted ? { quoted: opts.quoted } : {}),
|
|
1449
|
-
...(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
|
+
}]
|
|
1450
1470
|
};
|
|
1451
|
-
await relayMessage(jid, message,
|
|
1471
|
+
await relayMessage(jid, message, relayOptions);
|
|
1452
1472
|
return msgId;
|
|
1453
1473
|
},
|
|
1454
1474
|
// Additive Nexus-compatible AI-rich helpers. Existing Gen4/table/grid/carousel APIs remain unchanged.
|
|
@@ -1588,6 +1608,19 @@ export const makeMessagesSocket = (config) => {
|
|
|
1588
1608
|
}
|
|
1589
1609
|
return fullMsg;
|
|
1590
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
|
+
},
|
|
1591
1624
|
captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted, options = {}) => {
|
|
1592
1625
|
const captured = captureUnifiedResponse(metaAiMsg);
|
|
1593
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
|
};
|
|
@@ -65,10 +65,10 @@ const decodeDecryptedMsmsgMessage = (decrypted) => {
|
|
|
65
65
|
const unpadded = Buffer.from(unpadRandomMax16(buf));
|
|
66
66
|
const decoded = proto.Message.decode(unpadded);
|
|
67
67
|
const hasContent = Object.keys(decoded).some(k => k !== 'messageContextInfo' && decoded[k] != null);
|
|
68
|
-
if (hasContent) return decoded;
|
|
68
|
+
if (hasContent) return decodeUnifiedResponseInPlace(decoded);
|
|
69
69
|
}
|
|
70
70
|
catch {}
|
|
71
|
-
return proto.Message.decode(buf);
|
|
71
|
+
return decodeUnifiedResponseInPlace(proto.Message.decode(buf));
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -127,4 +127,77 @@ export const decryptMsmsgBotMessage = async (messageSecret, messageKey, msMsg) =
|
|
|
127
127
|
throw err;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
// unifiedResponse.data is base64/bytes-wrapped JSON, not encrypted. Keep the
|
|
131
|
+
// original data and expose both the decoded object and flattened text.
|
|
132
|
+
export const parseUnifiedResponseData = (data) => {
|
|
133
|
+
if (!data)
|
|
134
|
+
return null;
|
|
135
|
+
try {
|
|
136
|
+
let buf;
|
|
137
|
+
if (Buffer.isBuffer(data))
|
|
138
|
+
buf = data;
|
|
139
|
+
else if (data instanceof Uint8Array)
|
|
140
|
+
buf = Buffer.from(data);
|
|
141
|
+
else if (typeof data === 'string')
|
|
142
|
+
buf = Buffer.from(data, 'base64');
|
|
143
|
+
else if (data.type === 'Buffer' && Array.isArray(data.data))
|
|
144
|
+
buf = Buffer.from(data.data);
|
|
145
|
+
else
|
|
146
|
+
return null;
|
|
147
|
+
return JSON.parse(buf.toString('utf8'));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
export const textFromUnifiedResponse = (data) => {
|
|
154
|
+
const json = parseUnifiedResponseData(data);
|
|
155
|
+
if (!json)
|
|
156
|
+
return null;
|
|
157
|
+
const parts = [];
|
|
158
|
+
for (const section of json.sections || []) {
|
|
159
|
+
const text = section?.view_model?.primitive?.text;
|
|
160
|
+
if (text)
|
|
161
|
+
parts.push(text);
|
|
162
|
+
}
|
|
163
|
+
return parts.length ? parts.join('\n') : null;
|
|
164
|
+
};
|
|
165
|
+
export const textFromRichResponse = (richResponse) => {
|
|
166
|
+
if (!richResponse)
|
|
167
|
+
return null;
|
|
168
|
+
if (Array.isArray(richResponse.submessages)) {
|
|
169
|
+
const parts = richResponse.submessages
|
|
170
|
+
.map(submessage => submessage?.messageText)
|
|
171
|
+
.filter(text => typeof text === 'string' && text.length);
|
|
172
|
+
if (parts.length)
|
|
173
|
+
return parts.join('\n');
|
|
174
|
+
}
|
|
175
|
+
return textFromUnifiedResponse(richResponse.unifiedResponse?.data);
|
|
176
|
+
};
|
|
177
|
+
export const extractMetaAiText = (message) => {
|
|
178
|
+
if (!message)
|
|
179
|
+
return null;
|
|
180
|
+
const body = message.protocolMessage?.editedMessage || message;
|
|
181
|
+
return body.extendedTextMessage?.text ?? body.conversation ?? textFromRichResponse(body.richResponseMessage) ?? null;
|
|
182
|
+
};
|
|
183
|
+
export const decodeUnifiedResponseInPlace = (message) => {
|
|
184
|
+
if (!message || typeof message !== 'object')
|
|
185
|
+
return message;
|
|
186
|
+
const bodies = [
|
|
187
|
+
message,
|
|
188
|
+
message.protocolMessage?.editedMessage,
|
|
189
|
+
message.editedMessage
|
|
190
|
+
].filter(Boolean);
|
|
191
|
+
for (const body of bodies) {
|
|
192
|
+
const unifiedResponse = body.richResponseMessage?.unifiedResponse;
|
|
193
|
+
if (unifiedResponse && unifiedResponse.data != null && unifiedResponse.decodedData === undefined) {
|
|
194
|
+
unifiedResponse.decodedData = parseUnifiedResponseData(unifiedResponse.data);
|
|
195
|
+
const text = textFromUnifiedResponse(unifiedResponse.data);
|
|
196
|
+
if (text != null)
|
|
197
|
+
unifiedResponse.text = text;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return message;
|
|
201
|
+
};
|
|
202
|
+
|
|
130
203
|
export { decodeDecryptedMsmsgMessage };
|