@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 +175 -2
- package/lib/Socket/messages-send.js +193 -21
- package/lib/Utils/games/slot-machine.js +250 -11
- package/lib/Utils/messages.js +1 -0
- package/lib/Utils/rich-message-utils.js +3 -3
- package/lib/index.d.ts +74 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
|
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 (
|
|
622
|
+
if (isRetryResend) {
|
|
613
623
|
if (!isGroup && !isStatus) {
|
|
614
624
|
additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
|
|
615
625
|
}
|
|
616
|
-
const { user, device } = jidDecode(
|
|
617
|
-
devices.push({
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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(
|
|
849
|
-
const isMe = areJidsSameUser(
|
|
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:
|
|
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 (
|
|
939
|
+
if (isRetryResend) {
|
|
926
940
|
if (isJidGroup(destinationJid)) {
|
|
927
941
|
stanza.attrs.to = destinationJid;
|
|
928
|
-
stanza.attrs.participant =
|
|
942
|
+
stanza.attrs.participant = retryParticipants.jid;
|
|
929
943
|
}
|
|
930
|
-
else if (areJidsSameUser(
|
|
931
|
-
stanza.attrs.to =
|
|
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 =
|
|
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:
|
|
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 && !
|
|
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
|
-
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
|
7
|
-
|
|
8
|
-
}
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
package/lib/Utils/messages.js
CHANGED
|
@@ -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',
|
|
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.
|
|
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",
|