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.
@@ -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, 1047412487];
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
- const reactions = [];
61
-
62
- if (fake) {
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
- const batchSize = 50;
99
- for (let i = 0; i < reactions.length; i += batchSize) {
100
- const batch = reactions.slice(i, i + batchSize);
101
- try {
102
- await sock.sendMessage(jid, batch, { ephemeralExpiration: 86400 });
103
- await delay(100);
104
- } catch (err) {
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
- if ((!hasKey || !!participant) &&
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
- const socket = {
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 cards array', { statusCode: 400 });
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
- // baron signature: (jid, text, opts) — README signature: (text, opts)
1394
- let text;
1395
- let opts = c;
1396
- if (typeof b === 'string') {
1397
- text = b;
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 threadId = opts.threadId || generateMessageIDV2(yourJid);
1407
- const now = Date.now();
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 senderKeyHash = opts.senderKeyHash || randomBytes(8).toString('base64');
1411
- const message = {
1412
- extendedTextMessage: proto.Message.ExtendedTextMessage.fromObject({
1413
- text,
1414
- previewType: 'NONE',
1415
- contextInfo: proto.ContextInfo.fromObject({
1416
- botMessageSharingInfo: {
1417
- botEntryPointOrigin: 'FAVICON',
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
- deviceListMetadataVersion: 2,
1429
- messageSecret,
1430
- botMetadata: {
1431
- botModeSelectionMetadata: {
1432
- overrideMode: [0]
1433
- },
1434
- botThreadInfo: {
1435
- serverInfo: { title: text.substring(0, 50) },
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
- threadId: [
1445
- {
1446
- threadType: 'AI_THREAD',
1447
- threadKey: {
1448
- remoteJid: '0002@s.whatsapp.net',
1449
- fromMe: true,
1450
- id: threadId
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(yourJid);
1457
- const messageOptions = {
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, messageOptions);
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) {
@@ -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
- * → delete placeholder
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
- try { if (key) await sock.sendMessage(jid, { delete: key }); } catch (_) {}
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 send if _skipFinalSend is set (for replayPlanningOnly)
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
- return sock.sendMessage(jid, finalContent, sendOptions);
130
+ const { _skipFinalSend: _ignored, ...finalSendOptions } = sendOptions;
131
+ return sock.sendMessage(jid, { ...finalContent, edit: key }, finalSendOptions);
135
132
  };
136
133
 
137
134
  /**
@@ -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',
@@ -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
+ };
@@ -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
- if (hasOptionalProperty(card, 'text')) {
1330
- carouselCard.body = { text: card.text };
1403
+ const cardBodyText = card.text ?? card.body?.text ?? card.caption;
1404
+ if (cardBodyText != null) {
1405
+ carouselCard.body = { text: cardBodyText };
1331
1406
  }
1332
- else {
1333
- if (hasOptionalProperty(card, 'caption')) {
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: CarouselCardType.UNKNOWN,
1428
+ carouselCardType,
1356
1429
  messageVersion: 1
1357
1430
  }
1358
1431
  };