@codexverified/baileys 2.11.12 → 2.12.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.11.12` |
43
+ | Version | `2.12.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.11.12.tgz` | Place the `.tgz` beside your application |
57
+ | From local archive | `npm install ./codex-baileys-2.12.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.
@@ -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,19 +1,258 @@
1
1
  /**
2
- * Self-contained, dependency-free slot-machine HTML generator.
3
- * The generated page is local-only and makes no network requests.
2
+ * Slot Machine Game — Fruit Bonanza
3
+ *
4
+ * A complete canvas-based slot machine game that renders directly
5
+ * inside WhatsApp chat bubbles using FOAHtmlPrimitiveDemoDONOTUSE.
6
+ *
7
+ * Features:
8
+ * - 5 reels with Cherry, Lemon, Bell, Diamond, 7, BAR symbols
9
+ * - Betting system (10/20/50 credits)
10
+ * - Win detection with 5 paylines
11
+ * - Canvas-rendered symbols with animations
12
+ * - Jungle/green theme
13
+ * - Lever animation
14
+ * - Jackpot display & game over screen
15
+ *
16
+ * Usage:
17
+ * const { generateSlotMachineHtml } = require('./slot-machine');
18
+ * const html = generateSlotMachineHtml();
19
+ * await sock.sendHtmlMessage(jid, { html });
4
20
  */
5
21
 
6
- const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (character) => ({
7
- '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
8
- }[character]));
22
+ const SLOT_MACHINE_HTML = `<html><head><style>
23
+ *{box-sizing:border-box}html,body{margin:0;width:100%;overflow:hidden;background:transparent;font-family:Arial,sans-serif;overscroll-behavior:none}body{padding:9px;background:radial-gradient(circle at 50% 12%,#5b2d18,#160908 55%,#030202)}.machine{position:relative;overflow:hidden;padding:13px 34px 18px 13px;border:4px solid #2b0c05;border-radius:30px;background:linear-gradient(105deg,#1e0704,#8c3515 8%,#3b1008 20%,#611e0c 52%,#2b0906 82%,#9c401b 94%,#2a0b06);box-shadow:inset 0 0 0 3px #e6a139,inset 0 0 0 7px #5d210b,inset 0 20px 35px #ffb52a22,0 8px 0 #210705,0 14px 24px #000c;touch-action:none}.machine:before{content:"";position:absolute;inset:8px;border:2px solid #ffba3d;border-radius:22px;pointer-events:none;box-shadow:inset 0 0 11px #ff7b00}.lights{height:9px;margin:0 14px 8px;border:2px solid #6f2608;border-radius:8px;background:repeating-radial-gradient(circle at 6px 50%,#fffbd3 0 2px,#ffbd00 3px 5px,#641800 6px 12px);box-shadow:0 0 12px #ff8a00;animation:lights .55s steps(2) infinite}.title{padding:11px 4px 8px;border:3px solid #ffc54c;border-radius:50% 50% 11px 11px/30% 30% 10px 10px;color:#fff2a1;background:radial-gradient(ellipse at 50% 0,#12b2bc,#075165 58%,#06182a);box-shadow:inset 0 0 0 4px #74300d,inset 0 -11px 18px #001726,0 4px 0 #3b1207;text-align:center;font:25px Impact,Arial Black,sans-serif;letter-spacing:1px;text-shadow:0 3px #8f1c0c,2px 0 #8f1c0c,-2px 0 #8f1c0c}.jackpot{width:75%;margin:5px auto 8px;padding:4px;border:2px solid #ffcf5b;border-radius:10px;color:#ffe7a1;background:linear-gradient(#741523,#29060e);box-shadow:inset 0 2px 5px #ff667744;text-align:center;font:bold 10px monospace;letter-spacing:1px}.stats{display:flex;margin:0 2px 9px;padding:5px;border:2px solid #b66b20;border-radius:9px;background:linear-gradient(#210c08,#070303);box-shadow:inset 0 0 9px #000,0 3px 0 #4a1909}.stat{flex:1;border-right:1px solid #754016;color:#d6a15b;text-align:center;font:bold 10px monospace}.stat:last-child{border:0}.stat b{display:block;margin-top:2px;color:#fff0bb;font-size:15px;text-shadow:0 0 6px #f80}.frame{position:relative;padding:9px;border:5px solid #9b4e12;border-radius:18px;background:linear-gradient(90deg,#4b1c08,#ffd873 5%,#6a2709 10%,#6a2709 90%,#ffd873 95%,#421506);box-shadow:inset 0 0 0 3px #2b0d05,0 4px 0 #301005,0 8px 15px #000b}.reelbox{position:relative;display:grid;grid-template-columns:repeat(5,1fr);height:192px;overflow:hidden;border:3px solid #1d0804;border-radius:11px;background:#140604;box-shadow:inset 0 13px 20px #000,inset 0 -13px 20px #000}.reel{position:relative;overflow:hidden;border-right:2px solid #6e421e;background:linear-gradient(90deg,#a58149,#fffce4 17%,#fffdf0 50%,#f7e9bd 82%,#8e6a38);box-shadow:inset 7px 0 8px #573b1d66,inset -7px 0 8px #573b1d66}.reel:last-child{border:0}.reel:after{content:"";position:absolute;z-index:2;inset:0;pointer-events:none;background:linear-gradient(#3b1d0fbb 0,transparent 18%,transparent 80%,#281208cc 100%);box-shadow:inset 0 9px 11px #0005,inset 0 -9px 11px #0005}.strip{position:absolute;left:0;right:0;top:0;transform:translate3d(0,0,0);will-change:transform,filter}.strip.moving{filter:blur(1.4px) saturate(1.2)}.sym{height:64px;display:grid;place-items:center;border-bottom:1px solid #9f7e4f66;color:#d30f1c;font-family:Apple Color Emoji,Segoe UI Emoji,Arial Black,sans-serif;font-size:clamp(26px,8vw,40px);line-height:1;text-shadow:0 3px 0 #721018,0 0 4px #fff;transform:translateZ(0)}.sym.s4{font:900 42px Impact,Arial Black,sans-serif;color:#ef1726;-webkit-text-stroke:2px #850815;text-shadow:0 3px #5d0509,0 0 5px #fff}.sym.s5{font:900 16px Arial Black,sans-serif;color:#fff4c4;background:radial-gradient(ellipse at center,#e22d35 0,#6f0910 52%,transparent 54%);text-shadow:0 2px #401010}.sym.win{animation:win .5s ease-in-out infinite;background:radial-gradient(circle,#fff8a9,#ffae00 55%,transparent 72%)}.shine{position:absolute;z-index:3;inset:4px 8% 55%;border-radius:50%;background:linear-gradient(#fff7,transparent);pointer-events:none}.payline{position:absolute;z-index:4;left:9px;right:9px;height:3px;opacity:0;background:#fff5a1;box-shadow:0 0 7px #fff,0 0 14px #ff3d00;pointer-events:none}.payline.on{animation:line 1s ease infinite}.p0{top:20%}.p1{top:50%}.p2{top:80%}.message{height:34px;margin:9px 2px 7px;display:grid;place-items:center;border:2px solid #a55c19;border-radius:8px;color:#ffd46a;background:#150504;box-shadow:inset 0 0 8px #000;text-align:center;font:bold 14px monospace;text-shadow:0 0 7px #f60}.console{display:grid;grid-template-columns:1fr 1.7fr;gap:8px;margin:0 3px;padding:10px 9px 13px;border:3px solid #8e4914;border-radius:9px 9px 17px 17px;background:linear-gradient(#d69a49,#63300f 37%,#2a0b06 39%,#4b1509);box-shadow:inset 0 2px #ffe0a0,0 5px #1c0704,0 9px 12px #0008;transform:perspective(300px) rotateX(4deg)}button{height:53px;border:3px solid #351006;border-radius:13px;color:#fff;font-weight:900;touch-action:none}.bet{background:linear-gradient(#42c4df,#086184 53%,#03334c);box-shadow:inset 0 4px 4px #fff8,0 4px #051e2a}.spin{background:radial-gradient(circle at 50% 32%,#8aff77,#20a72d 47%,#075b17 76%);box-shadow:inset 0 4px 5px #e3ffdc,0 4px #07350e,0 0 14px #3cff4a;font-size:18px;text-shadow:0 2px #06420d}.tray{width:49%;height:19px;margin:14px auto 0;border:4px solid #48200b;border-radius:4px 4px 10px 10px;background:#090303;box-shadow:inset 0 6px 9px #000,0 3px #c47c29}.leverTrack{position:absolute;z-index:5;right:9px;top:211px;width:20px;height:150px;border:2px solid #3d1809;border-radius:13px;background:linear-gradient(90deg,#321006,#df9a39,#52200a);box-shadow:inset 0 0 5px #000}.lever{position:absolute;left:-4px;top:8px;width:28px;height:112px;transform-origin:50% 88%;transition:transform .34s cubic-bezier(.2,.8,.2,1);touch-action:none}.lever:before{content:"";position:absolute;left:11px;top:17px;width:7px;height:86px;border:2px solid #333;border-radius:4px;background:linear-gradient(90deg,#444,#fff 48%,#666 64%,#222)}.lever:after{content:"";position:absolute;left:0;width:29px;height:29px;border:3px solid #650000;border-radius:50%;background:radial-gradient(circle at 35% 25%,#fff,#ff7777 13%,#ed1720 37%,#850007 74%);box-shadow:inset -5px -6px 7px #490000,0 4px 5px #000,0 0 9px #f22}.lever.pull{transform:rotate(36deg)}.over{position:absolute;z-index:10;inset:0;display:grid;place-items:center;text-align:center;color:#ffe5a0;background:#080100ed}.over.off{display:none}.over h2{margin:0 0 8px;color:#ff3449;text-shadow:0 0 12px #f00}.over button{padding:0 22px;background:#ffd15a;color:#271204}@keyframes lights{50%{filter:brightness(1.7)}}@keyframes win{50%{transform:scale(1.09);filter:brightness(1.4)}}@keyframes line{50%{opacity:1}}
24
+ </style><style>.reelbox{display:block;height:205px;padding:0;background:#160704}
25
+ #reelCanvas{display:block;width:100%;height:100%;border-radius:8px;touch-action:none}
26
+ </style><style>body{padding:15px 13px 29px;perspective:950px}
27
+ .machine{transform-origin:50% 88%;transform:perspective(850px) rotateX(1.2deg) translateZ(0);box-shadow:inset 0 0 0 3px #f0b64b,inset 0 0 0 8px #501708,inset 14px 0 24px #ffb3421f,inset -15px 0 25px #16030199,0 9px 0 #180503,0 17px 0 #421508,0 27px 35px #000e}
28
+ .machine:after{content:"";position:absolute;z-index:-2;left:7%;right:7%;bottom:-30px;height:30px;border-radius:50%;background:radial-gradient(ellipse,#000d 0,#0008 42%,transparent 73%);filter:blur(4px)}
29
+ .title{transform:perspective(500px) rotateX(-3deg) translateZ(8px);box-shadow:inset 0 0 0 4px #74300d,inset 0 -13px 20px #001522,0 5px 0 #351006,0 10px 16px #0009}
30
+ .frame{transform:perspective(650px) rotateX(-1.5deg) translateZ(7px);box-shadow:inset 0 0 0 3px #2b0d05,inset 0 12px 13px #fff2b21c,0 6px 0 #291006,0 13px 19px #000c}
31
+ .console{transform:perspective(420px) rotateX(8deg) translateZ(5px);transform-origin:50% 0;box-shadow:inset 0 3px #ffe4ac,0 7px #1b0704,0 15px 20px #000b}
32
+ .machine.charging{animation:crateCharge .56s cubic-bezier(.36,.07,.19,.97) both}
33
+ .machine.charging .lights{animation:warningFlash .12s steps(2) infinite}
34
+ .machine.charging .frame{animation:frameTension .56s ease both}
35
+ .machine.charging .title{animation:signTension .56s ease both}
36
+ @keyframes crateCharge{0%{transform:perspective(850px) rotateX(1.2deg) rotateZ(0) translate(0,0) scale(1)}12%{transform:perspective(850px) rotateX(2deg) rotateZ(-.8deg) translate(-2px,1px) scale(.995)}25%{transform:perspective(850px) rotateX(.2deg) rotateZ(1deg) translate(3px,-1px) scale(1.008)}39%{transform:perspective(850px) rotateX(2.2deg) rotateZ(-1.15deg) translate(-3px,1px) scale(.998)}52%{transform:perspective(850px) rotateX(.3deg) rotateZ(.9deg) translate(3px,-2px) scale(1.012)}66%{transform:perspective(850px) rotateX(2deg) rotateZ(-.55deg) translate(-2px,1px) scale(1.004)}79%{transform:perspective(850px) rotateX(.7deg) rotateZ(.35deg) translate(1px,-1px) scale(1.01)}100%{transform:perspective(850px) rotateX(1.2deg) rotateZ(0) translate(0,0) scale(1)}}
37
+ @keyframes warningFlash{50%{filter:brightness(2.2) saturate(1.8);box-shadow:0 0 21px #ffca28}}
38
+ @keyframes frameTension{45%{transform:perspective(650px) rotateX(-2.5deg) translateZ(13px) scale(1.012)}100%{transform:perspective(650px) rotateX(-1.5deg) translateZ(7px)}}
39
+ @keyframes signTension{45%{transform:perspective(500px) rotateX(-5deg) translateZ(15px)}100%{transform:perspective(500px) rotateX(-3deg) translateZ(8px)}}
40
+ </style><style>.machine.charging{animation:leverDip .28s cubic-bezier(.25,.8,.25,1) both}
41
+ .machine.charging .frame,.machine.charging .title{animation:none}
42
+ .machine.reelKick{animation:reelKick .13s cubic-bezier(.2,.8,.3,1)}
43
+ @keyframes leverDip{0%{transform:perspective(850px) rotateX(1.2deg) translateY(0) scale(1)}42%{transform:perspective(850px) rotateX(2.4deg) translateY(3px) scale(.994)}72%{transform:perspective(850px) rotateX(.5deg) translateY(-2px) scale(1.004)}100%{transform:perspective(850px) rotateX(1.2deg) translateY(0) scale(1)}}
44
+ @keyframes reelKick{0%{transform:perspective(850px) rotateX(1.2deg) translateX(0)}35%{transform:perspective(850px) rotateX(1.7deg) translateX(-1.5px) translateY(1px)}70%{transform:perspective(850px) rotateX(.8deg) translateX(1px) translateY(-1px)}100%{transform:perspective(850px) rotateX(1.2deg) translateX(0)}}
45
+ </style><style>.machine.charging,.machine.reelKick{animation:none!important}
46
+ .machine.charging .frame,.machine.charging .title,.machine.charging .lights{animation:none!important}
47
+ .leverTrack{right:9px;top:211px;width:24px;height:158px;overflow:hidden;border-radius:12px}
48
+ .lever{left:0;top:7px;width:24px;height:100px;transform:none;transform-origin:50% 50%;transition:transform .3s cubic-bezier(.2,.8,.2,1)}
49
+ .lever:before{left:8px;top:18px;width:7px;height:77px}
50
+ .lever:after{left:0;top:0;width:24px;height:24px;border-width:2px}
51
+ .lever.pull{transform:translateY(47px)}
52
+ </style><style>
53
+ /* Fixed-pivot lever: the mount stays still; only the arm swings. */
54
+ .machine{
55
+ padding-right:70px!important;
56
+ }
9
57
 
58
+ .leverTrack{
59
+ position:absolute!important;
60
+ right:8px!important;
61
+ top:205px!important;
62
+ width:56px!important;
63
+ height:150px!important;
64
+ overflow:hidden!important;
65
+ border-radius:16px!important;
66
+ transform:none!important;
67
+ }
68
+
69
+ .leverTrack:after{
70
+ content:"";
71
+ position:absolute;
72
+ left:2px;
73
+ bottom:1px;
74
+ width:27px;
75
+ height:27px;
76
+ z-index:3;
77
+ border:2px solid #5b2509;
78
+ border-radius:50%;
79
+ box-sizing:border-box;
80
+ background:radial-gradient(circle at 35% 30%,#fff6c8 0 8%,#f7bd3d 9% 25%,#9b3d08 52%,#3a1205 76%);
81
+ box-shadow:inset 0 0 0 3px #ffda67,0 2px 5px #000b;
82
+ }
83
+
84
+ .lever{
85
+ position:absolute!important;
86
+ left:2px!important;
87
+ top:auto!important;
88
+ bottom:9px!important;
89
+ width:27px!important;
90
+ height:105px!important;
91
+ transform:rotate(0deg)!important;
92
+ transform-origin:13.5px 95px!important;
93
+ transition:transform .34s cubic-bezier(.2,.88,.25,1.08)!important;
94
+ z-index:2!important;
95
+ }
96
+
97
+ .lever:before{
98
+ left:10px!important;
99
+ top:18px!important;
100
+ width:7px!important;
101
+ height:80px!important;
102
+ border-radius:5px!important;
103
+ background:linear-gradient(90deg,#321003 0,#f3c56f 20%,#fff5cf 43%,#9b4c18 72%,#260b02 100%)!important;
104
+ box-shadow:0 2px 4px #000b!important;
105
+ }
106
+
107
+ .lever:after{
108
+ left:1px!important;
109
+ top:0!important;
110
+ width:24px!important;
111
+ height:24px!important;
112
+ border-width:2px!important;
113
+ }
114
+
115
+ .lever.pull{
116
+ transform:rotate(17deg)!important;
117
+ }
118
+
119
+ </style><style>
120
+ /* Front-facing pull: fixed lower pivot with perspective foreshortening. */
121
+ .lever{
122
+ transform:none!important;
123
+ transform-origin:50% 100%!important;
124
+ transition:none!important;
125
+ }
126
+
127
+ .lever:before{
128
+ transform:scaleY(1)!important;
129
+ transform-origin:50% 100%!important;
130
+ transition:transform .34s cubic-bezier(.2,.85,.25,1)!important;
131
+ }
132
+
133
+ .lever:after{
134
+ transform:translateY(0) scale(1)!important;
135
+ transform-origin:50% 50%!important;
136
+ transition:transform .34s cubic-bezier(.2,.85,.25,1.08)!important;
137
+ }
138
+
139
+ .lever.pull{
140
+ transform:none!important;
141
+ }
142
+
143
+ .lever.pull:before{
144
+ transform:scaleY(.56)!important;
145
+ }
146
+
147
+ .lever.pull:after{
148
+ transform:translateY(36px) scale(1.16)!important;
149
+ }
150
+
151
+ </style><style>
152
+ /* Rigid front-pulling lever assembly. */
153
+ .machine{padding-right:62px!important}
154
+ .leverTrack{right:8px!important;top:205px!important;width:47px!important;height:150px!important;overflow:hidden!important}
155
+ .leverTrack:after{left:9px!important;bottom:5px!important;width:27px!important;height:27px!important;z-index:4!important}
156
+ .lever{left:0!important;right:0!important;bottom:12px!important;top:auto!important;width:100%!important;height:112px!important;transform:none!important}
157
+ .lever:before,.lever:after{display:none!important}
158
+ .leverArm{position:absolute;left:20px;bottom:7px;width:7px;height:91px;border:2px solid #292929;border-radius:5px;box-sizing:border-box;background:linear-gradient(90deg,#3c3c3c,#fff 43%,#8a8a8a 68%,#252525);box-shadow:0 2px 4px #000b;transform:scaleY(1);transform-origin:50% 100%;transition:transform .34s cubic-bezier(.2,.82,.25,1)}
159
+ .leverKnob{position:absolute;left:-10px;top:-13px;width:27px;height:27px;border:3px solid #650000;border-radius:50%;box-sizing:border-box;background:radial-gradient(circle at 35% 25%,#fff,#ff7777 13%,#ed1720 37%,#850007 74%);box-shadow:inset -5px -6px 7px #490000,0 4px 5px #000,0 0 9px #f22;transform:scaleY(1) scale(1);transition:transform .34s cubic-bezier(.2,.82,.25,1)}
160
+ .lever.pull .leverArm{transform:scaleY(.52)}
161
+ .lever.pull .leverKnob{transform:scaleY(1.9231) scale(1.12)}
162
+
163
+ </style><style>
164
+ .leverTrack{display:none!important}
165
+ html,body{min-height:0!important}
166
+ body{padding:4px!important}
167
+ .machine{padding:8px 10px 10px!important;border-width:3px!important;border-radius:22px!important;box-shadow:inset 0 0 0 2px #e6a139,inset 0 0 0 5px #5d210b,inset 0 14px 25px #ffb52a22,0 5px 0 #210705,0 9px 16px #000c!important}
168
+ .machine:before{inset:6px!important;border-radius:17px!important}
169
+ .lights{height:7px!important;margin:0 12px 5px!important}
170
+ .title{padding:7px 4px 5px!important;font-size:21px!important}
171
+ .jackpot{margin:3px auto 5px!important;padding:3px!important}
172
+ .stats{margin:0 2px 6px!important;padding:4px!important}
173
+ .stat b{font-size:13px!important;margin-top:1px!important}
174
+ .frame{padding:6px!important;border-width:4px!important;border-radius:14px!important}
175
+ .reelbox{height:150px!important;border-radius:9px!important}
176
+ .message{height:29px!important;margin:6px 2px 5px!important;font-size:12px!important}
177
+ .console{gap:7px!important;margin:0 3px!important;padding:7px 8px 9px!important}
178
+ button{height:44px!important}
179
+ .spin{font-size:16px!important}
180
+ .tray{height:14px!important;margin-top:9px!important;border-width:3px!important}
181
+
182
+ </style><style>
183
+ body{background:radial-gradient(circle at 50% 8%,#1d6a50,#0f4938 48%,#07291f)!important}
184
+ .machine{border-color:#071f18!important;background:linear-gradient(110deg,#061e17,#1b644b 10%,#0b382a 24%,#15543f 54%,#092f24 82%,#28775a 94%,#071f18)!important;box-shadow:inset 0 0 0 2px #b9954d,inset 0 0 0 5px #163f31,inset 0 14px 24px #73d2a31c,0 5px 0 #041a13,0 9px 16px #000b!important}
185
+ .machine:before{border-color:#b59349!important;box-shadow:inset 0 0 9px #4fa57a66!important}
186
+ .lights{border-color:#123d2f!important;background:repeating-radial-gradient(circle at 6px 50%,#dfffdc 0 2px,#82c878 3px 5px,#164936 6px 12px)!important;box-shadow:0 0 9px #67b881!important}
187
+ .title{border-color:#b99a54!important;color:#e9e3bc!important;background:radial-gradient(ellipse at 50% 0,#2b8a6c,#11513f 58%,#082a24)!important;box-shadow:inset 0 0 0 4px #244f3d,inset 0 -10px 17px #061e19,0 4px 0 #071d16!important;text-shadow:0 2px #193f31,2px 0 #193f31,-2px 0 #193f31!important}
188
+ .jackpot{border-color:#a98c4d!important;color:#ded7ad!important;background:linear-gradient(#245d48,#0b3025)!important;box-shadow:inset 0 2px 5px #8ed3a233!important}
189
+ .stats,.message{border-color:#537d64!important;background:linear-gradient(#102d24,#061812)!important}
190
+ .stat{border-color:#416452!important;color:#91b59f!important}.stat b,.message{color:#e3dfbb!important;text-shadow:0 0 6px #62a77d!important}
191
+ .frame{border-color:#315c47!important;background:linear-gradient(90deg,#09271e,#b49a59 5%,#174936 10%,#174936 90%,#b49a59 95%,#09271e)!important;box-shadow:inset 0 0 0 3px #071b15,0 4px 0 #09271e,0 8px 15px #000a!important}
192
+ .reelbox{border-color:#071c15!important;background:#071a14!important;box-shadow:inset 0 9px 15px #0009,inset 0 -9px 15px #0009!important}
193
+ .shine{display:none!important}
194
+ .console{border-color:#416a53!important;background:linear-gradient(#9c8c59,#315d48 37%,#0a2e22 39%,#123e2f)!important;box-shadow:inset 0 2px #d9c992,0 5px #061d16,0 9px 12px #0008!important}
195
+ .bet{background:linear-gradient(#4f9a77,#216348 53%,#103d2d)!important;box-shadow:inset 0 4px 4px #d8ffe055,0 4px #092a20!important}
196
+ .spin{background:radial-gradient(circle at 50% 32%,#a8d96f,#4b8d46 47%,#1e542f 76%)!important;box-shadow:inset 0 4px 5px #e8ffd488,0 4px #12351e,0 0 12px #79b85c88!important}
197
+ .tray{border-color:#244d3a!important;background:#061a13!important;box-shadow:inset 0 6px 9px #000,0 3px #897945!important}
198
+
199
+ </style><style>
200
+ .payline{height:3px!important;background:linear-gradient(90deg,transparent,#dfff8a 18%,#fff5b0 50%,#7fe091 82%,transparent)!important;box-shadow:0 0 7px #efffa8,0 0 15px #65c984!important;transform:scaleX(.15);transform-origin:50% 50%}
201
+ .payline.on{opacity:1!important;animation:jungleLine 1.15s cubic-bezier(.2,.8,.2,1) infinite!important}
202
+ .machine.winner .message{animation:jungleDisplay .7s ease-in-out 2!important}
203
+ .machine.winner .lights{animation:jungleLights .22s steps(2) 6!important}
204
+ .machine.winner .title{animation:jungleTitle .7s ease-in-out 2!important}
205
+ @keyframes jungleLine{0%{transform:scaleX(.08);filter:brightness(1)}45%{transform:scaleX(1);filter:brightness(1.8)}75%,100%{transform:scaleX(1);opacity:0}}
206
+ @keyframes jungleDisplay{50%{color:#fff8bd;box-shadow:inset 0 0 14px #7ddc8a,0 0 13px #74ce82;filter:brightness(1.35)}}
207
+ @keyframes jungleLights{50%{filter:brightness(2.2) saturate(1.4)}}
208
+ @keyframes jungleTitle{50%{filter:brightness(1.35);text-shadow:0 0 12px #cfff91,0 2px #193f31}}
209
+
210
+ </style><style>
211
+ .machine{
212
+ box-shadow:inset 0 0 0 2px #b9954d,inset 0 0 0 5px #163f31,inset 0 14px 24px #73d2a31c!important;
213
+ }
214
+
215
+ </style><style>
216
+ html,body{width:100%!important}
217
+ body{padding:0!important}
218
+ .machine{width:100%!important;margin:0!important}
219
+
220
+ </style></head><body><div class="machine" id="machine"><div class="lights"></div><div class="title">FRUIT BONANZA</div><div class="jackpot">JACKPOT · 10,000 CREDITS</div><div class="stats"><div class="stat">CREDITS<b id="credits">500</b></div><div class="stat">BET<b id="betValue">10</b></div><div class="stat">BEST WIN<b id="best">0</b></div></div><div class="frame"><div id="reelbox" class="reelbox"><canvas id="reelCanvas"></canvas></div><div class="shine"></div><i class="payline p0"></i><i class="payline p1"></i><i class="payline p2"></i></div><div id="message" class="message">SPIN TO PLAY</div><div class="console"><button id="bet" class="bet">BET +</button><button id="spin" class="spin">SPIN</button></div><div class="tray"></div><div class="leverTrack"><div id="lever" class="lever"><i class="leverArm"><i class="leverKnob"></i></i></div></div><div id="over" class="over off"><div><h2>GAME OVER</h2><p>No more credits.<br>Best Win: <b id="finalBest">0</b></p><button id="restart">NEW GAME</button></div></div></div><script>(function(){var labels=['\\u{1F352}','\\u{1F34B}','\\u{1F514}','\\u{1F48E}','7','BAR'],weights=[30,25,18,12,8,7],pays=[2,3,5,8,12,20],credits=500,bet=10,best=0,busy=false,winCells=[],reels=[],sparks=[],winUntil=0,canvas=document.getElementById('reelCanvas'),ctx=canvas.getContext('2d'),C=document.getElementById('credits'),BV=document.getElementById('betValue'),BS=document.getElementById('best'),MSG=document.getElementById('message'),SP=document.getElementById('spin'),BT=document.getElementById('bet'),LV=document.getElementById('lever'),OV=document.getElementById('over'),FB=document.getElementById('finalBest'),MCH=document.getElementById('machine'),PL=document.querySelectorAll('.payline'),D=Math.min(devicePixelRatio||1,2),W,H,last=0,startTime=0;function pick(){var n=Math.random()*100,s=0;for(var i=0;i<weights.length;i++){s+=weights[i];if(n<s)return i}return 0}function buildStrip(){var a=[];for(var i=0;i<80;i++)a.push(pick());return a}function resize(){var r=canvas.getBoundingClientRect();W=r.width;H=r.height;canvas.width=Math.round(W*D);canvas.height=Math.round(H*D);ctx.setTransform(D,0,0,D,0,0);draw()}function ui(){C.textContent=credits;BV.textContent=bet;BS.textContent=best}function clearWins(){winCells=[];sparks=[];winUntil=0;MCH.classList.remove('winner');for(var i=0;i<PL.length;i++)PL[i].classList.remove('on')}for(var c=0;c<5;c++)reels.push({strip:buildStrip(),pos:Math.floor(Math.random()*60),speed:0,stop:0,stopped:true,decelStart:null,from:0,target:0});function mod(n,m){return((n%m)+m)%m}function symbol(col,row,index,x,y,w,h,curve){var value=reels[col].strip[mod(index,reels[col].strip.length)],won=winCells.some(function(v){return v[0]===col&&v[1]===row}),pulse=won?1+.09*Math.sin(performance.now()/85):1;if(won){var glow=ctx.createRadialGradient(x+w/2,y+h/2,2,x+w/2,y+h/2,w*.72);glow.addColorStop(0,'rgba(255,247,159,.92)');glow.addColorStop(.48,'rgba(119,205,94,.62)');glow.addColorStop(1,'rgba(44,126,75,0)');ctx.fillStyle=glow;ctx.fillRect(x,y,w,h)}ctx.save();ctx.translate(x+w/2,y+h/2);ctx.scale(pulse,curve*pulse);ctx.textAlign='center';ctx.textBaseline='middle';ctx.shadowColor=won?'#d9ff75':'rgba(18,55,38,.28)';ctx.shadowBlur=won?16:3;if(value===4){ctx.font='900 '+Math.floor(h*.72)+'px Impact,Arial Black';ctx.lineWidth=4;ctx.strokeStyle='#790914';ctx.strokeText('7',0,2);ctx.fillStyle='#f31c2b';ctx.fillText('7',0,2)}else if(value===5){ctx.font='900 '+Math.floor(h*.29)+'px Arial Black';ctx.fillStyle='#861019';ctx.beginPath();ctx.ellipse(0,0,w*.38,h*.25,0,0,Math.PI*2);ctx.fill();ctx.fillStyle='#fff0b4';ctx.fillText('BAR',0,1)}else{ctx.font=Math.floor(h*.56)+'px Apple Color Emoji,Segoe UI Emoji,sans-serif';ctx.fillText(labels[value],0,1)}ctx.restore()}function draw(){if(!W||!H)return;ctx.clearRect(0,0,W,H);var rw=W/5,rh=H/3;for(var c=0;c<5;c++){var x=c*rw,reel=reels[c],base=Math.floor(reel.pos),frac=reel.pos-base;var bg=ctx.createLinearGradient(x,0,x+rw,0);bg.addColorStop(0,'#8f6a39');bg.addColorStop(.16,'#fff8d8');bg.addColorStop(.5,'#fffdf0');bg.addColorStop(.84,'#f0dfad');bg.addColorStop(1,'#79552c');ctx.fillStyle=bg;ctx.fillRect(x,0,rw,H);ctx.save();ctx.beginPath();ctx.rect(x,0,rw,H);ctx.clip();ctx.filter=reel.speed>5?'blur(1.3px)':'none';for(var k=-1;k<5;k++){var y=(k-frac)*rh,row=k;var center=(y+rh/2)/H,curve=.78+.22*Math.cos((center-.5)*Math.PI);symbol(c,row,base+k,x,y,rw,rh,curve);ctx.strokeStyle='rgba(92,57,20,.25)';ctx.beginPath();ctx.moveTo(x,y+rh);ctx.lineTo(x+rw,y+rh);ctx.stroke()}ctx.restore();ctx.filter='none';if(c){ctx.fillStyle='#573512';ctx.fillRect(x-1,0,2,H)}}var shade=ctx.createLinearGradient(0,0,0,H);shade.addColorStop(0,'rgba(23,7,2,.72)');shade.addColorStop(.13,'rgba(0,0,0,0)');shade.addColorStop(.48,'rgba(255,255,255,0)');shade.addColorStop(.86,'rgba(0,0,0,0)');shade.addColorStop(1,'rgba(20,5,1,.78)');ctx.fillStyle=shade;ctx.fillRect(0,0,W,H);ctx.strokeStyle='rgba(20,61,43,.45)';ctx.strokeRect(.5,.5,W-1,H-1);drawSparks()}function drawSparks(){var now=performance.now();for(var i=0;i<sparks.length;i++){var s=sparks[i],t=(now-s.born)/1000;if(t<0)continue;var a=Math.max(0,1-t/1.25);if(!a)continue;ctx.save();ctx.globalAlpha=a;ctx.translate(s.x+s.vx*t,s.y+s.vy*t+85*t*t);ctx.rotate(t*s.spin);ctx.fillStyle=s.color;ctx.shadowColor=s.color;ctx.shadowBlur=5;ctx.fillRect(-s.size/2,-s.size/2,s.size,s.size*1.8);ctx.restore()}}function celebrate(now){draw();if(now<winUntil)requestAnimationFrame(celebrate);else{sparks=[];MCH.classList.remove('winner');draw()}}function startWin(){var colors=['#e7d277','#9ce277','#4ebc78','#fff4b0'];sparks=[];for(var i=0;i<34;i++)sparks.push({x:W*(.1+Math.random()*.8),y:H*(.28+Math.random()*.44),vx:(Math.random()-.5)*70,vy:-45-Math.random()*75,born:performance.now()+Math.random()*180,size:2+Math.random()*3,spin:(Math.random()-.5)*12,color:colors[i%colors.length]});winUntil=performance.now()+1450;MCH.classList.add('winner');requestAnimationFrame(celebrate)}function frame(now){if(!busy)return;var dt=Math.min((now-last)/1000,.035);last=now;var stopped=0;for(var c=0;c<5;c++){var r=reels[c],elapsed=now-startTime;if(elapsed<180){r.speed=23*(elapsed/180);r.pos+=r.speed*dt}else if(elapsed<r.stop){r.speed=23;r.pos+=r.speed*dt}else{if(r.decelStart===null){r.decelStart=now;r.from=r.pos;r.target=Math.ceil(r.pos)+7}var p=Math.min(1,(now-r.decelStart)/720),ease=1-Math.pow(1-p,4);r.pos=r.from+(r.target-r.from)*ease;r.speed=(r.target-r.from)*4*Math.pow(1-p,3)/.72;if(p>=1){r.pos=r.target;r.speed=0;r.stopped=true}}if(r.stopped)stopped++}draw();if(stopped===5){busy=false;setTimeout(evaluate,150)}else requestAnimationFrame(frame)}function boardValue(col,row){var r=reels[col];return r.strip[mod(Math.round(r.pos)+row,r.strip.length)]}function evaluate(){LV.classList.remove('pull');var patterns=[[0,0,0,0,0],[1,1,1,1,1],[2,2,2,2,2],[0,1,2,1,0],[2,1,0,1,2]],total=0,wins=[];patterns.forEach(function(p,pi){var a=boardValue(0,p[0]),count=1;for(var x=1;x<5&&boardValue(x,p[x])===a;x++)count++;if(count>=3){total+=Math.floor(bet*pays[a]*(count===3?1:count===4?2:5));wins.push({p:p,n:count,line:pi})}});if(total){credits+=total;best=Math.max(best,total);wins.forEach(function(w){for(var x=0;x<w.n;x++)winCells.push([x,w.p[x]]);if(w.line<3)PL[w.line].classList.add('on')});MSG.textContent=total>=bet*20?'JACKPOT +'+total:'WIN +'+total;startWin()}else MSG.textContent='TRY AGAIN';SP.disabled=false;BT.disabled=false;ui();draw();if(credits<10)setTimeout(gameover,800);else if(bet>credits){bet=10;ui()}}function spin(){if(busy||credits<bet)return;busy=true;clearWins();credits-=bet;ui();SP.disabled=true;BT.disabled=true;MSG.textContent='GOOD LUCK';LV.classList.add('pull');startTime=performance.now();last=startTime;for(var c=0;c<5;c++){reels[c].speed=0;reels[c].stop=1050+c*220;reels[c].stopped=false;reels[c].decelStart=null}requestAnimationFrame(frame)}function gameover(){FB.textContent=best;OV.className='over'}BT.onclick=function(){if(busy)return;bet=bet===10?20:bet===20?50:10;if(bet>credits)bet=10;ui()};SP.onclick=spin;LV.onclick=spin;document.getElementById('restart').onclick=function(){credits=500;bet=10;best=0;busy=false;OV.className='over off';MSG.textContent='SPIN TO PLAY';SP.disabled=false;BT.disabled=false;clearWins();ui();draw()};addEventListener('resize',resize);ui();resize()})();</script></body></html>`;
221
+
222
+ /**
223
+ * Generate the slot machine HTML payload.
224
+ *
225
+ * @param {Object} [options]
226
+ * @param {string} [options.title='FRUIT BONANZA'] - Machine title
227
+ * @param {number} [options.startingCredits=500] - Starting credits
228
+ * @returns {string} Complete HTML string for the slot machine
229
+ */
10
230
  export const generateSlotMachineHtml = (options = {}) => {
11
- const title = escapeHtml(options.title || 'FRUIT BONANZA');
12
- const startingCredits = Number.isFinite(Number(options.startingCredits)) && Number(options.startingCredits) >= 0
13
- ? Math.floor(Number(options.startingCredits)) : 500;
14
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>
15
- *{box-sizing:border-box}body{margin:0;padding:12px;background:#160704;color:#ffe8a3;font:14px system-ui,sans-serif}.machine{max-width:540px;margin:auto;padding:16px;border:4px solid #e6a139;border-radius:22px;background:linear-gradient(135deg,#230805,#8c3515,#2b0906);box-shadow:0 8px 18px #000a}.title{text-align:center;font-size:24px;font-weight:900;color:#fff2a1;text-shadow:0 2px #8f1c0c}.stats{display:flex;justify-content:space-around;margin:10px 0;padding:8px;border:2px solid #a55c19;border-radius:8px;background:#100303}.reels{display:grid;grid-template-columns:repeat(5,1fr);gap:5px;padding:8px;border:5px solid #9b4e12;border-radius:14px;background:#140604}.reel{height:78px;display:grid;place-items:center;border-radius:6px;background:linear-gradient(#fffce4,#d7bf87);color:#b20f1c;font-size:38px;font-weight:900;overflow:hidden}.reel.spin{animation:spin .16s linear infinite}.message{min-height:30px;margin:10px;text-align:center;font-weight:800}.controls{display:flex;gap:8px}.controls button{flex:1;padding:12px;border:2px solid #351006;border-radius:10px;font-weight:900;cursor:pointer}.bet{background:#1685a5;color:white}.spin{background:#27a93b;color:white}.spin:disabled{opacity:.5}@keyframes spin{50%{transform:translateY(5px);filter:blur(1px)}}small{display:block;margin-top:10px;text-align:center;color:#d6a15b}</style></head><body><main class="machine"><div class="title">${title}</div><div class="stats"><span>Credits <b id="credits">${startingCredits}</b></span><span>Bet <b id="bet">10</b></span><span>Best <b id="best">0</b></span></div><div class="reels">${[0,1,2,3,4].map((index) => `<div class="reel" id="r${index}">🍒</div>`).join('')}</div><div class="message" id="message">SPIN TO PLAY</div><div class="controls"><button class="bet" id="betButton" type="button">BET</button><button class="spin" id="spinButton" type="button">SPIN</button></div><small>Three matching symbols win. This is a local chat-embedded game.</small></main><script>
16
- (()=>{const symbols=['🍒','🍋','🔔','💎','7️⃣','🅱️'];let credits=${startingCredits},bet=10,best=0,busy=false;const $=id=>document.getElementById(id);const ui=()=>{$('credits').textContent=credits;$('bet').textContent=bet;$('best').textContent=best};$('betButton').onclick=()=>{if(busy)return;bet=bet===10?20:bet===20?50:10;if(bet>credits)bet=10;ui()};$('spinButton').onclick=()=>{if(busy||credits<bet)return;busy=true;credits-=bet;$('message').textContent='GOOD LUCK';ui();const reels=[0,1,2,3,4].map(i=>$('r'+i));reels.forEach(r=>r.classList.add('spin'));setTimeout(()=>{const values=reels.map(r=>{r.classList.remove('spin');const value=symbols[Math.floor(Math.random()*symbols.length)];r.textContent=value;return value});const counts=values.reduce((map,value)=>(map[value]=(map[value]||0)+1,map),{});const match=Math.max(...Object.values(counts));const win=match>=3?bet*(match===5?20:match===4?8:3):0;credits+=win;best=Math.max(best,win);$('message').textContent=win?'WIN +'+win:(credits?'TRY AGAIN':'GAME OVER');busy=false;ui()},700)};ui()})();</script></body></html>`;
231
+ let html = SLOT_MACHINE_HTML;
232
+ if (options.title && options.title !== 'FRUIT BONANZA') {
233
+ html = html.replace('FRUIT BONANZA', options.title);
234
+ }
235
+ if (options.startingCredits && options.startingCredits !== 500) {
236
+ html = html.replace(/credits=500/, `credits=${options.startingCredits}`);
237
+ html = html.replace(/>500</, `>${options.startingCredits}<`);
238
+ }
239
+ return html;
240
+ };
241
+
242
+ /**
243
+ * Prepare a slot machine message using the FOAHtmlPrimitiveDemoDONOTUSE primitive.
244
+ * Returns a wrapped botForwardedMessage ready for relayMessage.
245
+ *
246
+ * @param {Object} [options]
247
+ * @param {string} [options.title] - Machine title
248
+ * @param {number} [options.startingCredits] - Starting credits
249
+ * @returns {Object} Wrapped botForwardedMessage
250
+ */
251
+ export const prepareSlotMachineMessage = (options = {}) => {
252
+ const html = generateSlotMachineHtml(options);
253
+ // Lazy import to avoid circular deps
254
+ const { prepareHtmlMessage } = require('./rich-message-utils.js');
255
+ return prepareHtmlMessage({ html });
17
256
  };
18
257
 
19
258
  export default generateSlotMachineHtml;
@@ -1385,6 +1385,7 @@ export const generateWAMessageContent = async (message, options) => {
1385
1385
  else if (key) {
1386
1386
  key.contextInfo = { isSpoiler: true };
1387
1387
  }
1388
+ m = { spoilerMessage: { message: m } };
1388
1389
  }
1389
1390
  delete message.mediaSpoilerMode;
1390
1391
  }
@@ -485,7 +485,7 @@ export const prepareRichImageMessage = ({ url = '', mimeType = 'image/png' } = {
485
485
  };
486
486
 
487
487
  /** Build a rich response containing clickable inline links. */
488
- export const prepareRichLinkMessage = ({ links = [], text = '' }= {}) => {
488
+ export const prepareRichLinkMessage = ({ links = [], text = '' } = {}) => {
489
489
  const entities = [];
490
490
  let combined = '';
491
491
  for (const [index, link] of links.entries()) {
@@ -501,8 +501,8 @@ export const prepareRichLinkMessage = ({ links = [], text = '' }= {}) => {
501
501
  export const prepareRichGenerationMessage = (content = {}) => prepareRichResponseMessage({ ...content, noDonation: true });
502
502
 
503
503
  /** Build an HTML primitive payload. Use only with an HTML-capable, authorized client. */
504
- export const prepareHtmlMessage = ({ html = '' } = {}) => {
505
- const unified = { response_id: randomUUID(), sections: [{ view_model: { primitive: { __typename: 'FOAHtmlPrimitiveDemoDONOTUSE', html: String(html) }, __typename: 'GenAISingleLayoutViewModel' } }] };
504
+ export const prepareHtmlMessage = ({ html = '', trustedSources = [] } = {}) => {
505
+ const unified = { response_id: randomUUID(), sections: [{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitive: { __typename: 'FOAHtmlPrimitiveDemoDONOTUSE', trusted_sources: trustedSources, payload: String(html).trim() }, __typename: 'GenAISingleLayoutViewModel' } }] };
506
506
  const rich = proto.AIRichResponseMessage.create({ submessages: [], messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 } });
507
507
  return wrapToBotForwardedMessage(rich);
508
508
  };
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.11.12",
3
+ "version": "2.12.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",