@codexverified/baileys 2.14.14 β†’ 2.17.18

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.
@@ -12,12 +12,14 @@ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinar
12
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
13
13
  import { captureUnifiedResponse, generateButtonGridContent, generateCodeBlockContent, generateLatexContent, generateListContent, generateRichMenuContent, generateTableContent, generateUnifiedResponseContent } from '../Utils/message-composer.js';
14
14
  import { Baron } from './interactive-handler.js';
15
- import { prepareRichResponseMessage, prepareRichGenerationMessage, prepareRichTextMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareHtmlMessage, prepareRichHtmlMessage } from '../Utils/rich-message-utils.js';
15
+ import { prepareRichResponseMessage, prepareRichGenerationMessage, prepareRichTextMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareVideoGridMessage, prepareVerticalAlbumMessage, prepareHtmlMessage, prepareRichHtmlMessage, prepareA2UIMessage, prepareBotPromptSuggestionsMessage, prepareBotPttTranscriptMessage, prepareBotSourcesMessage, prepareInstagramProfileMessage, prepareRichDynamicMediaMessage, prepareRichMapMessage, prepareSocialEntityMessage } from '../Utils/rich-message-utils.js';
16
16
  import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
17
+ import { generateMiniAppHtml } from '../Utils/games/miniapp.js';
17
18
  import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/website-preview.js';
18
19
  import { isViewOnceMessage, downloadViewOnceMedia, createRegularMessageFromViewOnce, parseViewOnceInfo } from '../Utils/view-once-utils.js';
19
20
  import { collectMetaAIBotParticipantJids, isMetaAIBotResponse, META_AI_BOT_JID } from './aigroups.js';
20
21
  import { makeUsernameSocket } from './username.js';
22
+ import { grepSource } from '../Utils/source-grep.js';
21
23
 
22
24
  const STATUS_JID = 'status@broadcast';
23
25
  export const normalizeStatusJidList = (value) => {
@@ -1242,11 +1244,38 @@ export const makeMessagesSocket = (config) => {
1242
1244
  if (!content.copyText) throw new Boom('sendQuoteCopy requires copyText content', { statusCode: 400 });
1243
1245
  return socket.sendRichTextCopy(jid, content, options);
1244
1246
  },
1247
+ /** Send a full-width stacked AIRich album with one large image/video per row. */
1245
1248
  sendAlbumMessage: async (jid, medias = [], options = {}) => {
1246
1249
  if (!jid) throw new Boom('sendAlbumMessage requires a target JID', { statusCode: 400 });
1247
1250
  if (!Array.isArray(medias) || medias.length < 2) throw new Boom('sendAlbumMessage requires at least two media items', { statusCode: 400 });
1248
- const album = medias.map((media, index) => { const normalized = media?.type && media?.data ? { [media.type]: media.data } : { ...media }; if (index === 0 && options.caption && !normalized.caption) normalized.caption = options.caption; return normalized; });
1249
- return socket.sendMessage(jid, { album }, options);
1251
+ const albumItems = medias.map((media) => {
1252
+ const raw = media?.type && media?.data ? media.data : (media?.image ?? media);
1253
+ if (media?.video || media?.type === 'video') {
1254
+ const video = media?.video || raw;
1255
+ const sourceUrl = typeof video === 'string' ? video.trim() : String(video?.url || video?.videoUrl || '').trim();
1256
+ if (!sourceUrl) throw new Boom('Each video album item requires a video URL', { statusCode: 400 });
1257
+ return { type: 'video', video: { ...video, url: sourceUrl } };
1258
+ }
1259
+ const image = typeof raw === 'string' ? raw.trim() : { ...raw, url: String(raw?.url || '').trim() };
1260
+ if (!image || (typeof image === 'object' && !image.url && !image.sourceUrl)) {
1261
+ throw new Boom('Each image album item requires an image URL', { statusCode: 400 });
1262
+ }
1263
+ return { type: 'image', image };
1264
+ });
1265
+ const wrapped = prepareVerticalAlbumMessage({ items: albumItems, verificationMetadata: options.verificationMetadata });
1266
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1267
+ logger,
1268
+ userJid: authState.creds.me?.id,
1269
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1270
+ ...options
1271
+ });
1272
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
1273
+ if (config.emitOwnEvents) {
1274
+ process.nextTick(async () => {
1275
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1276
+ });
1277
+ }
1278
+ return fullMsg;
1250
1279
  },
1251
1280
  sendBotDiagnostics: async (jid, content = {}, options = {}) => {
1252
1281
  if (!jid) throw new Boom('sendBotDiagnostics requires a target JID', { statusCode: 400 });
@@ -1258,6 +1287,29 @@ export const makeMessagesSocket = (config) => {
1258
1287
  const { text = 'Bot command test', commandName = '', commandDescription = '', commandPrompt = '', botJid = '867051314767696@bot' } = content;
1259
1288
  return socket.sendRichGeneration(jid, { text, botMetadata: { commandMetadata: { commandName, commandDescription, commandPrompt } }, botJid }, undefined, options);
1260
1289
  },
1290
+ /** Sends a planning/execution-plan rich response with WAProto capabilities. */
1291
+ sendBotPlanning: async (jid, content = {}, options = {}) => {
1292
+ if (!jid) throw new Boom('sendBotPlanning requires a target JID', { statusCode: 400 });
1293
+ const { text = 'Execution plan', steps = [], queryPlan = true, botJid = '867051314767696@bot' } = content;
1294
+ const submessages = [
1295
+ { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text },
1296
+ ...steps.map(step => ({
1297
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
1298
+ messageText: typeof step === 'string' ? step : `${step.title || step.name || 'Step'}${step.body ? `: ${step.body}` : ''}`
1299
+ }))
1300
+ ];
1301
+ const wrapped = buildBotRichResponse({
1302
+ botJid,
1303
+ submessages,
1304
+ capabilities: [
1305
+ BotCapabilityType.AGENTIC_PLANNING,
1306
+ ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])
1307
+ ]
1308
+ });
1309
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
1310
+ await relayRichMessage(jid, fullMsg, options);
1311
+ return fullMsg;
1312
+ },
1261
1313
  sendBotToolResult: async (jid, content = {}, options = {}) => {
1262
1314
  if (!jid) throw new Boom('sendBotToolResult requires a target JID', { statusCode: 400 });
1263
1315
  const { text = 'Bot tool result test', toolCallId = 'tool-' + Date.now(), resolutionData, resolutionDataSerialized, botJid = '867051314767696@bot' } = content;
@@ -1270,6 +1322,153 @@ export const makeMessagesSocket = (config) => {
1270
1322
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
1271
1323
  return fullMsg;
1272
1324
  },
1325
+ sendA2UIMessage: async (jid, content = {}, options = {}) => {
1326
+ if (!jid) throw new Boom('sendA2UIMessage requires a target JID', { statusCode: 400 });
1327
+ const { image, ...payload } = content || {};
1328
+ let imageMessage;
1329
+ if (image) {
1330
+ const prepared = await prepareWAMessageMedia({ image }, { upload: waUploadToServer });
1331
+ imageMessage = prepared.imageMessage;
1332
+ }
1333
+ const wrapped = prepareA2UIMessage({ ...payload, imageMessage });
1334
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1335
+ logger,
1336
+ userJid: authState.creds.me?.id,
1337
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1338
+ ...options,
1339
+ });
1340
+ await relayRichMessage(jid, fullMsg, { ...options, forceRichEdit: true });
1341
+ if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
1342
+ return fullMsg;
1343
+ },
1344
+ sendBotPromptSuggestions: async (jid, content = {}, options = {}) => {
1345
+ if (!jid) throw new Boom('sendBotPromptSuggestions requires a target JID', { statusCode: 400 });
1346
+ const wrapped = prepareBotPromptSuggestionsMessage(content);
1347
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1348
+ logger,
1349
+ userJid: authState.creds.me?.id,
1350
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
1351
+ ...options
1352
+ });
1353
+ await relayRichMessage(jid, fullMsg, options);
1354
+ return fullMsg;
1355
+ },
1356
+ sendBotPttTranscript: async (jid, content = {}, options = {}) => {
1357
+ if (!jid) throw new Boom('sendBotPttTranscript requires a target JID', { statusCode: 400 });
1358
+ const wrapped = prepareBotPttTranscriptMessage(content);
1359
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1360
+ logger,
1361
+ userJid: authState.creds.me?.id,
1362
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
1363
+ ...options
1364
+ });
1365
+ await relayRichMessage(jid, fullMsg, options);
1366
+ return fullMsg;
1367
+ },
1368
+ sendBotSources: async (jid, content = {}, options = {}) => {
1369
+ if (!jid) throw new Boom('sendBotSources requires a target JID', { statusCode: 400 });
1370
+ const wrapped = prepareBotSourcesMessage(content);
1371
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1372
+ logger,
1373
+ userJid: authState.creds.me?.id,
1374
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
1375
+ ...options
1376
+ });
1377
+ await relayRichMessage(jid, fullMsg, options);
1378
+ return fullMsg;
1379
+ },
1380
+ sendInstagramProfile: async (jid, content = {}, options = {}) => {
1381
+ if (!jid) throw new Boom('sendInstagramProfile requires a target JID', { statusCode: 400 });
1382
+ const wrapped = prepareInstagramProfileMessage(content);
1383
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1384
+ logger,
1385
+ userJid: authState.creds.me?.id,
1386
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1387
+ ...options
1388
+ });
1389
+ await relayRichMessage(jid, fullMsg, options);
1390
+ if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
1391
+ return fullMsg;
1392
+ },
1393
+ sendRichDynamicMedia: async (jid, content = {}, options = {}) => {
1394
+ if (!jid) throw new Boom('sendRichDynamicMedia requires a target JID', { statusCode: 400 });
1395
+ const wrapped = prepareRichDynamicMediaMessage(content);
1396
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1397
+ logger,
1398
+ userJid: authState.creds.me?.id,
1399
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
1400
+ ...options
1401
+ });
1402
+ await relayRichMessage(jid, fullMsg, options);
1403
+ return fullMsg;
1404
+ },
1405
+ sendRichMap: async (jid, content = {}, options = {}) => {
1406
+ if (!jid) throw new Boom('sendRichMap requires a target JID', { statusCode: 400 });
1407
+ const wrapped = prepareRichMapMessage(content);
1408
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1409
+ logger,
1410
+ userJid: authState.creds.me?.id,
1411
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
1412
+ ...options
1413
+ });
1414
+ await relayRichMessage(jid, fullMsg, options);
1415
+ return fullMsg;
1416
+ },
1417
+ sendSocialEntity: async (jid, content = {}, options = {}) => {
1418
+ if (!jid) throw new Boom('sendSocialEntity requires a target JID', { statusCode: 400 });
1419
+ const wrapped = prepareSocialEntityMessage(content);
1420
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1421
+ logger,
1422
+ userJid: authState.creds.me?.id,
1423
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1424
+ ...options,
1425
+ });
1426
+ await relayRichMessage(jid, fullMsg, options);
1427
+ if (config.emitOwnEvents) process.nextTick(async () => { await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')); });
1428
+ return fullMsg;
1429
+ },
1430
+ /** Send the large A2UI command menu with an uploaded image header, rows, and URL buttons. */
1431
+ /** Sends the manually serialized AIRich command card shown in the screenshot. */
1432
+ sendAIRichCommandMenu: async (jid, content = {}, options = {}) => {
1433
+ if (!jid) throw new Boom('sendAIRichCommandMenu requires a target JID', { statusCode: 400 });
1434
+ const wrapped = prepareAIRichCommandMenu(content);
1435
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1436
+ logger,
1437
+ userJid: authState.creds.me?.id,
1438
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1439
+ ...options,
1440
+ });
1441
+ await relayRichMessage(jid, fullMsg, options);
1442
+ if (config.emitOwnEvents) {
1443
+ process.nextTick(async () => {
1444
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1445
+ });
1446
+ }
1447
+ return fullMsg;
1448
+ },
1449
+ sendA2UICommandMenu: async (jid, content = {}, options = {}) => {
1450
+ if (!jid) throw new Boom('sendA2UICommandMenu requires a target JID', { statusCode: 400 });
1451
+ const { image, ...menuContent } = content || {};
1452
+ let imageMessage;
1453
+ if (image) {
1454
+ const prepared = await prepareWAMessageMedia({ image }, { upload: waUploadToServer });
1455
+ imageMessage = prepared.imageMessage;
1456
+ }
1457
+ const wrapped = prepareA2UICommandMenu({ ...menuContent, imageMessage });
1458
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1459
+ logger,
1460
+ userJid: authState.creds.me?.id,
1461
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1462
+ ...options
1463
+ });
1464
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options.relayOptions });
1465
+ if (config.emitOwnEvents) {
1466
+ process.nextTick(async () => {
1467
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1468
+ });
1469
+ }
1470
+ return fullMsg;
1471
+ },
1273
1472
  sendRichButtonGrid: async (jid, grid, options = {}) => {
1274
1473
  const message = generateButtonGridContent(grid, options.quoted, options);
1275
1474
  const fullMsg = await generateWAMessageFromContent(jid, message, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
@@ -1289,6 +1488,8 @@ export const makeMessagesSocket = (config) => {
1289
1488
  const lines = [title, headers.length ? `| ${headers.map(String).join(' | ')} |` : '', headers.length ? `| ${headers.map(() => '---').join(' | ')} |` : '', ...rows.map(row => `| ${(Array.isArray(row) ? row : [row]).map(value => String(value ?? '')).join(' | ')} |`)].filter(Boolean);
1290
1489
  return sock.sendMessage(jid, { ...(image ? { image, caption: lines.join('\\n') } : { text: lines.join('\\n') }), buttons, ...(footer ? { footer } : {}) }, options);
1291
1490
  },
1491
+ /** Compatibility alias for the former Gen4/grid menu entry point. */
1492
+ HoldOn: async (jid, grid, options = {}) => socket.sendRichButtonGrid(jid, grid, options),
1292
1493
  sendWhatsAppFlow: async (jid, flow = {}, options = {}) => {
1293
1494
  const { text, footer, image, caption, flowId, flowToken, flowName = 'flow', flowAction = 'navigate', flowActionPayload = {} } = flow;
1294
1495
  if (!flowId || !flowToken) throw new Boom('sendWhatsAppFlow requires flowId and flowToken', { statusCode: 400 });
@@ -1313,6 +1514,38 @@ export const makeMessagesSocket = (config) => {
1313
1514
  await relayMessage(jid, message, { messageId });
1314
1515
  return { message, messageId };
1315
1516
  },
1517
+ sendCarousel: async (jid, cards, options = {}) => {
1518
+ if (!Array.isArray(cards) || cards.length === 0) {
1519
+ throw new Boom('sendCarousel requires a non-empty cards array', { statusCode: 400 });
1520
+ }
1521
+ const normalizedCards = cards.map(card => ({
1522
+ ...card,
1523
+ text: card.text ?? card.body?.text,
1524
+ caption: card.caption ?? card.body?.text,
1525
+ footer: card.footer?.text ?? card.footer,
1526
+ buttons: card.buttons ?? card.nativeFlowMessage?.buttons,
1527
+ ...(card.media?.image ? { image: card.media.image } : {}),
1528
+ ...(card.media?.video ? { video: card.media.video } : {}),
1529
+ ...(card.media?.document ? { document: card.media.document } : {})
1530
+ }));
1531
+ const { text, footer, ...messageOptions } = options;
1532
+ const fullMsg = await generateWAMessage(jid, {
1533
+ ...messageOptions,
1534
+ cards: normalizedCards,
1535
+ ...(text !== undefined ? { text } : {}),
1536
+ ...(footer !== undefined ? { footer } : {})
1537
+ }, {
1538
+ logger,
1539
+ userJid: authState.creds.me?.id,
1540
+ upload: waUploadToServer,
1541
+ mediaCache: config.mediaCache,
1542
+ options: config.options,
1543
+ ...options,
1544
+ messageId: options.messageId || generateMessageIDV2(jid)
1545
+ });
1546
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
1547
+ return fullMsg;
1548
+ },
1316
1549
  sendLatex: async (jid, quoted, options) => {
1317
1550
  const { message, messageId } = generateLatexContent(quoted, options);
1318
1551
  await relayMessage(jid, message, { messageId });
@@ -1519,6 +1752,31 @@ export const makeMessagesSocket = (config) => {
1519
1752
  await relayMessage(jid, message, { messageId });
1520
1753
  return { message, messageId };
1521
1754
  },
1755
+ /** Send one tolerant AIRich response containing every supported section supplied in content. */
1756
+ sendCodexMessage: async (jid, content = {}, quoted, options = {}) => {
1757
+ if (!jid) throw new Boom('sendCodexMessage requires a target JID', { statusCode: 400 });
1758
+ if (!content || typeof content !== 'object' || Array.isArray(content)) {
1759
+ throw new Boom('sendCodexMessage content must be an object', { statusCode: 400 });
1760
+ }
1761
+ const message = prepareRichResponseMessage({
1762
+ ...content,
1763
+ noDonation: options.noDonation ?? true,
1764
+ });
1765
+ const fullMsg = generateWAMessageFromContent(jid, message, {
1766
+ logger,
1767
+ userJid: authState.creds.me?.id,
1768
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1769
+ quoted,
1770
+ ...options,
1771
+ });
1772
+ await relayMessage(jid, fullMsg, options);
1773
+ if (config.emitOwnEvents) {
1774
+ process.nextTick(async () => {
1775
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1776
+ });
1777
+ }
1778
+ return fullMsg;
1779
+ },
1522
1780
  captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted) => {
1523
1781
  const captured = captureUnifiedResponse(metaAiMsg);
1524
1782
  if (!captured) {
@@ -2259,6 +2517,35 @@ export const makeMessagesSocket = (config) => {
2259
2517
  return { message, messageId };
2260
2518
  };
2261
2519
 
2520
+ /**
2521
+ * Sends a unified-response video grid. This uses GenAIVideoPrimitive items
2522
+ * inside GenAIGridLayoutViewModel; it is not the image-only GRID_IMAGE type.
2523
+ */
2524
+ socket.sendVideoGridMessage = async (jid, videos = [], options = {}) => {
2525
+ if (!jid) throw new Boom('sendVideoGridMessage requires a target JID', { statusCode: 400 });
2526
+ if (!Array.isArray(videos) || videos.length < 2) {
2527
+ throw new Boom('sendVideoGridMessage requires at least two videos', { statusCode: 400 });
2528
+ }
2529
+ const wrapped = prepareVideoGridMessage({
2530
+ videos,
2531
+ verificationMetadata: options.verificationMetadata
2532
+ });
2533
+ const fullMsg = generateWAMessageFromContent(jid, wrapped, {
2534
+ logger,
2535
+ userJid: authState.creds.me?.id,
2536
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
2537
+ ...options
2538
+ });
2539
+ await relayRichMessage(jid, fullMsg, options);
2540
+ if (config.emitOwnEvents) {
2541
+ process.nextTick(async () => {
2542
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
2543
+ });
2544
+ }
2545
+ return fullMsg;
2546
+ };
2547
+
2548
+
2262
2549
  socket.sendHtmlMessage = async (jid, content = {}, options = {}) => {
2263
2550
  if (!jid) throw new Boom('sendHtmlMessage requires a target JID', { statusCode: 400 });
2264
2551
  if (!content.html) throw new Boom('sendHtmlMessage requires html content', { statusCode: 400 });
@@ -2276,10 +2563,23 @@ export const makeMessagesSocket = (config) => {
2276
2563
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2277
2564
  return fullMsg;
2278
2565
  };
2566
+ /** Find a word or expression in the installed package source. */
2567
+ socket.grepCode = (pattern, options = {}) => grepSource(pattern, options);
2568
+
2279
2569
  socket.sendSlotMachine = async (jid, options = {}, sendOptions = {}) => {
2280
2570
  return socket.sendHtmlMessage(jid, { html: generateSlotMachineHtml(options) }, sendOptions);
2281
2571
  };
2282
2572
 
2573
+ /** Send the built-in interactive Codex mini-app through the rich HTML envelope. */
2574
+ socket.sendMiniApp = async (jid, options = {}, sendOptions = {}) => {
2575
+ if (!jid) throw new Boom('sendMiniApp requires a target JID', { statusCode: 400 });
2576
+ return socket.sendRichHtmlMessage(jid, {
2577
+ html: generateMiniAppHtml(options),
2578
+ title: options.title || 'mini-app',
2579
+ url: 'https://codex-ai.site',
2580
+ trustedSources: ['codex-ai.site']
2581
+ }, sendOptions);
2582
+ };
2283
2583
  socket.sendWebsitePreview = async (jid, url, options = {}, sendOptions = {}) => {
2284
2584
  if (!url) throw new Boom('sendWebsitePreview requires a URL', { statusCode: 400 });
2285
2585
  return socket.sendHtmlMessage(jid, { html: await generateWebsitePreviewHtml(url, options) }, sendOptions);
@@ -1,3 +1,4 @@
1
+ import { Boom } from '@hapi/boom';
1
2
  import { proto } from '../../WAProto/index.js';
2
3
  import { QueryIds, XWAPaths } from '../Types/index.js';
3
4
  import { generateProfilePicture } from '../Utils/messages-media.js';
@@ -34,6 +35,15 @@ const parseNewsletterMetadata = (result) => {
34
35
  }
35
36
  return null;
36
37
  };
38
+ const normalizeNewsletterInviteCode = (value) => {
39
+ if (typeof value !== 'string' || !value.trim()) {
40
+ throw new Boom('newsletterGetInviteInfo requires a non-empty invite code or URL', { statusCode: 400 });
41
+ }
42
+ return value.trim()
43
+ .replace(/^https?:\/\/(?:www\.)?whatsapp\.com\/channel\//i, '')
44
+ .replace(/^https?:\/\/wa\.me\/channel\//i, '')
45
+ .split(/[/?#]/, 1)[0];
46
+ };
37
47
  export const makeNewsletterSocket = (config) => {
38
48
  const sock = makeAIGroupsSocket(config);
39
49
  const { query, generateMessageTag } = sock;
@@ -84,6 +94,14 @@ export const makeNewsletterSocket = (config) => {
84
94
  const result = await executeWMexQuery(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata);
85
95
  return parseNewsletterMetadata(result);
86
96
  },
97
+ newsletterGetInviteInfo: async (inviteCodeOrUrl) => {
98
+ const inviteCode = normalizeNewsletterInviteCode(inviteCodeOrUrl);
99
+ return executeWMexQuery(
100
+ { input: { key: inviteCode, type: 'INVITE' }, fetch_creation_time: true, fetch_full_image: true, fetch_viewer_metadata: true },
101
+ QueryIds.METADATA,
102
+ XWAPaths.xwa2_newsletter_metadata
103
+ ).then(parseNewsletterMetadata);
104
+ },
87
105
  newsletterFollow: (jid) => {
88
106
  return executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2);
89
107
  },
@@ -224,3 +242,4 @@ export const makeNewsletterSocket = (config) => {
224
242
  }
225
243
  };
226
244
  };
245
+
@@ -0,0 +1,84 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+
3
+ /**
4
+ * Numeric capability values exposed by WAProto.BotCapabilityMetadata.
5
+ * Keep this derived from the generated protobuf so callers cannot drift from
6
+ * the checked-in schema.
7
+ */
8
+ export const BotCapabilityType = proto.BotCapabilityMetadata.BotCapabilityType;
9
+
10
+ const capabilityNames = new Set(
11
+ Object.keys(BotCapabilityType).filter(name => Number.isNaN(Number(name)))
12
+ );
13
+
14
+ /** Normalize enum names/numbers and reject unknown capability values. */
15
+ export const normalizeBotCapabilities = (capabilities = []) => {
16
+ if (!Array.isArray(capabilities)) {
17
+ throw new TypeError('capabilities must be an array');
18
+ }
19
+ const values = capabilities.map(capability => {
20
+ if (typeof capability === 'string') {
21
+ if (!capabilityNames.has(capability)) {
22
+ throw new TypeError(`Unknown bot capability: ${capability}`);
23
+ }
24
+ return BotCapabilityType[capability];
25
+ }
26
+ if (Number.isInteger(capability) && BotCapabilityType[capability] !== undefined) {
27
+ return capability;
28
+ }
29
+ throw new TypeError(`Invalid bot capability: ${String(capability)}`);
30
+ });
31
+ return [...new Set(values)];
32
+ };
33
+
34
+ export const buildBotCapabilityMetadata = (capabilities = []) => ({
35
+ capabilities: normalizeBotCapabilities(capabilities)
36
+ });
37
+
38
+ /**
39
+ * Build the canonical wrapper used by WhatsApp's bot rich-response messages.
40
+ * The capability list is deliberately explicit; unknown or duplicate values
41
+ * are never put on the wire.
42
+ */
43
+ export const buildBotRichResponse = ({
44
+ text,
45
+ submessages,
46
+ capabilities = [],
47
+ botMetadata = {},
48
+ botJid = '867051314767696@bot',
49
+ richResponse = {}
50
+ } = {}) => {
51
+ const resolvedSubmessages = submessages || (text == null ? [] : [{
52
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
53
+ messageText: String(text)
54
+ }]);
55
+ return {
56
+ messageContextInfo: {
57
+ botMetadata: {
58
+ ...botMetadata,
59
+ capabilityMetadata: buildBotCapabilityMetadata(capabilities)
60
+ }
61
+ },
62
+ botForwardedMessage: {
63
+ message: {
64
+ richResponseMessage: {
65
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
66
+ submessages: resolvedSubmessages,
67
+ ...richResponse,
68
+ contextInfo: {
69
+ isForwarded: true,
70
+ forwardingScore: 1,
71
+ forwardedAiBotMessageInfo: { botJid },
72
+ forwardOrigin: 4,
73
+ ...(richResponse.contextInfo || {})
74
+ }
75
+ }
76
+ }
77
+ }
78
+ };
79
+ };
80
+
81
+ export const capability = (name) => {
82
+ if (!capabilityNames.has(name)) throw new TypeError(`Unknown bot capability: ${name}`);
83
+ return BotCapabilityType[name];
84
+ };
@@ -0,0 +1,5 @@
1
+ /** Generate the attached interactive mini-app HTML game. */
2
+ export const generateMiniAppHtml = (options = {}) => {
3
+ const html = "<style>*{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}</style>\n<body style=\"margin:0;background:transparent;font-family:Arial,sans-serif;color:#eee;touch-action:manipulation;cursor:pointer\">\n<div style=\"width:100%;max-width:620px;margin:auto;padding:16px;box-sizing:border-box\">\n<div style=\"background:rgba(255,255,255,.06);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.15);border-radius:16px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,.35)\">\n<div style=\"padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.12);display:flex;justify-content:space-between;align-items:center\">\n<div><div style=\"font-size:11px;letter-spacing:1.5px;color:rgba(255,255,255,.45)\">π‘Άπ’“π’Šπ’ˆπ’Šπ’βœ¦</div><div style=\"font-size:21px;font-weight:bold;color:#fff\">mini-app</div></div>\n<div style=\"text-align:right\"><div id=\"score\" style=\"font-size:18px;font-weight:bold;color:#fff;text-shadow:0 0 10px rgba(108,92,231,.85);transition:transform .15s\">00000</div><div id=\"best\" style=\"font-size:10px;color:rgba(255,255,255,.4);margin-top:2px\">BEST 00000</div></div>\n</div>\n<div style=\"padding:18px\">\n<canvas id=\"game\" width=\"560\" height=\"190\" style=\"width:100%;height:auto;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.12);border-radius:12px;display:block\"></canvas>\n<div id=\"status\" style=\"text-align:center;margin-top:10px;font-size:12px;color:rgba(255,255,255,.55)\">VelocitΓ  5.0x</div>\n</div></div></div>\n<script>\nconst c=document.getElementById('game'),x=c.getContext('2d'),scoreEl=document.getElementById('score'),bestEl=document.getElementById('best'),statusEl=document.getElementById('status');\nconst GY=170;\nlet d,o,clouds,particles,ambient,trail,score,best=0,speed,gameOver,last,shake,flash,runT,spawnTimer,milestone,squash;\nfunction loadBest(){\nlet vals=[];\ntry{let v=localStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let v=sessionStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let m=document.cookie.match(/(?:^|;\\s*)dino_best=(\\d+)/);if(m)vals.push(parseInt(m[1],10))}catch(e){}\nreturn vals.length?Math.max(...vals.filter(v=>!isNaN(v))):0\n}\nfunction saveBest(v){\nlet val=String(Math.floor(v));\ntry{localStorage.setItem('dino_best',val)}catch(e){}\ntry{sessionStorage.setItem('dino_best',val)}catch(e){}\ntry{document.cookie='dino_best='+val+';max-age=31536000;path=/'}catch(e){}\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{try{rq.result.transaction('kv','readwrite').objectStore('kv').put(val,'dino_best')}catch(e){}}\n}catch(e){}\n}\nfunction loadBestAsync(cb){\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{\ntry{\nlet gr=rq.result.transaction('kv','readonly').objectStore('kv').get('dino_best');\ngr.onsuccess=()=>{if(gr.result)cb(parseInt(gr.result,10))}\n}catch(e){}\n}\n}catch(e){}\n}\nbest=loadBest();\nloadBestAsync(v=>{if(!isNaN(v)&&v>best){best=v;bestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0')}});\nfunction reset(){\nd={x:55,y:132,w:27,h:30,vy:0,jumping:false};\no=[];\nclouds=[{x:120,y:32,w:44,s:.35},{x:300,y:52,w:60,s:.22},{x:460,y:26,w:36,s:.4},{x:560,y:70,w:50,s:.18}];\nparticles=[];\ntrail=[];\nif(!ambient){ambient=[];for(let i=0;i<18;i++)ambient.push({x:Math.random()*c.width,y:Math.random()*c.height,r:.5+Math.random()*1.5,vx:.1+Math.random()*.3,ph:Math.random()*10})}\nscore=0;speed=5;gameOver=false;last=0;shake=0;flash=0;runT=0;milestone=0;squash=1;\nspawnTimer=70+Math.random()*30;\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed 5.0x'\n}\nfunction burst(px,py,n,col,spd){for(let i=0;i<n;i++)particles.push({x:px,y:py,vx:(Math.random()-.5)*spd,vy:-Math.random()*spd,life:1,col,size:2+Math.random()*2})}\nfunction jumpDino(){\nif(gameOver){reset();return}\nif(!d.jumping){d.jumping=true;d.vy=-13;squash=.7;burst(d.x+13,d.y+30,10,'255,255,255',4)}\n}\nfunction cactus(){\nlet h=24+Math.random()*24;\no.push({x:c.width+20,y:GY-h,w:16+Math.random()*6,h});\nif(Math.random()<.22){o.push({x:c.width+20+34+Math.random()*10,y:GY-(20+Math.random()*18),w:16,h:20+Math.random()*18})}\n}\nfunction hit(a,b){return a.x+4<b.x+b.w&&a.x+a.w-4>b.x&&a.y+4<b.y+b.h&&a.y+a.h>b.y}\nfunction drawTrail(){\ntrail.forEach((p,i)=>{x.fillStyle='rgba(108,92,231,'+(.25*(i/trail.length))+')';x.fillRect(p.x,p.y,27,30)})\n}\nfunction drawDino(){\nx.save();\nlet cx=d.x+13,cy=d.y+30;\nx.translate(cx,cy);\nx.scale(1/squash,squash);\nx.translate(-cx,-cy);\nlet legOff=d.jumping?0:Math.sin(runT*.5)*5;\nx.fillStyle='#eaeaea';\nx.fillRect(d.x,d.y,27,30);\nx.fillRect(d.x+22,d.y+5,13,18);\nx.fillStyle='#6c5ce7';\nx.fillRect(d.x+29,d.y+8,4,4);\nx.fillStyle='#eaeaea';\nx.fillRect(d.x+5,d.y+30,6,8+legOff);\nx.fillRect(d.x+20,d.y+30,6,8-legOff);\nx.restore()\n}\nfunction drawCactus(q){\nx.save();\nx.shadowColor='rgba(255,90,90,.35)';x.shadowBlur=10;\nx.fillStyle='#e17a7a';\nx.fillRect(q.x,q.y,q.w,q.h);\nx.fillRect(q.x-7,q.y+10,7,6);\nx.fillRect(q.x-7,q.y+4,6,12);\nx.fillRect(q.x+q.w,q.y+18,7,6);\nx.fillRect(q.x+q.w+1,q.y+12,6,12);\nx.restore()\n}\nfunction drawParticles(){\nparticles.forEach(p=>{x.fillStyle='rgba('+p.col+','+Math.max(p.life,0)+')';x.fillRect(p.x,p.y,p.size,p.size)})\n}\nfunction drawAmbient(){\nambient.forEach(p=>{let a=.15+Math.sin(runT*.05+p.ph)*.1;x.fillStyle='rgba(180,160,255,'+a+')';x.beginPath();x.arc(p.x,p.y,p.r,0,7);x.fill()})\n}\nfunction draw(){\nx.clearRect(0,0,c.width,c.height);\nx.save();\nif(shake>0)x.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);\ndrawAmbient();\nx.fillStyle='rgba(255,255,255,.35)';\nclouds.forEach(q=>{let b=Math.sin(runT*.03+q.x)*2;x.fillRect(q.x,q.y+b,q.w,5);x.fillRect(q.x+10,q.y+b-5,q.w*.45,10)});\nx.strokeStyle='rgba(255,255,255,.25)';\nx.lineWidth=2;\nx.setLineDash([10,8]);\nx.lineDashOffset=-runT*speed*.6;\nx.beginPath();x.moveTo(0,GY);x.lineTo(c.width,GY);x.stroke();\nx.setLineDash([]);\ndrawTrail();\ndrawDino();\no.forEach(drawCactus);\ndrawParticles();\nif(flash>0){x.fillStyle='rgba(255,60,60,'+(flash*.35)+')';x.fillRect(0,0,c.width,c.height)}\nx.restore();\nif(gameOver){\nx.fillStyle='rgba(15,15,25,.55)';x.fillRect(0,0,c.width,c.height);\nx.fillStyle='#fff';x.textAlign='center';\nx.font='bold 24px Arial';x.fillText('GAME OVER',c.width/2,85);\nx.font='14px Arial';x.fillText('Tocca per giocare di nuovo',c.width/2,112);\nx.textAlign='left'\n}\n}\nfunction loop(t){\nif(!last)last=t;\nlet dt=Math.min((t-last)/16.67,2);\nlast=t;\nrunT+=dt;\nif(!gameOver){\nd.y+=d.vy*dt;d.vy+=.75*dt;\nif(d.y>=132){\nif(d.jumping){burst(d.x+13,GY,10,'255,255,255',3.5);squash=1.35}\nd.y=132;d.vy=0;d.jumping=false\n}\nif(d.jumping)trail.push({x:d.x,y:d.y});\nif(trail.length>6)trail.shift();\nif(!d.jumping)trail.length=0;\nsquash+=(1-squash)*.18*dt;\nif(!d.jumping&&Math.floor(runT)%8===0&&Math.random()<.4)burst(d.x+6,GY-2,1,'255,255,255',1.5);\nambient.forEach(p=>{p.x-=p.vx*dt;if(p.x<-4)p.x=c.width+4});\nspawnTimer-=dt;\nif(spawnTimer<=0){cactus();spawnTimer=Math.max(38,62-speed*1.4)+Math.random()*30}\no.forEach(q=>q.x-=speed*dt);\no=o.filter(q=>q.x>-40);\nclouds.forEach(q=>{q.x-=q.s*dt;if(q.x<-80)q.x=c.width+Math.random()*100});\nparticles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=.3*dt;p.life-=.03*dt});\nparticles=particles.filter(p=>p.life>0);\nspeed=Math.min(11,speed+.0018*dt);\nscore+=dt*.6;\nif(score>best)best=score;\nif(Math.floor(score/500)>milestone){\nmilestone=Math.floor(score/500);\nscoreEl.style.transform='scale(1.35)';\nsetTimeout(()=>scoreEl.style.transform='scale(1)',150)\n}\nscoreEl.textContent=String(Math.floor(score)).padStart(5,'0');\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed '+speed.toFixed(1)+'x';\nfor(const q of o)if(hit(d,q)){\ngameOver=true;shake=14;flash=1;\nsaveBest(best);\nburst(d.x+13,d.y+15,18,'255,90,90',5)\n}\n}\nif(shake>0)shake=Math.max(0,shake-.6*dt);\nif(flash>0)flash=Math.max(0,flash-.05*dt);\ndraw();\nrequestAnimationFrame(loop)\n}\ndocument.addEventListener('pointerdown',e=>{e.preventDefault();jumpDino()});\ndocument.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();jumpDino()}});\nreset();\nrequestAnimationFrame(loop);\n</script></body>";
4
+ return options.title ? html.replace('mini-app', String(options.title)) : html;
5
+ };
@@ -6,6 +6,7 @@ export * from './messages-media.js';
6
6
  export * from './rich-message-utils.js';
7
7
  export * from './meta-compositing.js';
8
8
  export * from './bot-planning-replay.js';
9
+ export * from './bot-capabilities.js';
9
10
  export * from './welcome-flow.js';
10
11
  export * from './message-composer.js';
11
12
  export * from './consumer-application.js';
@@ -38,3 +39,6 @@ export * from './native-flow.js';
38
39
  export * from './view-once-utils.js';
39
40
  export * from './website-preview.js';
40
41
  export * from './portable-compat.js';
42
+ export * from './games/miniapp.js';
43
+ export { prepareVerticalAlbumMessage } from './rich-message-utils.js';
44
+ export { prepareAIRichCommandMenu, prepareVideoGridMessage } from './rich-message-utils.js';