@codexverified/baileys 2.12.12 → 2.13.13

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 CHANGED
@@ -27,6 +27,8 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
27
27
  - [About Codex](#about-codex)
28
28
  - [Package layout](#package-layout)
29
29
  - [Security and responsible use](#security-and-responsible-use)
30
+ - [Disclaimer](#disclaimer)
31
+ - [Privacy policy](#privacy-policy)
30
32
  - [License and maintenance](#license-and-maintenance)
31
33
  - [Compatibility additions](#compatibility-additions)
32
34
  - [Comparison references](#comparison-references)
@@ -38,7 +40,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
38
40
  | Property | Value |
39
41
  | --- | --- |
40
42
  | Package | `@codexverified/baileys` |
41
- | Version | `2.12.12` |
43
+ | Version | `2.13.13` |
42
44
  | Runtime | Node.js 20 or newer |
43
45
  | Module format | ESM, with CommonJS compatibility where supported |
44
46
  | Type declarations | Included at `lib/index.d.ts` |
@@ -52,7 +54,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
52
54
  | Method | Command | Notes |
53
55
  | --- | --- | --- |
54
56
  | From npm | `npm install @codexverified/baileys` | Standard install when published under your organization |
55
- | From local archive | `npm install ./codex-baileys-2.12.12.tgz` | Place the `.tgz` beside your application |
57
+ | From local archive | `npm install ./codex-baileys-2.13.13.tgz` | Place the `.tgz` beside your application |
56
58
  | Source audit (no scripts) | `npm install --ignore-scripts` | Skips install hooks for local review |
57
59
 
58
60
  Both npm and archive installs use the same import name:
@@ -341,6 +343,59 @@ This library interacts with WhatsApp accounts and user communications.
341
343
  | Transparency | Provide clear notice when an application performs automated actions |
342
344
  | Dependency review | Review installation scripts and optional dependencies before deploying to production; use pinned versions for repeatable builds |
343
345
 
346
+ ## Disclaimer
347
+
348
+ `@codexverified/baileys` is an independent, source-available Node.js library that communicates with WhatsApp using the Baileys protocol. It is not affiliated with, endorsed by, or officially connected to WhatsApp LLC or Meta Platforms, Inc. "WhatsApp" is a trademark of its respective owner, referenced here only for interoperability description.
349
+
350
+ **No malware, no phishing, no hidden network calls.** As a matter of project policy and design:
351
+
352
+ | Statement | Detail |
353
+ | --- | --- |
354
+ | No malicious payloads | The package does not contain, download, execute, or otherwise support malware, spyware, ransomware, keyloggers, or similar malicious code. |
355
+ | No phishing or credential harvesting | The library does not construct, send, or facilitate phishing messages, fake login prompts, or credential-harvesting flows. It provides generic messaging primitives; how an application built on top of them is used is the responsibility of that application's developer. |
356
+ | No covert data exfiltration | The package does not silently transmit your messages, contacts, media, or authentication state to Codex, CodexVerified, or any third party. The only network calls it makes are the ones necessary to connect to WhatsApp's own servers, plus explicitly caller-invoked helpers (such as `checkStatusWA`) that are never triggered automatically. |
357
+ | No obfuscated code | The source is distributed as readable JavaScript/TypeScript with public type declarations, so it can be inspected line by line. Installs can be run with `--ignore-scripts` for an audit-first review, as noted in the [Installation](#installation) table. |
358
+ | Not a guarantee, an invitation to verify | This statement describes the project's design intent and current source code. It is not a substitute for your own review, a third-party security audit, or a legal warranty. You are encouraged to read the source, pin dependency versions, and review any application built on top of this package before deploying it. |
359
+
360
+ ### Developer responsibility and acceptable use
361
+
362
+ This package gives developers general-purpose building blocks for WhatsApp automation (messaging, groups, newsletters/channels, rich responses, and similar features). Like any general-purpose toolkit, those same building blocks can be pointed at abusive uses by a developer who chooses to. The table below names the patterns this project explicitly does **not** endorse, alongside why they're harmful:
363
+
364
+ | Prohibited / discouraged use | Why it's a problem |
365
+ | --- | --- |
366
+ | Auto-following WhatsApp channels/newsletters on behalf of users without their explicit, informed consent | Misrepresents the user's intent, inflates follower counts artificially, and can violate WhatsApp's platform policies |
367
+ | Sending unsolicited "channel pill" style promotional messages, forwarded rich-response spam, or bulk broadcast content to people who did not opt in | Standard spam/harassment; damages recipients' experience and violates most anti-spam and consumer-protection laws |
368
+ | Using the bundled diagnostic helpers (e.g. `checkStatusWA`) for bulk number enumeration or unsolicited scanning of phone numbers | Enables targeting and abuse of people who never opted into any check; the helper's own documentation already flags this |
369
+ | Impersonating WhatsApp, Meta, or another party; phishing; credential harvesting | Deceives end users and is illegal in most jurisdictions |
370
+ | Evading WhatsApp's account-integrity enforcement, terms of service, or rate limits | Undermines platform trust and the safety of the messaging ecosystem for everyone |
371
+
372
+ **Where responsibility sits.** CodexVerified builds and documents the library itself, not the applications developers build with it. If a developer chooses to use this package to do any of the above, **that developer — not CodexVerified or the `@codexverified/baileys` project — is solely responsible for that conduct**, including any legal, financial, or platform consequences that follow from it. The maintainers do not operate, monitor, or have visibility into third-party deployments of this code, and inclusion of a feature in this library is not an endorsement of every way it could be used.
373
+
374
+ This project's own position is the opposite of the misuse above: CodexVerified is built around respecting user privacy and giving developers transparent, inspectable tools — not around helping anyone spam, deceive, or manipulate WhatsApp users. Developers who build on this package are expected to hold themselves to that same standard.
375
+
376
+ **No warranty.** This software is provided "as is," without warranty of any kind, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and non-infringement, to the fullest extent permitted by the license declared in `package.json`. Use of this package to interact with WhatsApp may be subject to WhatsApp's own terms of service, which are outside the maintainers' control.
377
+
378
+ ## Privacy policy
379
+
380
+ This section describes what the package itself does and does not do with data. It does not describe how any particular application built on top of this package handles data — that is determined by the developer of that application.
381
+
382
+ **Data the package handles locally, on your own infrastructure:**
383
+
384
+ | Data type | How it's handled |
385
+ | --- | --- |
386
+ | Authentication/session state | Generated and stored wherever your application configures it (e.g. `useMultiFileAuthState('./session')`). The package does not upload this elsewhere. Keep it private, as noted in [Security and responsible use](#security-and-responsible-use). |
387
+ | Messages and media | Sent to and received from WhatsApp's own servers as part of normal protocol operation. The package does not copy, log, or relay this content to Codex, CodexVerified, or any other third party. |
388
+ | Phone numbers passed to optional helpers | `checkStatusWA` sends a phone number to whatever endpoint you configure for that call. This only happens if your code explicitly calls it; the destination and its own privacy practices are your responsibility to review before use. |
389
+
390
+ **What the package does not do:**
391
+ - It does not phone home, run analytics/telemetry, or transmit usage data to Codex, CodexVerified, or any other party.
392
+ - It does not embed hidden credentials, tracking identifiers, or remote "kill switches."
393
+ - It does not access data outside of what your application explicitly wires it up to.
394
+
395
+ **Your responsibilities as a developer.** If you build an application on top of this package that collects personal data (e.g. contacts, message content, phone numbers), you are the data controller for that application and are responsible for complying with applicable privacy laws (such as GDPR, CCPA, or Nigeria's NDPA) and with WhatsApp's own terms and business policies. This package is a communication toolkit, not a compliance framework.
396
+
397
+ **Contact.** Questions about this policy or the project can be sent to codexauthorized@gmail.com or via [@codexverified](https://t.me/codexverified) on Telegram.
398
+
344
399
  ## License and maintenance
345
400
 
346
401
  This distribution is provided under the license declared in `package.json` and the accompanying license file. Review those terms before redistributing or combining this package with third-party code.
@@ -450,3 +505,121 @@ All networked helpers are opt-in. Website previews fetch only when explicitly ca
450
505
  | --- | --- |
451
506
  | `state` | Generation status: `GENERATING`, `COMPLETE`, or `ERROR` |
452
507
  | `mediaUrl` | URL of the generated media
508
+
509
+
510
+ ## CODEX-compatible rich messaging additions
511
+
512
+ The Codex socket now includes the compatible rich-message helpers documented by [`CODEX`](https://github.com/codexverified/baileys), while retaining Codex-specific APIs and package identity. These helpers are opt-in convenience methods over the existing WhatsApp message relay layer.
513
+
514
+ | Helper | Purpose |
515
+ | --- | --- |
516
+ | `sendRichMedia` | Sends a complete GenAI-style rich response envelope. |
517
+ | `sendRichGeneration` | Sends an AI image/video generation card with a generation state. |
518
+ | `updateRichGeneration` | Replaces an existing generation card using the message-edit protocol. |
519
+ | `sendRichText` | Sends text inside a forwarded rich-response envelope. |
520
+ | `sendRichImage` | Sends an image inside a forwarded rich-response envelope. |
521
+ | `sendRichLink` | Sends inline links inside a forwarded rich-response envelope. |
522
+ | `sendReels` | Sends rich response content containing reel metadata. |
523
+ | `sendRichSources` | Sends rich response content with source metadata. |
524
+ | `sendLink` / `sendLinkV2` | Convenience aliases for rich inline-link responses. |
525
+ | `sendRichWebview` | Sends a native-flow URL CTA for a hosted WebView or Mini App. |
526
+
527
+ ### Rich text, images, and links
528
+
529
+ ```javascript
530
+ await sock.sendRichText(jid, {
531
+ text: 'A rich response from Codex'
532
+ })
533
+
534
+ await sock.sendRichImage(jid, {
535
+ url: 'https://example.com/image.png',
536
+ mimeType: 'image/png'
537
+ })
538
+
539
+ await sock.sendRichLink(jid, {
540
+ text: 'Read the documentation',
541
+ links: [{ text: 'Documentation', url: 'https://example.com/docs' }]
542
+ })
543
+ ```
544
+
545
+ ### AI generation cards
546
+
547
+ ```javascript
548
+ const pending = await sock.sendRichGeneration(jid, {
549
+ text: 'Generating an image…',
550
+ mediaType: 'image',
551
+ status: 'GENERATING',
552
+ estimatedMs: 5000,
553
+ itemId: 'image-001'
554
+ })
555
+
556
+ await sock.updateRichGeneration(jid, pending.messageId, {
557
+ text: 'Generation complete',
558
+ mediaType: 'image',
559
+ status: 'READY',
560
+ url: 'https://example.com/generated.png',
561
+ itemId: 'image-001'
562
+ })
563
+ ```
564
+
565
+ `updateRichGeneration` requires the original message identifier returned by `sendRichGeneration`. Generation and rich-response payloads are protocol-sensitive and should be tested against the WhatsApp clients your application supports.
566
+
567
+ ### WebView or Mini App CTA
568
+
569
+ ```javascript
570
+ await sock.sendRichWebview(jid, {
571
+ title: 'Open the dashboard',
572
+ text: 'Tap below to continue',
573
+ buttonText: 'Open dashboard',
574
+ url: 'https://example.com/dashboard',
575
+ useWebview: true,
576
+ toast: 'Opening dashboard'
577
+ })
578
+ ```
579
+
580
+ The URL is validated before serialization. Treat button replies and URL destinations as untrusted input, and perform authorization on the server rather than relying on a button identifier.
581
+
582
+ ### Reels and source metadata
583
+
584
+ ```javascript
585
+ await sock.sendReels(jid, [{
586
+ url: 'https://example.com/reel.mp4',
587
+ title: 'Featured reel'
588
+ }], quotedMessage, { text: 'Featured today' })
589
+
590
+ await sock.sendRichSources(
591
+ jid,
592
+ 'Useful references',
593
+ [{ title: 'Project site', url: 'https://example.com' }],
594
+ quotedMessage
595
+ )
596
+ ```
597
+
598
+ These helpers preserve the existing `quoted` and send-option arguments where supported. The package does not enable background automation or contact discovery as a side effect of using them.
599
+
600
+ ### References
601
+
602
+ [1]: https://github.com/codexverified/baileys "codexverified/baileys repository"
603
+
604
+
605
+ ### Prepared-message relay targeting
606
+
607
+ `relayMessage` now accepts optional delivery-scoping controls compatible with CODEX’s relay API. These controls are intended for prepared messages and retry handling; use them only when the application understands the device-level delivery consequences.
608
+
609
+ ```javascript
610
+ await sock.relayMessage(jid, preparedMessage, {
611
+ messageId: 'message-id',
612
+ participant: { jid: '15551234567@s.whatsapp.net' },
613
+ protected: true
614
+ })
615
+
616
+ await sock.relayMessage(jid, preparedMessage, {
617
+ messageId: 'message-id',
618
+ retryParticipants: {
619
+ jid: '15551234567:2@s.whatsapp.net',
620
+ count: 1
621
+ }
622
+ })
623
+ ```
624
+
625
+ The `participant` option targets recipient-only delivery in a one-to-one chat. `retryParticipants` or `participants` identifies a device for retry resend, while `isSecret`, `protected`, and `me` restrict delivery to the corresponding device classes. These options do not bypass WhatsApp permissions or authorization checks.
@@ -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, 1040735178];
5
+ const version = [2, 3000, 1043857760];
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';
@@ -48,13 +48,13 @@ export const DEFAULT_CONNECTION_CONFIG = {
48
48
  browser: Browsers.macOS('Chrome'),
49
49
  waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
50
50
  connectTimeoutMs: 20000,
51
- keepAliveIntervalMs: 15000,
51
+ keepAliveIntervalMs: 30000,
52
52
  logger: logger.child({ class: 'baileys' }),
53
53
  emitOwnEvents: true,
54
54
  defaultQueryTimeoutMs: 60000,
55
55
  customUploadHosts: [],
56
56
  retryRequestDelayMs: 250,
57
- maxMsgRetryCount: 3,
57
+ maxMsgRetryCount: 5,
58
58
  fireInitQueries: true,
59
59
  auth: undefined,
60
60
  markOnlineOnConnect: true,
@@ -66,7 +66,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
66
66
  shouldIgnoreJid: () => false,
67
67
  linkPreviewImageThumbnailWidth: 192,
68
68
  transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
69
- generateHighQualityLinkPreview: true,
69
+ generateHighQualityLinkPreview: false,
70
70
  enableAutoSessionRecreation: true,
71
71
  enableRecentMessageCache: true,
72
72
  options: {},
@@ -12,6 +12,10 @@ 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 } from '../Utils/rich-message-utils.js';
16
+ import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
17
+ import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/website-preview.js';
18
+ import { isViewOnceMessage, downloadViewOnceMedia, createRegularMessageFromViewOnce, parseViewOnceInfo } from '../Utils/view-once-utils.js';
15
19
  import { collectMetaAIBotParticipantJids, isMetaAIBotResponse, META_AI_BOT_JID } from './aigroups.js';
16
20
  import { makeUsernameSocket } from './username.js';
17
21
 
@@ -580,10 +584,12 @@ export const makeMessagesSocket = (config) => {
580
584
  return { nodes, shouldIncludeDeviceIdentity };
581
585
  };
582
586
 
583
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList, statusPrivacy }) => {
587
+ const relayMessage = async (jid, message, { messageId: msgId, participant, participants: retryParticipantsOption, retryParticipants: retryParticipantsExplicit, isSecret = false, protected: protectedSend = false, me: meOnly = false, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList, statusPrivacy }) => {
584
588
  const meId = assertMeId(authState.creds);
585
589
  const meLid = authState.creds.me?.lid;
586
- const isRetryResend = Boolean(participant?.jid);
590
+ const retryParticipants = retryParticipantsExplicit || retryParticipantsOption || (participant?.count != null ? participant : undefined);
591
+ const isRetryResend = Boolean(retryParticipants?.jid && retryParticipants?.count != null);
592
+ const isRecipientOnly = Boolean(participant?.jid && !isRetryResend);
587
593
  let shouldIncludeDeviceIdentity = isRetryResend;
588
594
  const statusJid = 'status@broadcast';
589
595
  const { user, server } = jidDecode(jid);
@@ -601,6 +607,10 @@ export const makeMessagesSocket = (config) => {
601
607
  const binaryNodeContent = [];
602
608
  const devices = [];
603
609
  let reportingMessage;
610
+ const requiresViewOnce = !!(message.buttonsMessage || message.templateMessage || message.listMessage || message.interactiveMessage);
611
+ if (requiresViewOnce) {
612
+ message = { viewOnceMessage: { message: { ...message } } };
613
+ }
604
614
  const meMsg = {
605
615
  deviceSentMessage: {
606
616
  destinationJid,
@@ -609,16 +619,15 @@ export const makeMessagesSocket = (config) => {
609
619
  messageContextInfo: message.messageContextInfo
610
620
  };
611
621
  const extraAttrs = {};
612
- if (participant) {
622
+ if (isRetryResend) {
613
623
  if (!isGroup && !isStatus) {
614
624
  additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
615
625
  }
616
- const { user, device } = jidDecode(participant.jid);
617
- devices.push({
618
- user,
619
- device,
620
- jid: participant.jid
621
- });
626
+ const { user, device } = jidDecode(retryParticipants.jid);
627
+ devices.push({ user, device, jid: retryParticipants.jid });
628
+ }
629
+ if ((isRecipientOnly || isSecret || protectedSend || meOnly) && !isGroup && !isStatus) {
630
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
622
631
  }
623
632
  await authState.keys.transaction(async () => {
624
633
  const innerMessage = normalizeMessageContent(message);
@@ -824,6 +833,11 @@ export const makeMessagesSocket = (config) => {
824
833
  continue;
825
834
  }
826
835
  const isMe = user === mePnUser || user === meLidUser;
836
+ const hasDevice = jid.includes(':');
837
+ if (isRecipientOnly && isMe) continue;
838
+ if (isSecret && !(!isMe && !hasDevice)) continue;
839
+ if (protectedSend && !isMe && hasDevice) continue;
840
+ if (meOnly && !isMe) continue;
827
841
  if (isMe) {
828
842
  meRecipients.push(jid);
829
843
  }
@@ -845,8 +859,8 @@ export const makeMessagesSocket = (config) => {
845
859
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
846
860
  }
847
861
  if (isRetryResend) {
848
- const isParticipantLid = isLidUser(participant.jid);
849
- const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
862
+ const isParticipantLid = isLidUser(retryParticipants.jid);
863
+ const isMe = areJidsSameUser(retryParticipants.jid, isParticipantLid ? meLid : meId);
850
864
  let messageToSend = message;
851
865
  if (isGroupOrStatus) {
852
866
  let groupSenderIdentity;
@@ -892,7 +906,7 @@ export const makeMessagesSocket = (config) => {
892
906
  attrs: {
893
907
  v: '2',
894
908
  type,
895
- count: (participant.count || 0).toString()
909
+ count: retryParticipants.count.toString()
896
910
  },
897
911
  content: encryptedContent
898
912
  });
@@ -922,17 +936,17 @@ export const makeMessagesSocket = (config) => {
922
936
  },
923
937
  content: binaryNodeContent
924
938
  };
925
- if (participant) {
939
+ if (isRetryResend) {
926
940
  if (isJidGroup(destinationJid)) {
927
941
  stanza.attrs.to = destinationJid;
928
- stanza.attrs.participant = participant.jid;
942
+ stanza.attrs.participant = retryParticipants.jid;
929
943
  }
930
- else if (areJidsSameUser(participant.jid, meId)) {
931
- stanza.attrs.to = participant.jid;
944
+ else if (areJidsSameUser(retryParticipants.jid, meId)) {
945
+ stanza.attrs.to = retryParticipants.jid;
932
946
  stanza.attrs.recipient = destinationJid;
933
947
  }
934
948
  else {
935
- stanza.attrs.to = participant.jid;
949
+ stanza.attrs.to = retryParticipants.jid;
936
950
  }
937
951
  }
938
952
  else {
@@ -956,7 +970,7 @@ export const makeMessagesSocket = (config) => {
956
970
  id: msgId,
957
971
  fromMe: true,
958
972
  remoteJid: destinationJid,
959
- participant: participant?.jid
973
+ participant: retryParticipants?.jid
960
974
  };
961
975
  const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
962
976
  if (reportingNode) {
@@ -1046,7 +1060,7 @@ export const makeMessagesSocket = (config) => {
1046
1060
  inFlightTcTokenIssuance.delete(tcTokenJid);
1047
1061
  });
1048
1062
  }
1049
- if (messageRetryManager && !participant) {
1063
+ if (messageRetryManager && !retryParticipants) {
1050
1064
  messageRetryManager.addRecentMessage(destinationJid, msgId, message);
1051
1065
  }
1052
1066
  }, meId);
@@ -1179,7 +1193,7 @@ export const makeMessagesSocket = (config) => {
1179
1193
  followingCache.clear();
1180
1194
  });
1181
1195
 
1182
- return {
1196
+ const socket = {
1183
1197
  ...sock,
1184
1198
  userDevicesCache,
1185
1199
  devicesMutex,
@@ -1265,6 +1279,105 @@ export const makeMessagesSocket = (config) => {
1265
1279
  await relayMessage(jid, message, { messageId });
1266
1280
  return { message, messageId };
1267
1281
  },
1282
+ /** Send a complete GenAI-style rich response envelope. */
1283
+ sendRichMedia: async (jid, content = {}, quoted, options = {}) => {
1284
+ const message = prepareRichResponseMessage({ ...content, noDonation: options.noDonation ?? true });
1285
+ const messageId = generateMessageIDV2(jid);
1286
+ await relayMessage(jid, message, { messageId, quoted });
1287
+ return { message, messageId };
1288
+ },
1289
+ /** Send a native AI image/video generation card. */
1290
+ sendRichGeneration: async (jid, content = {}, quoted, options = {}) => {
1291
+ const generated = prepareRichGenerationMessage({
1292
+ ...content,
1293
+ responseId: options.responseId,
1294
+ forwarded: options.forwarded !== false
1295
+ });
1296
+ const messageId = generateMessageIDV2(jid);
1297
+ await relayMessage(jid, generated.message, { messageId, quoted });
1298
+ return { ...generated, messageId };
1299
+ },
1300
+ /** Edit an existing rich generation card in place. */
1301
+ updateRichGeneration: async (jid, targetId, content = {}, options = {}) => {
1302
+ if (!jid) throw new Boom('updateRichGeneration requires a target JID', { statusCode: 400 });
1303
+ if (!targetId) throw new Boom('updateRichGeneration requires the original message ID', { statusCode: 400 });
1304
+ const generated = prepareRichGenerationMessage({
1305
+ ...content,
1306
+ responseId: options.responseId,
1307
+ itemId: options.itemId || content.itemId,
1308
+ forwarded: options.forwarded !== false
1309
+ });
1310
+ const editedMessage = generateWAMessageFromContent(jid, {
1311
+ botForwardedMessage: {
1312
+ message: {
1313
+ protocolMessage: {
1314
+ key: { remoteJid: jid, fromMe: true, id: targetId },
1315
+ type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
1316
+ editedMessage: generated.message
1317
+ }
1318
+ }
1319
+ }
1320
+ }, { messageId: generateMessageIDV2(jid) });
1321
+ await relayMessage(jid, editedMessage.message, { messageId: editedMessage.key.id });
1322
+ return { ...generated, messageId: editedMessage.key.id, targetId };
1323
+ },
1324
+ /** Send plain text inside a forwarded GenAI rich response. */
1325
+ sendRichText: async (jid, content = {}, options = {}) => {
1326
+ const message = prepareRichTextMessage(content);
1327
+ const messageId = generateMessageIDV2(jid);
1328
+ await relayMessage(jid, message, { messageId, quoted: options.quoted });
1329
+ return { message, messageId };
1330
+ },
1331
+ /** Send an image inside a forwarded GenAI rich response. */
1332
+ sendRichImage: async (jid, content = {}, options = {}) => {
1333
+ const message = prepareRichImageMessage(content);
1334
+ const messageId = generateMessageIDV2(jid);
1335
+ await relayMessage(jid, message, { messageId, quoted: options.quoted });
1336
+ return { message, messageId };
1337
+ },
1338
+ /** Send inline hyperlinks inside a forwarded GenAI rich response. */
1339
+ sendRichLink: async (jid, content = {}, options = {}) => {
1340
+ const message = prepareRichLinkMessage(content);
1341
+ const messageId = generateMessageIDV2(jid);
1342
+ await relayMessage(jid, message, { messageId, quoted: options.quoted });
1343
+ return { message, messageId };
1344
+ },
1345
+ sendReels: async (jid, reels, quoted, options = {}) => {
1346
+ const message = prepareRichResponseMessage({ contentText: options.text || '', reels, noDonation: options.noDonation ?? true });
1347
+ const messageId = generateMessageIDV2(jid);
1348
+ await relayMessage(jid, message, { messageId, quoted });
1349
+ return { message, messageId };
1350
+ },
1351
+ sendRichSources: async (jid, text, sources, quoted, options = {}) => {
1352
+ const message = prepareRichResponseMessage({ contentText: text, sources, noDonation: options.noDonation ?? true });
1353
+ const messageId = generateMessageIDV2(jid);
1354
+ await relayMessage(jid, message, { messageId, quoted });
1355
+ return { message, messageId };
1356
+ },
1357
+ sendLink: async (jid, text, links, quoted, options = {}) => {
1358
+ const message = prepareRichLinkMessage({ text, links });
1359
+ const messageId = generateMessageIDV2(jid);
1360
+ await relayMessage(jid, message, { messageId, quoted });
1361
+ return { message, messageId };
1362
+ },
1363
+ sendLinkV2: async (jid, text, links, quoted, options = {}) => {
1364
+ const message = prepareRichLinkMessage({ text, links });
1365
+ const messageId = generateMessageIDV2(jid);
1366
+ await relayMessage(jid, message, { messageId, quoted });
1367
+ return { message, messageId };
1368
+ },
1369
+ /** Send a native-flow URL CTA for a hosted WebView/Mini App. */
1370
+ sendRichWebview: async (jid, content = {}, options = {}) => {
1371
+ const { text = '', title, footer, image, caption, buttonText = 'Open', url, useWebview = true, toast, ...rest } = content;
1372
+ if (!url || typeof url !== 'string') throw new Boom('sendRichWebview requires a URL', { statusCode: 400 });
1373
+ const button = { text: buttonText, url, useWebview, ...(toast ? { toast } : {}), ...rest };
1374
+ return sock.sendMessage(jid, {
1375
+ ...(image ? { image, caption: caption || text || title || buttonText } : { text: text || title || buttonText }),
1376
+ ...(footer ? { footer } : {}),
1377
+ nativeFlow: [button],
1378
+ ...(toast ? { toast } : {})
1379
+ }, options);
1380
+ },
1268
1381
  sendMetaAI: async (a, b, c = {}) => {
1269
1382
  // baron signature: (jid, text, opts) README signature: (text, opts)
1270
1383
  let text;
@@ -2070,5 +2183,64 @@ export const makeMessagesSocket = (config) => {
2070
2183
  return fullMsg;
2071
2184
  }
2072
2185
  }
2073
- }
2186
+ };
2187
+
2188
+ socket.autoVV = async (message, options = {}) => {
2189
+ if (!isViewOnceMessage(message)) return null;
2190
+ const info = parseViewOnceInfo(message);
2191
+ const mediaBuffer = await downloadViewOnceMedia(message, { logger, ...options });
2192
+ const regularMessage = createRegularMessageFromViewOnce(message, mediaBuffer);
2193
+ if (regularMessage) ev.emit('messages.upsert', { messages: [regularMessage], type: 'append' });
2194
+ logger.info({ key: message?.key, ...info, bufferSize: mediaBuffer.length }, 'autoVV: successfully decrypted view-once message');
2195
+ return regularMessage;
2196
+ };
2197
+
2198
+ socket.antiVV = async (message) => {
2199
+ if (!isViewOnceMessage(message)) return false;
2200
+ const info = parseViewOnceInfo(message);
2201
+ const jid = message.key.remoteJid;
2202
+ const participant = message.key.participant || jid;
2203
+ await sendReceipt(jid, participant, [message.key.id], 'sender');
2204
+ ev.emit('messages.update', [{ key: message.key, update: { messageStubType: proto.WebMessageInfo.StubType.REVOKE, messageStubParameters: ['auto-vv-delete'] } }]);
2205
+ logger.info({ key: message?.key, ...info }, 'antiVV: successfully deleted view-once message');
2206
+ return true;
2207
+ };
2208
+
2209
+ socket.isViewOnce = (message) => isViewOnceMessage(message);
2210
+ socket.getViewOnceInfo = (message) => parseViewOnceInfo(message);
2211
+
2212
+ socket.sendLatexImage = async (jid, options = {}) => {
2213
+ const { text, expressions = [], headerText, footer } = options;
2214
+ const renderedExpressions = expressions.map(expr => {
2215
+ const latex = expr?.latexExpression || expr?.expression || expr;
2216
+ return { latexExpression: latex, url: `https://latex.codecogs.com/png.latex?${encodeURIComponent(latex).replace(/'/g, '%27')}`, width: expr?.width || 400, height: expr?.height || 100 };
2217
+ });
2218
+ const { message, messageId } = generateLatexContent(null, { text, expressions: renderedExpressions, headerText, footer });
2219
+ await relayMessage(jid, message, { messageId });
2220
+ return { message, messageId };
2221
+ };
2222
+
2223
+ socket.sendHtmlMessage = async (jid, content = {}, options = {}) => {
2224
+ if (!jid) throw new Boom('sendHtmlMessage requires a target JID', { statusCode: 400 });
2225
+ if (!content.html) throw new Boom('sendHtmlMessage requires html content', { statusCode: 400 });
2226
+ const fullMsg = await generateWAMessageFromContent(jid, prepareHtmlMessage(content), { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2227
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2228
+ return fullMsg;
2229
+ };
2230
+
2231
+ socket.sendSlotMachine = async (jid, options = {}, sendOptions = {}) => {
2232
+ return socket.sendHtmlMessage(jid, { html: generateSlotMachineHtml(options) }, sendOptions);
2233
+ };
2234
+
2235
+ socket.sendWebsitePreview = async (jid, url, options = {}, sendOptions = {}) => {
2236
+ if (!url) throw new Boom('sendWebsitePreview requires a URL', { statusCode: 400 });
2237
+ return socket.sendHtmlMessage(jid, { html: await generateWebsitePreviewHtml(url, options) }, sendOptions);
2238
+ };
2239
+
2240
+ socket.sendLinkPreview = async (jid, url, options = {}, sendOptions = {}) => {
2241
+ if (!url) throw new Boom('sendLinkPreview requires a URL', { statusCode: 400 });
2242
+ return socket.sendHtmlMessage(jid, { html: generateLinkPreviewHtml(url, options) }, sendOptions);
2243
+ };
2244
+
2245
+ return socket;
2074
2246
  };
@@ -1,5 +1,6 @@
1
1
  import { promisify } from 'util';
2
- import { inflate } from 'zlib';
2
+ import { pipeline } from 'stream/promises';
3
+ import { createInflate, inflate } from 'zlib';
3
4
  import { proto } from '../../WAProto/index.js';
4
5
  import { WAMessageStubType } from '../Types/index.js';
5
6
  import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser } from '../WABinary/index.js';
@@ -24,13 +25,13 @@ const extractPnFromMessages = (messages) => {
24
25
  };
25
26
  export const downloadHistory = async (msg, options) => {
26
27
  const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options });
27
- const bufferArray = [];
28
- for await (const chunk of stream) {
29
- bufferArray.push(chunk);
30
- }
31
- let buffer = Buffer.concat(bufferArray);
32
- // decompress buffer
33
- buffer = await inflatePromise(buffer);
28
+ // Pipe the decrypted stream directly through zlib inflate to avoid
29
+ // allocating an additional intermediate compressed-data buffer.
30
+ const inflater = createInflate();
31
+ const chunks = [];
32
+ inflater.on('data', (chunk) => chunks.push(chunk));
33
+ await pipeline(stream, inflater);
34
+ const buffer = Buffer.concat(chunks);
34
35
  const syncData = proto.HistorySync.decode(buffer);
35
36
  return syncData;
36
37
  };
@@ -55,6 +56,7 @@ export const processHistoryMessage = (item, logger) => {
55
56
  contacts.push({
56
57
  id: chat.id,
57
58
  name: chat.displayName || chat.name || chat.username || undefined,
59
+ username: chat.username || undefined,
58
60
  lid: chat.lidJid || chat.accountLid || undefined,
59
61
  phoneNumber: chat.pnJid || undefined
60
62
  });
@@ -95,7 +97,7 @@ export const processHistoryMessage = (item, logger) => {
95
97
  });
96
98
  }
97
99
  }
98
- chats.push({ ...chat });
100
+ chats.push(chat);
99
101
  }
100
102
  break;
101
103
  case proto.HistorySync.HistorySyncType.PUSH_NAME:
@@ -109,6 +111,7 @@ export const processHistoryMessage = (item, logger) => {
109
111
  contacts,
110
112
  messages,
111
113
  lidPnMappings,
114
+ pastParticipants: item.pastParticipants,
112
115
  syncType: item.syncType,
113
116
  progress: item.progress
114
117
  };
@@ -127,4 +130,4 @@ export const getHistoryMsg = (message) => {
127
130
  const normalizedContent = !!message ? normalizeMessageContent(message) : undefined;
128
131
  const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification;
129
132
  return anyHistoryMsg;
130
- };
133
+ };
@@ -52,6 +52,13 @@ export class MessageRetryManager {
52
52
  ttlAutopurge: true,
53
53
  updateAgeOnGet: true
54
54
  }); // 15 minutes TTL
55
+ // Cache Signal base keys briefly so duplicate retry receipts can be
56
+ // recognized without repeatedly recreating an otherwise valid session.
57
+ this.baseKeys = new LRUCache({
58
+ max: 1024,
59
+ ttl: 15 * 60 * 1000,
60
+ ttlAutopurge: true
61
+ });
55
62
  this.pendingPhoneRequests = {};
56
63
  this.maxMsgRetryCount = 5;
57
64
  this.statistics = {
@@ -215,6 +222,7 @@ export class MessageRetryManager {
215
222
  this.messageKeyIndex.clear();
216
223
  this.sessionRecreateHistory.clear();
217
224
  this.retryCounters.clear();
225
+ this.baseKeys.clear();
218
226
  for (const messageId of Object.keys(this.pendingPhoneRequests)) {
219
227
  this.cancelPendingPhoneRequest(messageId);
220
228
  }
@@ -227,6 +235,24 @@ export class MessageRetryManager {
227
235
  phoneRequests: 0
228
236
  };
229
237
  }
238
+ saveBaseKey(addr, msgId, baseKey) {
239
+ this.baseKeys.set(`${addr}:${msgId}`, baseKey);
240
+ }
241
+ hasSameBaseKey(addr, msgId, baseKey) {
242
+ const stored = this.baseKeys.get(`${addr}:${msgId}`);
243
+ if (!stored || stored.length !== baseKey.length) {
244
+ return false;
245
+ }
246
+ for (let i = 0; i < stored.length; i++) {
247
+ if (stored[i] !== baseKey[i]) {
248
+ return false;
249
+ }
250
+ }
251
+ return true;
252
+ }
253
+ deleteBaseKey(addr, msgId) {
254
+ this.baseKeys.delete(`${addr}:${msgId}`);
255
+ }
230
256
  keyToString(key) {
231
257
  return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`;
232
258
  }
@@ -238,4 +264,4 @@ export class MessageRetryManager {
238
264
  this.recentMessagesMap.delete(keyStr);
239
265
  this.messageKeyIndex.delete(messageId);
240
266
  }
241
- }
267
+ }
@@ -5,6 +5,7 @@ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJid
5
5
  import { aesDecryptGCM, hmacSign } from './crypto.js';
6
6
  import { getKeyAuthor, toNumber } from './generics.js';
7
7
  import { downloadAndProcessHistorySyncNotification } from './history.js';
8
+ import { buildMergedTcTokenIndexWrite, resolveTcTokenJid } from './tc-token-utils.js';
8
9
  const REAL_MSG_STUB_TYPES = new Set([
9
10
  WAMessageStubType.CALL_MISSED_GROUP_VIDEO,
10
11
  WAMessageStubType.CALL_MISSED_GROUP_VOICE,
@@ -12,6 +13,52 @@ const REAL_MSG_STUB_TYPES = new Set([
12
13
  WAMessageStubType.CALL_MISSED_VOICE
13
14
  ]);
14
15
  const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD]);
16
+ async function storeTcTokensFromHistorySync(chats, signalRepository, keyStore, logger) {
17
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
18
+ const candidates = [];
19
+ for (const chat of chats || []) {
20
+ const ts = chat.tcTokenTimestamp ? toNumber(chat.tcTokenTimestamp) : 0;
21
+ if (chat.tcToken?.length && ts > 0) {
22
+ const jid = jidNormalizedUser(chat.id);
23
+ const storageJid = await resolveTcTokenJid(jid, getLIDForPN);
24
+ candidates.push({
25
+ storageJid,
26
+ token: Buffer.from(chat.tcToken),
27
+ ts,
28
+ senderTs: chat.tcTokenSenderTimestamp ? toNumber(chat.tcTokenSenderTimestamp) : undefined
29
+ });
30
+ }
31
+ }
32
+ if (!candidates.length) {
33
+ return;
34
+ }
35
+ const jids = candidates.map(candidate => candidate.storageJid);
36
+ const existing = await keyStore.get('tctoken', jids);
37
+ const entries = {};
38
+ for (const candidate of candidates) {
39
+ const existingEntry = existing[candidate.storageJid];
40
+ const existingTs = existingEntry?.timestamp ? Number(existingEntry.timestamp) : 0;
41
+ if (existingTs > 0 && existingTs >= candidate.ts) {
42
+ continue;
43
+ }
44
+ entries[candidate.storageJid] = {
45
+ ...existingEntry,
46
+ token: candidate.token,
47
+ timestamp: String(candidate.ts),
48
+ ...(candidate.senderTs !== undefined ? { senderTimestamp: candidate.senderTs } : {})
49
+ };
50
+ }
51
+ if (Object.keys(entries).length) {
52
+ logger?.debug({ count: Object.keys(entries).length }, 'storing tctokens from history sync');
53
+ try {
54
+ const indexWrite = await buildMergedTcTokenIndexWrite(keyStore, Object.keys(entries));
55
+ await keyStore.set({ tctoken: { ...entries, ...indexWrite } });
56
+ }
57
+ catch (err) {
58
+ logger?.warn({ err }, 'failed to store tctokens from history sync');
59
+ }
60
+ }
61
+ }
15
62
  /** Cleans a received message to further processing */
16
63
  export const cleanMessage = (message, meId, meLid) => {
17
64
  // ensure remoteJid and participant doesn't have device or agent in it
@@ -146,6 +193,22 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
146
193
  }
147
194
  const protocolMsg = content?.protocolMessage;
148
195
  if (protocolMsg) {
196
+ // These protocol messages are generated by the user’s primary device and
197
+ // must not be accepted from an arbitrary remote sender. Other protocol
198
+ // types, such as revokes and edits, legitimately originate from peers.
199
+ const SELF_ONLY_TYPES = new Set([
200
+ proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION,
201
+ proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE,
202
+ proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC,
203
+ proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE
204
+ ]);
205
+ if (protocolMsg.type !== null &&
206
+ protocolMsg.type !== undefined &&
207
+ SELF_ONLY_TYPES.has(protocolMsg.type) &&
208
+ !message.key.fromMe) {
209
+ logger?.warn({ msgId: message.key.id, type: protocolMsg.type, from: message.key.participant || message.key.remoteJid }, 'dropping spoofed self-only protocolMessage from non-self origin');
210
+ return;
211
+ }
149
212
  switch (protocolMsg.type) {
150
213
  case proto.Message.ProtocolMessage.Type.HISTORY_SYNC_NOTIFICATION:
151
214
  const histNotification = protocolMsg.historySyncNotification;
@@ -174,6 +237,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
174
237
  .storeLIDPNMappings(data.lidPnMappings)
175
238
  .catch(err => logger?.warn({ err }, 'failed to store LID-PN mappings from history sync'));
176
239
  }
240
+ await storeTcTokensFromHistorySync(data.chats, signalRepository, keyStore, logger);
177
241
  ev.emit('messaging-history.set', {
178
242
  ...data,
179
243
  isLatest: histNotification.syncType !== proto.HistorySync.HistorySyncType.ON_DEMAND ? isLatest : undefined,
@@ -383,12 +447,19 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
383
447
  id: jid,
384
448
  author: message.key.participant,
385
449
  authorPn: message.key.participantAlt,
450
+ authorUsername: message.key.participantUsername,
386
451
  participants,
387
452
  action
388
453
  });
389
454
  const emitGroupUpdate = (update) => {
390
455
  ev.emit('groups.update', [
391
- { id: jid, ...update, author: message.key.participant ?? undefined, authorPn: message.key.participantAlt }
456
+ {
457
+ id: jid,
458
+ ...update,
459
+ author: message.key.participant ?? undefined,
460
+ authorPn: message.key.participantAlt,
461
+ authorUsername: message.key.participantUsername
462
+ }
392
463
  ]);
393
464
  };
394
465
  const emitGroupRequestJoin = (participant, action, method) => {
@@ -396,6 +467,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
396
467
  id: jid,
397
468
  author: message.key.participant,
398
469
  authorPn: message.key.participantAlt,
470
+ authorUsername: message.key.participantUsername,
399
471
  participant: participant.lid,
400
472
  participantPn: participant.pn,
401
473
  action,
@@ -524,4 +596,4 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
524
596
  ev.emit('chats.update', [chat]);
525
597
  }
526
598
  };
527
- export default processMessage;
599
+ export default processMessage;
@@ -102,7 +102,8 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], ge
102
102
  const tcTokenData = await authState.keys.get('tctoken', [storageJid]);
103
103
  const entry = tcTokenData?.[storageJid];
104
104
  const tcTokenBuffer = entry?.token;
105
- if (!tcTokenBuffer?.length || isTcTokenExpired(entry?.timestamp)) {
105
+ const timestamp = entry?.timestamp;
106
+ if (!tcTokenBuffer?.length || timestamp === undefined || isTcTokenExpired(timestamp)) {
106
107
  if (tcTokenBuffer) {
107
108
  // Preserve senderTimestamp so shouldSendNewTcToken() keeps its dedupe state
108
109
  // after we drop the unusable peer token. Only wipe the record entirely when
@@ -116,7 +117,7 @@ export async function buildTcTokenFromJid({ authState, jid, baseContent = [], ge
116
117
  }
117
118
  baseContent.push({
118
119
  tag: 'tctoken',
119
- attrs: {},
120
+ attrs: { t: String(timestamp) },
120
121
  content: tcTokenBuffer
121
122
  });
122
123
  return baseContent;
@@ -1,5 +1,5 @@
1
1
  import { Mutex } from 'async-mutex';
2
- import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
2
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from 'fs/promises';
3
3
  import { join } from 'path';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { initAuthCreds } from './auth-utils.js';
@@ -31,10 +31,14 @@ export const useMultiFileAuthState = async (folder) => {
31
31
  const filePath = join(folder, fixFileName(file));
32
32
  const mutex = getFileLock(filePath);
33
33
  return mutex.acquire().then(async (release) => {
34
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
34
35
  try {
35
- await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer));
36
+ const serialized = JSON.stringify(data, BufferJSON.replacer);
37
+ await writeFile(tempPath, serialized, { encoding: 'utf8', mode: 0o600 });
38
+ await rename(tempPath, filePath);
36
39
  }
37
40
  finally {
41
+ await unlink(tempPath).catch(() => undefined);
38
42
  release();
39
43
  }
40
44
  });
@@ -81,7 +85,7 @@ export const useMultiFileAuthState = async (folder) => {
81
85
  }
82
86
  }
83
87
  else {
84
- await mkdir(folder, { recursive: true });
88
+ await mkdir(folder, { recursive: true, mode: 0o700 });
85
89
  }
86
90
  const fixFileName = (file) => file?.replace(/\//g, '__')?.replace(/:/g, '-');
87
91
  const creds = (await readData('creds.json')) || initAuthCreds();
@@ -117,4 +121,4 @@ export const useMultiFileAuthState = async (folder) => {
117
121
  return writeData(creds, 'creds.json');
118
122
  }
119
123
  };
120
- };
124
+ };
@@ -28,7 +28,7 @@ const getUserAgent = (config) => {
28
28
  };
29
29
  const PLATFORM_MAP = {
30
30
  'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
31
- Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
31
+ Windows: proto.ClientPayload.WebInfo.WebSubPlatform.WIN_HYBRID
32
32
  };
33
33
  const getWebInfo = (config) => {
34
34
  let webSubPlatform = proto.ClientPayload.WebInfo.WebSubPlatform.WEB_BROWSER;
@@ -48,6 +48,9 @@ const getClientPayload = (config) => {
48
48
  if (!config.browser[1].toLocaleLowerCase().includes('android')) {
49
49
  payload.webInfo = getWebInfo(config);
50
50
  }
51
+ if (config.pushName) {
52
+ payload.pushName = config.pushName;
53
+ }
51
54
  return payload;
52
55
  };
53
56
  export const generateLoginNode = (userJid, config) => {
@@ -203,4 +206,4 @@ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
203
206
  account.accountSignatureKey = null;
204
207
  }
205
208
  return proto.ADVSignedDeviceIdentity.encode(account).finish();
206
- };
209
+ };
package/lib/index.d.ts CHANGED
@@ -121,8 +121,47 @@ export interface AIPromptOptions {
121
121
  [key: string]: unknown;
122
122
  }
123
123
 
124
+ export interface RelayMessageOptions {
125
+ messageId?: string;
126
+ /** Recipient-only delivery: skip the sender’s own devices in a one-to-one chat. */
127
+ participant?: { jid: string };
128
+ /** Retry resend target with a device JID and retry count. */
129
+ participants?: { jid: string; count: number };
130
+ retryParticipants?: { jid: string; count: number };
131
+ /** Keep only the target’s primary device. */
132
+ isSecret?: boolean;
133
+ /** Skip the target’s linked devices. */
134
+ protected?: boolean;
135
+ /** Keep only the sender’s own devices. */
136
+ me?: boolean;
137
+ [key: string]: unknown;
138
+ }
139
+
140
+ export interface RichGenerationContent {
141
+ text?: string;
142
+ mediaType?: 'image' | 'video' | string;
143
+ url?: string;
144
+ thumbnail?: string | Uint8Array | Record<string, unknown>;
145
+ mimeType?: string;
146
+ fileLength?: number;
147
+ duration?: number;
148
+ status?: 'GENERATING' | 'READY' | 'FAILED' | string;
149
+ estimatedMs?: number;
150
+ itemId?: string;
151
+ }
152
+
153
+ export interface RichGenerationResult {
154
+ message: unknown;
155
+ messageId: string;
156
+ responseId?: string;
157
+ itemId?: string;
158
+ targetId?: string;
159
+ }
160
+
124
161
  export interface WASocket {
125
162
  sendMessage(jid: string | string[], content: StatusContent | GroupStatusContent | Record<string, unknown>, options?: Partial<StatusOptions> & Record<string, unknown>): Promise<unknown>;
163
+ /** Relay a prepared message with optional recipient/device-scoping controls. */
164
+ relayMessage(jid: string, message: Record<string, unknown>, options?: RelayMessageOptions): Promise<unknown>;
126
165
  deleteGroupStatus(jid: string, key: WAMessageKey): Promise<unknown>;
127
166
  /** Reports the contact and blocks it after WhatsApp accepts the report. */
128
167
  reportContact(jid: string, messageKeys?: WAMessageKey[]): Promise<unknown>;
@@ -265,6 +304,41 @@ export interface WASocket {
265
304
  sendInteractiveTable(jid: string, table: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
266
305
  /** Send a WhatsApp native flow button. */
267
306
  sendWhatsAppFlow(jid: string, flow: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
307
+ /** Send a complete GenAI-style rich response envelope. */
308
+ sendRichMedia(jid: string, content?: Record<string, unknown>, quoted?: unknown, options?: Record<string, unknown>): Promise<{ message: unknown; messageId: string }>;
309
+ /** Send a native AI image/video generation card. */
310
+ sendRichGeneration(jid: string, content?: RichGenerationContent, quoted?: unknown, options?: Record<string, unknown>): Promise<RichGenerationResult>;
311
+ /** Edit an existing rich generation card in place. */
312
+ updateRichGeneration(jid: string, targetId: string, content?: RichGenerationContent, options?: Record<string, unknown>): Promise<RichGenerationResult>;
313
+ /** Send plain text inside a forwarded GenAI rich response. */
314
+ sendRichText(jid: string, content?: { text?: string }, options?: Record<string, unknown>): Promise<unknown>;
315
+ /** Send an image inside a forwarded GenAI rich response. */
316
+ sendRichImage(jid: string, content?: { url?: string; mimeType?: string }, options?: Record<string, unknown>): Promise<unknown>;
317
+ /** Send inline hyperlinks inside a forwarded GenAI rich response. */
318
+ sendRichLink(jid: string, content?: { links?: Array<Record<string, unknown>>; text?: string }, options?: Record<string, unknown>): Promise<unknown>;
319
+ /** Send rich media with reels or source metadata. */
320
+ sendReels(jid: string, reels: unknown, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
321
+ sendRichSources(jid: string, text: string, sources: unknown, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
322
+ sendLink(jid: string, text: string, links: unknown, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
323
+ sendLinkV2(jid: string, text: string, links: unknown, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
324
+ /** Send a native-flow URL CTA for a hosted WebView/Mini App. */
325
+ sendRichWebview(jid: string, content: { text?: string; title?: string; footer?: string; image?: unknown; caption?: string; buttonText?: string; url: string; useWebview?: boolean; toast?: string; [key: string]: unknown }, options?: Record<string, unknown>): Promise<unknown>;
326
+ /** Decrypt a view-once message and emit the recovered regular message. */
327
+ autoVV(message: unknown, options?: Record<string, unknown>): Promise<unknown>;
328
+ /** Revoke a received view-once message and emit a local removal update. */
329
+ antiVV(message: unknown, options?: Record<string, unknown>): Promise<boolean>;
330
+ isViewOnce(message: unknown): boolean;
331
+ getViewOnceInfo(message: unknown): Record<string, unknown>;
332
+ /** Send LaTeX expressions using Codecogs PNG URLs. */
333
+ sendLatexImage(jid: string, options?: { text?: string; expressions?: Array<Record<string, unknown> | string>; headerText?: string; footer?: string }): Promise<unknown>;
334
+ /** Render and send HTML as a rich response. */
335
+ sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
336
+ /** Send the built-in HTML slot-machine payload. */
337
+ sendSlotMachine(jid: string, options?: { title?: string; startingCredits?: number }, sendOptions?: Record<string, unknown>): Promise<unknown>;
338
+ /** Send a fetched website preview as an HTML rich response. */
339
+ sendWebsitePreview(jid: string, url: string, options?: Record<string, unknown>, sendOptions?: Record<string, unknown>): Promise<unknown>;
340
+ /** Send a lightweight HTML link-preview card. */
341
+ sendLinkPreview(jid: string, url: string, options?: Record<string, unknown>, sendOptions?: Record<string, unknown>): Promise<unknown>;
268
342
 
269
343
  // ---- GraphQL layer ----
270
344
  setAccessToken(token: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexverified/baileys",
3
- "version": "2.12.12",
3
+ "version": "2.13.13",
4
4
  "description": "CODEX TECHNOLOGY a premium Baileys developed and built by Dev Codex, with Meta compositing, bot planning replay, welcome flow, rich messages, interactive messages, albums, and more.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",