plogme 2.0.4 → 2.0.6
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 +1526 -5
- package/lib/Socket/chats.js +3 -0
- package/lib/Socket/messages-send.js +178 -9
- package/lib/Utils/messages.js +10 -1
- package/lib/Utils/rich-message-utils.js +270 -33
- package/lib/index.d.ts +29 -0
- package/package.json +1 -1
package/lib/Socket/chats.js
CHANGED
|
@@ -1863,6 +1863,9 @@ export const makeChatsSocket = (config) => {
|
|
|
1863
1863
|
getUserDisclosures,
|
|
1864
1864
|
acceptTosNotice,
|
|
1865
1865
|
reportSpam,
|
|
1866
|
+
reportUser,
|
|
1867
|
+
reportMessage,
|
|
1868
|
+
reportAndBlockUser,
|
|
1866
1869
|
toggleGroupHistory,
|
|
1867
1870
|
getOptOutList,
|
|
1868
1871
|
signPrivateCredential,
|
|
@@ -5,7 +5,7 @@ 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, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
|
|
8
|
+
import { prepareRichMenuMessage, prepareA2UIMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareSocialEntityMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareSocialProfileMessage, prepareUnifiedResponseMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, 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';
|
|
@@ -1051,7 +1051,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1051
1051
|
await relayMessage(jid, fullMsg.message, relayOptions);
|
|
1052
1052
|
|
|
1053
1053
|
const richEnvelope = fullMsg.message?.botForwardedMessage?.message?.richResponseMessage;
|
|
1054
|
-
if (options.renderRichResponse !== false && richEnvelope) {
|
|
1054
|
+
if (options.renderRichResponse !== false && (richEnvelope || options.forceRichEdit || fullMsg.message?.interactiveMessage?.bloksWidget)) {
|
|
1055
1055
|
const editContent = proto.Message.fromObject({
|
|
1056
1056
|
botForwardedMessage: {
|
|
1057
1057
|
message: {
|
|
@@ -2370,6 +2370,76 @@ export const makeMessagesSocket = (config) => {
|
|
|
2370
2370
|
}
|
|
2371
2371
|
return fullMsg;
|
|
2372
2372
|
};
|
|
2373
|
+
const sendSocialProfile = async (methodName, jid, content, options, prepare) => {
|
|
2374
|
+
if (!jid) throw new Boom(`${methodName} requires a target JID`, { statusCode: 400 });
|
|
2375
|
+
const wrapped = prepare(content);
|
|
2376
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2377
|
+
logger,
|
|
2378
|
+
userJid: authState.creds.me?.id,
|
|
2379
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
|
|
2380
|
+
...options
|
|
2381
|
+
});
|
|
2382
|
+
await relayMessage(jid, fullMsg.message, {
|
|
2383
|
+
messageId: fullMsg.key.id,
|
|
2384
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
2385
|
+
statusJidList: options.statusJidList
|
|
2386
|
+
});
|
|
2387
|
+
if (config.emitOwnEvents) {
|
|
2388
|
+
process.nextTick(async () => {
|
|
2389
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
return fullMsg;
|
|
2393
|
+
};
|
|
2394
|
+
socket.sendSocialProfile = async (jid, content = {}, options = {}) => sendSocialProfile('sendSocialProfile', jid, content, options, prepareSocialProfileMessage);
|
|
2395
|
+
socket.sendInstagramProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
|
|
2396
|
+
'sendInstagramProfile',
|
|
2397
|
+
jid,
|
|
2398
|
+
{ ...content, platform: 'instagram', entityType: content.entityType || 'IG_PROFILE', entityUrl: content.entityUrl || `https://www.instagram.com/${content.username || ''}` },
|
|
2399
|
+
options,
|
|
2400
|
+
prepareInstagramProfileMessage
|
|
2401
|
+
);
|
|
2402
|
+
socket.sendFacebookProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
|
|
2403
|
+
'sendFacebookProfile', jid,
|
|
2404
|
+
{ ...content, platform: 'facebook', entityType: content.entityType || 'FB_PROFILE', entityUrl: content.entityUrl || `https://www.facebook.com/${content.username || ''}` },
|
|
2405
|
+
options, prepareSocialProfileMessage
|
|
2406
|
+
);
|
|
2407
|
+
socket.sendTikTokProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
|
|
2408
|
+
'sendTikTokProfile', jid,
|
|
2409
|
+
{ ...content, platform: 'tiktok', entityType: content.entityType || 'TIKTOK_PROFILE', entityUrl: content.entityUrl || `https://www.tiktok.com/@${content.username || ''}` },
|
|
2410
|
+
options, prepareSocialProfileMessage
|
|
2411
|
+
);
|
|
2412
|
+
socket.sendYouTubeProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
|
|
2413
|
+
'sendYouTubeProfile', jid,
|
|
2414
|
+
{ ...content, platform: 'youtube', entityType: content.entityType || 'YOUTUBE_PROFILE', entityUrl: content.entityUrl || `https://www.youtube.com/@${content.username || ''}` },
|
|
2415
|
+
options, prepareSocialProfileMessage
|
|
2416
|
+
);
|
|
2417
|
+
socket.sendXProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
|
|
2418
|
+
'sendXProfile', jid,
|
|
2419
|
+
{ ...content, platform: 'x', entityType: content.entityType || 'X_PROFILE', entityUrl: content.entityUrl || `https://x.com/${content.username || ''}` },
|
|
2420
|
+
options, prepareSocialProfileMessage
|
|
2421
|
+
);
|
|
2422
|
+
socket.sendUnifiedResponse = async (jid, content = {}, options = {}) => {
|
|
2423
|
+
if (!jid) throw new Boom('sendUnifiedResponse requires a target JID', { statusCode: 400 });
|
|
2424
|
+
const wrapped = prepareUnifiedResponseMessage(content);
|
|
2425
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2426
|
+
logger,
|
|
2427
|
+
userJid: authState.creds.me?.id,
|
|
2428
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
|
|
2429
|
+
...options
|
|
2430
|
+
});
|
|
2431
|
+
await relayMessage(jid, fullMsg.message, {
|
|
2432
|
+
messageId: fullMsg.key.id,
|
|
2433
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
2434
|
+
statusJidList: options.statusJidList
|
|
2435
|
+
});
|
|
2436
|
+
if (config.emitOwnEvents) {
|
|
2437
|
+
process.nextTick(async () => {
|
|
2438
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
return fullMsg;
|
|
2442
|
+
};
|
|
2373
2443
|
socket.richMenu = async (jid, content = {}, options = {}) => {
|
|
2374
2444
|
if (!jid) throw new Boom('richMenu requires a target JID', { statusCode: 400 });
|
|
2375
2445
|
const wrapped = prepareRichMenuMessage(content);
|
|
@@ -2407,7 +2477,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
2407
2477
|
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2408
2478
|
...options,
|
|
2409
2479
|
});
|
|
2410
|
-
await
|
|
2480
|
+
await relayRichMessage(jid, fullMsg, { ...options, forceRichEdit: true });
|
|
2411
2481
|
if (config.emitOwnEvents) {
|
|
2412
2482
|
process.nextTick(async () => {
|
|
2413
2483
|
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
@@ -2415,6 +2485,27 @@ export const makeMessagesSocket = (config) => {
|
|
|
2415
2485
|
}
|
|
2416
2486
|
return fullMsg;
|
|
2417
2487
|
};
|
|
2488
|
+
/** Send arbitrary A2UI/interactive payloads; sendA2UICommandMenu is a preset wrapper. */
|
|
2489
|
+
socket.sendA2UIMessage = async (jid, content = {}, options = {}) => {
|
|
2490
|
+
if (!jid) throw new Boom('sendA2UIMessage requires a target JID', { statusCode: 400 });
|
|
2491
|
+
const { image, ...payload } = content || {};
|
|
2492
|
+
let imageMessage;
|
|
2493
|
+
if (image) {
|
|
2494
|
+
const prepared = await prepareWAMessageMedia({ image }, { upload: waUploadToServer });
|
|
2495
|
+
imageMessage = prepared.imageMessage;
|
|
2496
|
+
}
|
|
2497
|
+
const wrapped = prepareA2UIMessage({ ...payload, imageMessage });
|
|
2498
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2499
|
+
logger,
|
|
2500
|
+
userJid: authState.creds.me?.id,
|
|
2501
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2502
|
+
...options,
|
|
2503
|
+
});
|
|
2504
|
+
await relayRichMessage(jid, fullMsg, { ...options, forceRichEdit: true });
|
|
2505
|
+
if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
|
|
2506
|
+
return fullMsg;
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2418
2509
|
/** Sends the manually serialized AIRich command card shown in the screenshot. */
|
|
2419
2510
|
socket.sendAIRichCommandMenu = async (jid, content = {}, options = {}) => {
|
|
2420
2511
|
if (!jid) throw new Boom('sendAIRichCommandMenu requires a target JID', { statusCode: 400 });
|
|
@@ -2433,6 +2524,36 @@ export const makeMessagesSocket = (config) => {
|
|
|
2433
2524
|
}
|
|
2434
2525
|
return fullMsg;
|
|
2435
2526
|
};
|
|
2527
|
+
/** Send a provider-neutral AIRich social/entity result card. */
|
|
2528
|
+
socket.sendSocialEntity = async (jid, content = {}, options = {}) => {
|
|
2529
|
+
if (!jid) throw new Boom('sendSocialEntity requires a target JID', { statusCode: 400 });
|
|
2530
|
+
const wrapped = prepareSocialEntityMessage(content);
|
|
2531
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2532
|
+
logger,
|
|
2533
|
+
userJid: authState.creds.me?.id,
|
|
2534
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2535
|
+
...options,
|
|
2536
|
+
});
|
|
2537
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
2538
|
+
if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
|
|
2539
|
+
return fullMsg;
|
|
2540
|
+
};
|
|
2541
|
+
|
|
2542
|
+
/** Send the primary Instagram profile result using the universal social/entity API. */
|
|
2543
|
+
socket.sendInstagramProfile = async (jid, content = {}, options = {}) => {
|
|
2544
|
+
if (!jid) throw new Boom('sendInstagramProfile requires a target JID', { statusCode: 400 });
|
|
2545
|
+
const wrapped = prepareInstagramProfileMessage(content);
|
|
2546
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2547
|
+
logger,
|
|
2548
|
+
userJid: authState.creds.me?.id,
|
|
2549
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2550
|
+
...options,
|
|
2551
|
+
});
|
|
2552
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
2553
|
+
if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
|
|
2554
|
+
return fullMsg;
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2436
2557
|
/** Sends a rich response with experimental BotInfrastructureDiagnostics metadata. */
|
|
2437
2558
|
socket.sendBotDiagnostics = async (jid, content = {}, options = {}) => {
|
|
2438
2559
|
if (!jid) throw new Boom('sendBotDiagnostics requires a target JID', { statusCode: 400 });
|
|
@@ -2484,7 +2605,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
2484
2605
|
const wrapped = buildBotRichResponse({
|
|
2485
2606
|
text,
|
|
2486
2607
|
botJid,
|
|
2487
|
-
capabilities: [BotCapabilityType.AGENTIC_PLANNING],
|
|
2608
|
+
capabilities: [BotCapabilityType.AGENTIC_PLANNING, BotCapabilityType.QUERY_PLAN],
|
|
2488
2609
|
botMetadata: {
|
|
2489
2610
|
resolvedToolCallMetadata: {
|
|
2490
2611
|
toolCallId,
|
|
@@ -2496,6 +2617,58 @@ export const makeMessagesSocket = (config) => {
|
|
|
2496
2617
|
await relayRichMessage(jid, fullMsg, options);
|
|
2497
2618
|
return fullMsg;
|
|
2498
2619
|
};
|
|
2620
|
+
/**
|
|
2621
|
+
* Sends a real BotProgressIndicatorMetadata planning envelope. The plan is
|
|
2622
|
+
* application-controlled; this advertises the public schema capability but
|
|
2623
|
+
* does not claim to invoke Meta's private planning backend.
|
|
2624
|
+
*/
|
|
2625
|
+
socket.sendBotPlan = async (jid, content = {}, options = {}) => {
|
|
2626
|
+
if (!jid) throw new Boom('sendBotPlan requires a target JID', { statusCode: 400 });
|
|
2627
|
+
const {
|
|
2628
|
+
text = 'Execution plan',
|
|
2629
|
+
title = 'Execution plan',
|
|
2630
|
+
steps = [],
|
|
2631
|
+
estimatedCompletionTime,
|
|
2632
|
+
botJid = '867051314767696@bot'
|
|
2633
|
+
} = content;
|
|
2634
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
2635
|
+
throw new Boom('sendBotPlan requires at least one step', { statusCode: 400 });
|
|
2636
|
+
}
|
|
2637
|
+
const planningStatus = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus;
|
|
2638
|
+
const normalizedSteps = steps.map((step, index) => ({
|
|
2639
|
+
statusTitle: String(step.title || step.name || `Step ${index + 1}`),
|
|
2640
|
+
statusBody: String(step.body || step.description || ''),
|
|
2641
|
+
status: step.status === 'executing' ? planningStatus.EXECUTING
|
|
2642
|
+
: step.status === 'finished' || step.status === 'completed' ? planningStatus.FINISHED
|
|
2643
|
+
: planningStatus.PLANNED,
|
|
2644
|
+
isReasoning: step.isReasoning === true,
|
|
2645
|
+
isEnhancedSearch: step.isEnhancedSearch === true,
|
|
2646
|
+
...(Array.isArray(step.sections) ? { sections: step.sections } : {})
|
|
2647
|
+
}));
|
|
2648
|
+
const wrapped = {
|
|
2649
|
+
messageContextInfo: { botMetadata: {
|
|
2650
|
+
capabilityMetadata: {
|
|
2651
|
+
capabilities: [
|
|
2652
|
+
proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING,
|
|
2653
|
+
proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN
|
|
2654
|
+
]
|
|
2655
|
+
},
|
|
2656
|
+
progressIndicatorMetadata: {
|
|
2657
|
+
progressDescription: title,
|
|
2658
|
+
stepsMetadata: normalizedSteps,
|
|
2659
|
+
...(estimatedCompletionTime ? { estimatedCompletionTime } : {})
|
|
2660
|
+
}
|
|
2661
|
+
} },
|
|
2662
|
+
botForwardedMessage: { message: { richResponseMessage: {
|
|
2663
|
+
messageType: 1,
|
|
2664
|
+
submessages: [{ messageType: 2, messageText: text }],
|
|
2665
|
+
contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 }
|
|
2666
|
+
} } }
|
|
2667
|
+
};
|
|
2668
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
|
|
2669
|
+
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
|
|
2670
|
+
return fullMsg;
|
|
2671
|
+
};
|
|
2499
2672
|
socket.sendStatus = async (content, options = {}) => {
|
|
2500
2673
|
const statusJidList = await resolveStatusRecipients(options.statusJidList || content?.statusJidList);
|
|
2501
2674
|
return socket.sendMessage(STATUS_JID, {
|
|
@@ -2792,11 +2965,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
2792
2965
|
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
|
|
2793
2966
|
...options
|
|
2794
2967
|
});
|
|
2795
|
-
await
|
|
2796
|
-
messageId: fullMsg.key.id,
|
|
2797
|
-
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
2798
|
-
statusJidList: options.statusJidList
|
|
2799
|
-
});
|
|
2968
|
+
await relayRichMessage(jid, fullMsg, { ...options, forceRichEdit: true });
|
|
2800
2969
|
return fullMsg;
|
|
2801
2970
|
};
|
|
2802
2971
|
|
package/lib/Utils/messages.js
CHANGED
|
@@ -1592,7 +1592,16 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1592
1592
|
m = { lottieStickerMessage: { message: m } };
|
|
1593
1593
|
}
|
|
1594
1594
|
else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
|
|
1595
|
-
|
|
1595
|
+
// Text view-once clients expect the flag on the inner
|
|
1596
|
+
// ExtendedTextMessage. Media continues to use the standard outer
|
|
1597
|
+
// viewOnceMessage envelope.
|
|
1598
|
+
if (m.extendedTextMessage) {
|
|
1599
|
+
m.extendedTextMessage.viewOnce = true;
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
m = { viewOnceMessage: { message: m } };
|
|
1603
|
+
}
|
|
1604
|
+
delete message.viewOnce;
|
|
1596
1605
|
}
|
|
1597
1606
|
// Lia@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
|
|
1598
1607
|
else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* If you use or copy this code, please credit my name or project. AND DO NOT CHANGE THIS NOTE
|
|
6
6
|
*/
|
|
7
7
|
import { getRandomValues, randomUUID, randomBytes } from 'crypto';
|
|
8
|
+
import { Boom } from '@hapi/boom';
|
|
8
9
|
import { BOT_RENDERING_CONFIG_METADATA, DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
|
|
9
10
|
import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
|
|
10
11
|
import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
|
|
@@ -44,6 +45,38 @@ export const tokenizeCode = (code, language = 'javascript') => {
|
|
|
44
45
|
}
|
|
45
46
|
return blocks;
|
|
46
47
|
};
|
|
48
|
+
const firstImageValue = (...values) => values.find(value => value !== undefined && value !== null && String(value).length > 0);
|
|
49
|
+
|
|
50
|
+
/** Normalize album image variants without downgrading full-size URLs to previews. */
|
|
51
|
+
const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)) => {
|
|
52
|
+
const previewUrl = firstImageValue(image.imagePreviewUrl, image.previewUrl, image.preview_url, image.sourceUrl, image.source_url, image.url) || '';
|
|
53
|
+
const fullUrl = firstImageValue(image.imageHighResUrl, image.highResUrl, image.high_res_url, image.fullUrl, image.full_url, image.sourceUrl, image.source_url, image.url, previewUrl);
|
|
54
|
+
const darkPreviewUrl = firstImageValue(image.darkModePreviewUrl, image.dark_mode_preview_url, previewUrl);
|
|
55
|
+
const darkFullUrl = firstImageValue(image.darkModeHighResUrl, image.darkModeHighResUrl, image.dark_mode_high_res_url, image.darkModePreviewUrl, image.dark_mode_preview_url, fullUrl);
|
|
56
|
+
const width = Number(image.previewWidth || image.preview_width || image.width || 600);
|
|
57
|
+
const height = Number(image.previewHeight || image.preview_height || image.height || 400);
|
|
58
|
+
const fullWidth = Number(image.fullWidth || image.full_width || image.highResWidth || image.high_res_width || image.width || width);
|
|
59
|
+
const fullHeight = Number(image.fullHeight || image.full_height || image.highResHeight || image.high_res_height || image.height || height);
|
|
60
|
+
const darkWidth = Number(image.darkWidth || image.dark_width || fullWidth);
|
|
61
|
+
const darkHeight = Number(image.darkHeight || image.dark_height || fullHeight);
|
|
62
|
+
const mimeType = image.mimeType || image.mime_type || 'image/png';
|
|
63
|
+
const media = (url, mediaWidth, mediaHeight) => ({
|
|
64
|
+
url,
|
|
65
|
+
url_fallback: url,
|
|
66
|
+
mime_type: mimeType,
|
|
67
|
+
expiration_timestamp_ms: expiration,
|
|
68
|
+
width: mediaWidth,
|
|
69
|
+
height: mediaHeight
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
preview_image: media(previewUrl, width, height),
|
|
73
|
+
full_image: media(fullUrl, fullWidth, fullHeight),
|
|
74
|
+
dark_mode_preview_image: media(darkPreviewUrl, darkWidth, darkHeight),
|
|
75
|
+
dark_mode_full_image: media(darkFullUrl, darkWidth, darkHeight),
|
|
76
|
+
asset_query_status: 'FETCHED',
|
|
77
|
+
__typename: 'GenAIImagePrimitive'
|
|
78
|
+
};
|
|
79
|
+
};
|
|
47
80
|
// Lia@Changes 09-04-26 --- Inject buffer into unifiedResponse.data to support proper rendering of rich messages (ex: tables and code blocks)
|
|
48
81
|
export const toUnified = (submessages, uuid) => ({
|
|
49
82
|
response_id: uuid || randomUUID(),
|
|
@@ -66,24 +99,7 @@ export const toUnified = (submessages, uuid) => ({
|
|
|
66
99
|
const imageUrls = submessage.gridImageMetadata?.imageUrls || [];
|
|
67
100
|
if (!imageUrls.length) return null;
|
|
68
101
|
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
69
|
-
const primitives = imageUrls.map(img =>
|
|
70
|
-
const previewUrl = img.imagePreviewUrl || img.sourceUrl || '';
|
|
71
|
-
const fullUrl = img.imageHighResUrl || img.imagePreviewUrl || img.sourceUrl || '';
|
|
72
|
-
const width = img.width || 600;
|
|
73
|
-
const height = img.height || 400;
|
|
74
|
-
const darkPreviewUrl = img.darkModePreviewUrl || previewUrl;
|
|
75
|
-
const darkFullUrl = img.darkModeHighResUrl || img.darkModePreviewUrl || fullUrl;
|
|
76
|
-
const darkWidth = img.darkWidth || width;
|
|
77
|
-
const darkHeight = img.darkHeight || height;
|
|
78
|
-
return {
|
|
79
|
-
preview_image: { url: previewUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width, height },
|
|
80
|
-
full_image: { url: fullUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width, height },
|
|
81
|
-
dark_mode_preview_image: { url: darkPreviewUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width: darkWidth, height: darkHeight },
|
|
82
|
-
dark_mode_full_image: { url: darkFullUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width: darkWidth, height: darkHeight },
|
|
83
|
-
asset_query_status: 'FETCHED',
|
|
84
|
-
__typename: 'GenAIImagePrimitive'
|
|
85
|
-
};
|
|
86
|
-
});
|
|
102
|
+
const primitives = imageUrls.map(img => buildRichImagePrimitive(img, expiration));
|
|
87
103
|
return { view_model: { primitives, __typename: 'GenAIGridLayoutViewModel' } };
|
|
88
104
|
}
|
|
89
105
|
case RichSubMessageType.CONTENT_ITEMS: {
|
|
@@ -376,6 +392,111 @@ const richMenuButton = (button, toast = '') => {
|
|
|
376
392
|
* This is an experimental WhatsApp rich-response format, not a standard
|
|
377
393
|
* buttonsMessage/nativeFlowMessage payload.
|
|
378
394
|
*/
|
|
395
|
+
/**
|
|
396
|
+
* Build a universal AIRich social/entity result card.
|
|
397
|
+
* The wire format is provider-neutral; Instagram is exposed as a dedicated
|
|
398
|
+
* convenience wrapper below because it is the primary supported profile view.
|
|
399
|
+
*/
|
|
400
|
+
export const prepareSocialEntityMessage = ({
|
|
401
|
+
username = '',
|
|
402
|
+
platform = 'GENERIC',
|
|
403
|
+
title = 'CODY AI',
|
|
404
|
+
subtitle = '⚉',
|
|
405
|
+
imageUrl = '',
|
|
406
|
+
entityId,
|
|
407
|
+
entityUrl = '',
|
|
408
|
+
entityType,
|
|
409
|
+
fullName = 'NOT found',
|
|
410
|
+
isVerified = false,
|
|
411
|
+
resultText = 'See results',
|
|
412
|
+
botJid = '867051314767696@bot',
|
|
413
|
+
responseId
|
|
414
|
+
} = {}) => {
|
|
415
|
+
const normalizedPlatform = String(platform || 'GENERIC').toUpperCase();
|
|
416
|
+
const profileUrl = entityUrl || (username ? `https://www.${normalizedPlatform.toLowerCase()}.com/${encodeURIComponent(username)}` : '');
|
|
417
|
+
const resolvedType = entityType || (normalizedPlatform === 'INSTAGRAM' ? 'IG_PROFILE' : 'SOCIAL_PROFILE');
|
|
418
|
+
const displayName = username || fullName || 'Profile';
|
|
419
|
+
const sections = [
|
|
420
|
+
{
|
|
421
|
+
view_model: {
|
|
422
|
+
primitives: [{
|
|
423
|
+
title,
|
|
424
|
+
subtitle,
|
|
425
|
+
...(imageUrl ? { image: { url: imageUrl, mime_type: 'image/png' } } : {}),
|
|
426
|
+
entity_id: String(entityId ?? username ?? ''),
|
|
427
|
+
entity_url: profileUrl,
|
|
428
|
+
entity_type: 'WEBSITE',
|
|
429
|
+
action_type: 'OPEN_URL',
|
|
430
|
+
is_verified: !!isVerified,
|
|
431
|
+
__typename: 'GenAICompactEntityPrimitive'
|
|
432
|
+
}],
|
|
433
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
view_model: {
|
|
438
|
+
primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }],
|
|
439
|
+
__typename: 'GenAIVStackLayoutViewModel'
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
view_model: {
|
|
444
|
+
primitives: [
|
|
445
|
+
{ __typename: 'GenAISpacerPrimitive' },
|
|
446
|
+
{
|
|
447
|
+
text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `,
|
|
448
|
+
inline_entities: [{
|
|
449
|
+
key: 'social_entity_1',
|
|
450
|
+
metadata: {
|
|
451
|
+
__typename: 'GenAISocialEntityItem',
|
|
452
|
+
entity_id: String(entityId ?? username ?? ''),
|
|
453
|
+
entity_name: displayName,
|
|
454
|
+
entity_full_name: fullName,
|
|
455
|
+
platform: String(platform || 'GENERIC').toUpperCase(),
|
|
456
|
+
entity_picture_url: imageUrl,
|
|
457
|
+
entity_url: profileUrl,
|
|
458
|
+
entity_type: resolvedType,
|
|
459
|
+
platform: normalizedPlatform,
|
|
460
|
+
is_verified: !!isVerified
|
|
461
|
+
}
|
|
462
|
+
}],
|
|
463
|
+
__typename: 'GenAIMarkdownTextUXPrimitive'
|
|
464
|
+
},
|
|
465
|
+
{ __typename: 'GenAISpacerPrimitive' }
|
|
466
|
+
],
|
|
467
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
];
|
|
471
|
+
return {
|
|
472
|
+
botForwardedMessage: {
|
|
473
|
+
message: {
|
|
474
|
+
richResponseMessage: {
|
|
475
|
+
messageType: 1,
|
|
476
|
+
submessages: [],
|
|
477
|
+
unifiedResponse: {
|
|
478
|
+
response_id: responseId || randomUUID(),
|
|
479
|
+
data: Buffer.from(JSON.stringify({ sections })).toString('base64')
|
|
480
|
+
},
|
|
481
|
+
contextInfo: {
|
|
482
|
+
isForwarded: true,
|
|
483
|
+
forwardOrigin: 4,
|
|
484
|
+
forwardedAiBotMessageInfo: { botJid }
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/** Dedicated Instagram profile card using the universal social-entity wire format. */
|
|
493
|
+
export const prepareInstagramProfileMessage = (content = {}) => prepareSocialProfileMessage({
|
|
494
|
+
...content,
|
|
495
|
+
platform: 'INSTAGRAM',
|
|
496
|
+
entityType: 'IG_PROFILE',
|
|
497
|
+
entityUrl: content.entityUrl || (content.username ? `https://www.instagram.com/${encodeURIComponent(content.username)}` : undefined)
|
|
498
|
+
});
|
|
499
|
+
|
|
379
500
|
export const prepareRichMenuMessage = (content = {}) => {
|
|
380
501
|
const { header = {}, body = {}, footer = {}, verificationMetadata } = content || {};
|
|
381
502
|
const sections = [];
|
|
@@ -500,6 +621,29 @@ export const prepareAIRichCommandMenu = ({ image, title = 'CODY AI', subtitle =
|
|
|
500
621
|
return wrapToBotForwardedMessage(richResponseMessage);
|
|
501
622
|
};
|
|
502
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Build an arbitrary A2UI interactive message. The command-menu helper below
|
|
626
|
+
* is only a convenience preset; callers may provide any supported component,
|
|
627
|
+
* widget, or native-flow payload through this universal builder.
|
|
628
|
+
*/
|
|
629
|
+
export const prepareA2UIMessage = ({ imageMessage, bloksWidget = {}, nativeFlowMessage, fallback = '', ...interactiveFields } = {}) => ({
|
|
630
|
+
interactiveMessage: {
|
|
631
|
+
...interactiveFields,
|
|
632
|
+
...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
|
|
633
|
+
bloksWidget: {
|
|
634
|
+
type: bloksWidget.type || 'im_a2ui',
|
|
635
|
+
data: typeof bloksWidget.data === 'string' ? bloksWidget.data : JSON.stringify(bloksWidget.data || {}),
|
|
636
|
+
...(bloksWidget.fallback || fallback ? { fallback: String(bloksWidget.fallback || fallback) } : {})
|
|
637
|
+
},
|
|
638
|
+
...(nativeFlowMessage ? {
|
|
639
|
+
nativeFlowMessage: {
|
|
640
|
+
...nativeFlowMessage,
|
|
641
|
+
...(nativeFlowMessage.buttons ? { buttons: nativeFlowMessage.buttons } : {})
|
|
642
|
+
}
|
|
643
|
+
} : {})
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
503
647
|
/** Build the A2UI interactive command menu used by the full-size card layout. */
|
|
504
648
|
export const prepareA2UICommandMenu = ({ imageMessage, title = 'CODY AI', rows = [], buttons = [], fallback = '' } = {}) => {
|
|
505
649
|
if (!Array.isArray(rows)) throw new Error('A2UI command-menu rows must be an array');
|
|
@@ -790,26 +934,18 @@ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata }
|
|
|
790
934
|
return buildImaginePrimitive({ mediaType: 'video', url, thumbnail: video?.thumbnail || video?.thumbnailUrl, mimeType: video?.mimeType || video?.mime_type || 'video/mp4', fileLength: video?.fileLength || video?.file_length || 0, duration: video?.duration || 0, status: 'READY' });
|
|
791
935
|
}
|
|
792
936
|
const image = item?.image || item?.data || item;
|
|
793
|
-
const sourceUrl = typeof image === 'string' ? image : (
|
|
937
|
+
const sourceUrl = typeof image === 'string' ? image : firstImageValue(
|
|
938
|
+
image?.imageHighResUrl, image?.highResUrl, image?.fullUrl,
|
|
939
|
+
image?.imagePreviewUrl, image?.previewUrl, image?.sourceUrl, image?.url
|
|
940
|
+
) || '';
|
|
794
941
|
if (!sourceUrl) throw new Error('Each image album item requires an image URL');
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
const width = image?.width || item?.width || 600;
|
|
798
|
-
const height = image?.height || item?.height || 400;
|
|
799
|
-
const mimeType = image?.mimeType || image?.mime_type || 'image/png';
|
|
800
|
-
return {
|
|
801
|
-
preview_image: { url: previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
802
|
-
full_image: { url: fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
803
|
-
dark_mode_preview_image: { url: image?.darkModePreviewUrl || previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
804
|
-
dark_mode_full_image: { url: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
805
|
-
asset_query_status: 'FETCHED',
|
|
806
|
-
__typename: 'GenAIImagePrimitive'
|
|
807
|
-
};
|
|
942
|
+
const primitive = buildRichImagePrimitive({ ...image, width: image?.width || item?.width, height: image?.height || item?.height }, expiration);
|
|
943
|
+
return { ...primitive, display_mode: 'FULL_WIDTH', full_width: true };
|
|
808
944
|
});
|
|
809
945
|
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
810
946
|
submessages: [],
|
|
811
947
|
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
812
|
-
unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections: [{ view_model: { primitives, __typename: 'GenAIVStackLayoutViewModel' } }] })) },
|
|
948
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections: [{ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }] })) },
|
|
813
949
|
contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
|
|
814
950
|
});
|
|
815
951
|
return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
|
|
@@ -873,6 +1009,107 @@ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } =
|
|
|
873
1009
|
return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
|
|
874
1010
|
};
|
|
875
1011
|
|
|
1012
|
+
/**
|
|
1013
|
+
* Builds a compact social-profile card used by WhatsApp's unified response
|
|
1014
|
+
* renderer. The summary is an inline social entity, not ordinary plain text.
|
|
1015
|
+
*/
|
|
1016
|
+
export const prepareSocialProfileMessage = (options = {}) => {
|
|
1017
|
+
const {
|
|
1018
|
+
username,
|
|
1019
|
+
platform = 'instagram',
|
|
1020
|
+
title = 'CODY AI',
|
|
1021
|
+
subtitle = '⚉',
|
|
1022
|
+
imageUrl = '',
|
|
1023
|
+
entityId = username,
|
|
1024
|
+
entityUrl = `https://www.${platform}.com/${username || ''}`,
|
|
1025
|
+
entityType = platform === 'instagram' ? 'IG_PROFILE' : `${String(platform).toUpperCase()}_PROFILE`,
|
|
1026
|
+
fullName = 'NOT found',
|
|
1027
|
+
isVerified = false,
|
|
1028
|
+
resultText = 'See results'
|
|
1029
|
+
} = options;
|
|
1030
|
+
if (!username || typeof username !== 'string') {
|
|
1031
|
+
throw new Boom('sendSocialProfile requires a username', { statusCode: 400 });
|
|
1032
|
+
}
|
|
1033
|
+
const image = imageUrl ? { url: imageUrl, mime_type: 'image/png' } : undefined;
|
|
1034
|
+
const sections = [
|
|
1035
|
+
{
|
|
1036
|
+
view_model: {
|
|
1037
|
+
primitives: [{
|
|
1038
|
+
title,
|
|
1039
|
+
subtitle,
|
|
1040
|
+
...(image ? { image } : {}),
|
|
1041
|
+
entity_id: String(entityId || username),
|
|
1042
|
+
entity_url: entityUrl,
|
|
1043
|
+
entity_type: 'WEBSITE',
|
|
1044
|
+
action_type: 'OPEN_URL',
|
|
1045
|
+
is_verified: Boolean(isVerified),
|
|
1046
|
+
__typename: 'GenAICompactEntityPrimitive'
|
|
1047
|
+
}],
|
|
1048
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
view_model: {
|
|
1053
|
+
primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }],
|
|
1054
|
+
__typename: 'GenAIVStackLayoutViewModel'
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
view_model: {
|
|
1059
|
+
primitives: [
|
|
1060
|
+
{ __typename: 'GenAISpacerPrimitive' },
|
|
1061
|
+
{
|
|
1062
|
+
text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `,
|
|
1063
|
+
inline_entities: [{
|
|
1064
|
+
key: 'social_entity_1',
|
|
1065
|
+
metadata: {
|
|
1066
|
+
__typename: 'GenAISocialEntityItem',
|
|
1067
|
+
entity_id: username,
|
|
1068
|
+
entity_name: username,
|
|
1069
|
+
entity_full_name: fullName,
|
|
1070
|
+
platform: String(platform || 'GENERIC').toUpperCase(),
|
|
1071
|
+
entity_picture_url: imageUrl,
|
|
1072
|
+
entity_url: entityUrl,
|
|
1073
|
+
entity_type: entityType,
|
|
1074
|
+
is_verified: Boolean(isVerified)
|
|
1075
|
+
}
|
|
1076
|
+
}],
|
|
1077
|
+
__typename: 'GenAIMarkdownTextUXPrimitive'
|
|
1078
|
+
},
|
|
1079
|
+
{ __typename: 'GenAISpacerPrimitive' }
|
|
1080
|
+
],
|
|
1081
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
];
|
|
1085
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
1086
|
+
submessages: [],
|
|
1087
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
1088
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ sections })) },
|
|
1089
|
+
contextInfo: { isForwarded: true, forwardOrigin: 4 }
|
|
1090
|
+
});
|
|
1091
|
+
return wrapToBotForwardedMessage(richResponseMessage);
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Builds a generic unified-response envelope without reducing its sections to
|
|
1097
|
+
* plain text. This is the extension point for newer WhatsApp primitives that
|
|
1098
|
+
* are present in WAProto but do not yet have a dedicated convenience helper.
|
|
1099
|
+
*/
|
|
1100
|
+
export const prepareUnifiedResponseMessage = ({ sections = [], submessages = [], responseId = randomUUID(), contextInfo } = {}) => {
|
|
1101
|
+
if (!Array.isArray(sections) || sections.length === 0) {
|
|
1102
|
+
throw new Boom('sendUnifiedResponse requires at least one section', { statusCode: 400 });
|
|
1103
|
+
}
|
|
1104
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
1105
|
+
submessages,
|
|
1106
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
1107
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) },
|
|
1108
|
+
contextInfo: contextInfo || { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
|
|
1109
|
+
});
|
|
1110
|
+
return wrapToBotForwardedMessage(richResponseMessage);
|
|
1111
|
+
};
|
|
1112
|
+
|
|
876
1113
|
export const prepareRichResponseMessage = (content) => {
|
|
877
1114
|
const { alignment, code, contentText, disclaimerText, footerText, headerText, imageText, inlineImage, inlineVideo, items, language, latex, links, noDonation, noHeading, posts, products, reels, sources: sourceInputs, suggested, richResponse, table, tapLinkUrl, title, verificationMetadata } = content;
|
|
878
1115
|
let submessages = [];
|