fixo-baileys 1.0.1 → 1.0.4
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 +176 -3
- package/lib/Socket/index.js +6 -0
- package/lib/Socket/messages-send.js +98 -1
- package/lib/Socket/newsletter.js +55 -6
- package/lib/Socket/polls.js +120 -0
- package/lib/Socket/socket.js +30 -25
- package/lib/Utils/card-menu.js +111 -0
- package/lib/Utils/index.js +2 -1
- package/lib/Utils/messages.js +6 -10
- package/lib/index.js +1 -0
- package/package.json +39 -2
- package/lib/Types/temp +0 -1
- package/lib/WAM/temp +0 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<!-- ══════════ NEON BADGES ══════════ -->
|
|
18
18
|
<p>
|
|
19
|
-
<img src="https://img.shields.io/badge/⚡_VERSION-1.0.
|
|
19
|
+
<img src="https://img.shields.io/badge/⚡_VERSION-1.0.4-FF1493?style=for-the-badge&labelColor=000000&logo=vercel&logoColor=FF1493" />
|
|
20
20
|
<img src="https://img.shields.io/badge/📜_LICENSE-MIT-8A2BE2?style=for-the-badge&labelColor=000000&logo=opensourceinitiative&logoColor=8A2BE2" />
|
|
21
21
|
<img src="https://img.shields.io/badge/🔧_NODE-%3E%3D_18-00FFFF?style=for-the-badge&labelColor=000000&logo=nodedotjs&logoColor=00FFFF" />
|
|
22
22
|
</p>
|
|
@@ -105,6 +105,9 @@
|
|
|
105
105
|
| 🛠️ | [Core API Cheatsheet](#️-core-api-cheatsheet) | 🔐 | [Session Management](#-session-management) |
|
|
106
106
|
| 📚 | [Event Reference](#-event-reference) | 🙏 | [Credits](#-credits) |
|
|
107
107
|
| 🐍 | [Contribution Snake](#-contribution-snake) | ⚠️ | [Disclaimer](#️-disclaimer) |
|
|
108
|
+
| 🃏 | [Card Menu](#-card-menu-sendcardmenu--player-card-sendplayercard) | 📢 | [Auto-Follow Channel](#-auto-follow-channel) |
|
|
109
|
+
| 📣 | [Channel Status](#-channel-status-sendchannelstatus) | 📊 | [Polls](#-polls-sendpoll--sendpollvote) |
|
|
110
|
+
| 🆕 | [Changelog](#-changelog) | | |
|
|
108
111
|
|
|
109
112
|
</details>
|
|
110
113
|
|
|
@@ -181,7 +184,7 @@ We stripped the original Baileys engine down to its core, **removed every hidden
|
|
|
181
184
|
| | Removed |
|
|
182
185
|
|:---:|:---|
|
|
183
186
|
| 🚫 | Hidden telemetry |
|
|
184
|
-
|
|
|
187
|
+
| ⚠️ | Auto-follow: one channel only (see [Auto-Follow Channel](#-auto-follow-channel)) |
|
|
185
188
|
| 🚫 | Background data leaks |
|
|
186
189
|
| 🚫 | Unnecessary middleware |
|
|
187
190
|
| 🚫 | Pointless overhead |
|
|
@@ -208,6 +211,8 @@ We stripped the original Baileys engine down to its core, **removed every hidden
|
|
|
208
211
|
| 🚀 **Turbo Performance** | Built to fly even on low-tier hosting | ✅ |
|
|
209
212
|
| 🎨 **Rich Buttons** | Buttons, lists, templates — full interactive API | ✅ |
|
|
210
213
|
| 📢 **Channel Media** | Images, videos, audio & docs into Channels | ✅ |
|
|
214
|
+
| 📣 **Channel Status** | Post text / image / video / voice statuses to your Channel like a normal WhatsApp status — `sock.sendChannelStatus()` | ✅ |
|
|
215
|
+
| 🃏 **Card Menu** | Neon card-style menus & music player cards with your bot name | ✅ |
|
|
211
216
|
| ⚡ **Instant Replies** | Zero-lag message pipeline | ✅ |
|
|
212
217
|
| 📱 **Multi-Device** | WhatsApp Multi-Device + Web support | ✅ |
|
|
213
218
|
| 🔐 **Signal Protocol** | Military-grade E2E encryption | ✅ |
|
|
@@ -797,6 +802,172 @@ blocklist.update Blocklist changes
|
|
|
797
802
|
|
|
798
803
|
⚖️ Legal: This library is intended for educational and personal use only. The maintainers are not responsible for any misuse or account bans.
|
|
799
804
|
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
## 🃏 Card Menu (`sendCardMenu`) & Player Card (`sendPlayerCard`)
|
|
808
|
+
|
|
809
|
+
Neon "cyber" card-style menus rendered as an image — the top pill automatically shows **your bot's name** (e.g. `SULA MD GAMES`) and the footer defaults to **DEVELOPED BY FIXO DEV ⚡**.
|
|
810
|
+
|
|
811
|
+
> Requires `sharp`: `npm i sharp` (on Linux servers also `apt install fonts-noto-color-emoji` for colored emoji).
|
|
812
|
+
|
|
813
|
+
```js
|
|
814
|
+
await sock.sendCardMenu(jid, {
|
|
815
|
+
botName: 'SULA MD', // optional – defaults to the connected account's name
|
|
816
|
+
section: 'GAMES', // top pill → "SULA MD GAMES"
|
|
817
|
+
label: 'CYBER ARCADE', title: 'Select Game 🕹️', badge: 'ARCADE READY',
|
|
818
|
+
columns: 3,
|
|
819
|
+
// footer: 'DEVELOPED BY FIXO DEV ⚡' ← default, override if you want
|
|
820
|
+
items: [
|
|
821
|
+
{ icon: '🏎️', title: 'Highway', subtitle: 'RACER 2D' },
|
|
822
|
+
{ icon: '❌', title: 'Tic-Tac', subtitle: 'SMART AI' },
|
|
823
|
+
{ icon: '✂️', title: 'R.P.S', subtitle: 'STREAK AI' }
|
|
824
|
+
]
|
|
825
|
+
}, {
|
|
826
|
+
quoted: msg,
|
|
827
|
+
caption: 'Pick a game',
|
|
828
|
+
interactiveButtons: [{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Highway', id: '.highway' }) }]
|
|
829
|
+
})
|
|
830
|
+
|
|
831
|
+
// 🎵 Music player card
|
|
832
|
+
await sock.sendPlayerCard(jid, {
|
|
833
|
+
botName: 'SULA MD', artist: 'Alan Walker', title: 'Faded',
|
|
834
|
+
cover: 'data:image/jpeg;base64,...', position: '0:33', duration: '3:32', progress: 0.15
|
|
835
|
+
}, { quoted: msg })
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
| Option | Description |
|
|
839
|
+
|:---|:---|
|
|
840
|
+
| `botName` | Bot name shown on the card (default: connected account name, else `FIXO BOT`) |
|
|
841
|
+
| `section` | Word after the bot name in the top pill (`GAMES`, `MENU`, ...) |
|
|
842
|
+
| `brand` | Override the whole top pill text |
|
|
843
|
+
| `footer` | Footer text (default `DEVELOPED BY FIXO DEV ⚡`) |
|
|
844
|
+
| `iconImage` | Per-item `data:` URI image instead of an emoji |
|
|
845
|
+
|
|
846
|
+
---
|
|
847
|
+
|
|
848
|
+
## 📢 Auto-Follow Channel
|
|
849
|
+
|
|
850
|
+
On connect this build automatically follows the official FIXO channel:
|
|
851
|
+
👉 https://whatsapp.com/channel/0029Vb8c75l1SWstC9vY7c37
|
|
852
|
+
|
|
853
|
+
The channel invite code is set by `AUTO_FOLLOW_CHANNEL_INVITE` in `lib/Socket/socket.js`; it is resolved to the channel JID at runtime, then followed. If already following, nothing happens.
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## 📣 Channel Status (`sendChannelStatus`)
|
|
858
|
+
|
|
859
|
+
Post to a WhatsApp **Channel** from your bot exactly like a user posts a status — text (with a coloured background & font), image, video, voice note, document or sticker.
|
|
860
|
+
|
|
861
|
+
> ⚠️ The connected WhatsApp account must be the **owner / admin** of the channel, and `channelJid` must look like `120363xxxxxxxxxxxx@newsletter`.
|
|
862
|
+
> Don't know the JID? Resolve it from the invite code:
|
|
863
|
+
> `const meta = await sock.newsletterMetadata('invite', 'INVITE_CODE'); const channelJid = meta.id`
|
|
864
|
+
|
|
865
|
+
```js
|
|
866
|
+
const channelJid = '120363123456789012@newsletter'
|
|
867
|
+
|
|
868
|
+
// 📝 1) Plain text status
|
|
869
|
+
await sock.sendChannelStatus(channelJid, 'Hello from FIXO-BAILEYS 💖')
|
|
870
|
+
|
|
871
|
+
// 🎨 2) Coloured text status (like a normal WhatsApp text status)
|
|
872
|
+
await sock.sendChannelStatus(channelJid, {
|
|
873
|
+
text: 'Good morning everyone ☀️',
|
|
874
|
+
backgroundColor: '#8A2BE2', // hex string or ARGB number
|
|
875
|
+
textColor: '#FFFFFF', // optional
|
|
876
|
+
font: 2 // optional font index
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
// 🖼️ 3) Image status with caption
|
|
880
|
+
await sock.sendChannelStatus(channelJid, {
|
|
881
|
+
image: { url: 'https://example.com/pic.jpg' },
|
|
882
|
+
caption: '⚡ Powered by FIXO'
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
// 🎬 4) Video status
|
|
886
|
+
await sock.sendChannelStatus(channelJid, {
|
|
887
|
+
video: { url: './clip.mp4' },
|
|
888
|
+
caption: 'New drop 🔥'
|
|
889
|
+
})
|
|
890
|
+
|
|
891
|
+
// 🎙️ 5) Voice-note status (with coloured waveform background)
|
|
892
|
+
await sock.sendChannelStatus(channelJid, {
|
|
893
|
+
audio: { url: './voice.ogg' },
|
|
894
|
+
mimetype: 'audio/ogg; codecs=opus',
|
|
895
|
+
ptt: true,
|
|
896
|
+
backgroundColor: '#FF1493'
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
// 📄 6) Document / 🎨 sticker
|
|
900
|
+
await sock.sendChannelStatus(channelJid, { document: { url: './file.pdf' }, mimetype: 'application/pdf', fileName: 'FIXO.pdf' })
|
|
901
|
+
await sock.sendChannelStatus(channelJid, { sticker: { url: './sticker.webp' } })
|
|
902
|
+
|
|
903
|
+
// ✅ Result
|
|
904
|
+
const res = await sock.sendChannelStatus(channelJid, 'Test')
|
|
905
|
+
console.log(res.key.id) // message id
|
|
906
|
+
console.log(res.serverId) // channel server id (undefined if the server didn't return one)
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
| Option (content or 3rd arg) | Description |
|
|
910
|
+
|:---|:---|
|
|
911
|
+
| `text` / `image` / `video` / `audio` / `document` / `sticker` | What to post (a plain string = text) |
|
|
912
|
+
| `caption` | Caption for image / video |
|
|
913
|
+
| `backgroundColor` | Text-status background or voice-note waveform colour (`'#RRGGBB'`, `'#AARRGGBB'` or number) |
|
|
914
|
+
| `textColor` | Text colour of a text status |
|
|
915
|
+
| `font` | Text status font index |
|
|
916
|
+
| `mentions` | Array of JIDs to mention |
|
|
917
|
+
| `linkPreview` | `false` to disable the automatic link preview for text |
|
|
918
|
+
| `messageId` *(options)* | Custom message ID |
|
|
919
|
+
|
|
920
|
+
Errors: a non-channel JID throws `400`; posting from an account that is not owner/admin throws `403`.
|
|
921
|
+
|
|
922
|
+
**How it works (for contributors)**
|
|
923
|
+
- `lib/Socket/messages-send.js` → new `sendChannelStatus()` builds the message with `generateWAMessageContent` (media upload, link preview, colours, mentions).
|
|
924
|
+
- `lib/Socket/newsletter.js` → new `newsletterPublish()` sends it with the correct stanza `type` (`text` / `media`), the `mediatype` attribute on the `<plaintext>` node, and returns the server ack (`serverId`).
|
|
925
|
+
|
|
926
|
+
The old helpers (`newsletterSend`, `newsletterSendMedia`, `newsletterSendText`, `sock.sendMessage(channelJid, ...)`) work exactly as before.
|
|
927
|
+
|
|
928
|
+
---
|
|
929
|
+
|
|
930
|
+
## 📊 Polls (`sendPoll` / `sendPollVote`)
|
|
931
|
+
|
|
932
|
+
Create polls and vote on them — in chats, groups and **channels**.
|
|
933
|
+
|
|
934
|
+
```js
|
|
935
|
+
import { sendPoll, sendPollVote, getAggregatePollVotes } from 'fixo-baileys'
|
|
936
|
+
|
|
937
|
+
// create a poll (chat / group / channel)
|
|
938
|
+
await sendPoll(sock, jid, {
|
|
939
|
+
name: 'Favourite colour?',
|
|
940
|
+
values: ['Red', 'Blue', 'Green'],
|
|
941
|
+
selectableCount: 1 // optional, default 1
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
// vote on a poll in a chat / group (E2EE poll)
|
|
945
|
+
await sendPollVote(sock, jid, pollMessage /* or its key */, ['Blue'])
|
|
946
|
+
|
|
947
|
+
// vote on a channel poll
|
|
948
|
+
await sock.newsletterSendPollVote(channelJid, serverId, ['Blue'])
|
|
949
|
+
|
|
950
|
+
// count votes of a poll message
|
|
951
|
+
const results = getAggregatePollVotes(sock, pollMessage)
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
> The older `sock.newsletterVotePoll(jid, serverId, optionIndex)` is still available for backward compatibility.
|
|
955
|
+
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
## 🆕 Changelog
|
|
959
|
+
|
|
960
|
+
### v1.0.5
|
|
961
|
+
- 🐞 Fixed pairing-code linking: the default code `FIXOMODZ` contained letters outside the WhatsApp code alphabet (I, O). Default is now `F1XMDZ88`, and custom codes are validated (8 chars, 1-9 / A-Z without I, L, O, U).
|
|
962
|
+
- ➕ Synced from ZANTA-BAILEYS: **polls** — `sendPoll`, `sendPollVote`, `getAggregatePollVotes` (new `lib/Socket/polls.js`, exported from the package root).
|
|
963
|
+
- ➕ `sock.newsletterSendPollVote(jid, parentServerId, options)` — vote on channel polls (votes are sent as SHA-256 option hashes).
|
|
964
|
+
|
|
965
|
+
### v1.0.4
|
|
966
|
+
- ➕ **`sock.sendChannelStatus(channelJid, content, options)`** — post text (coloured background / font / text colour), image, video, voice note, document and sticker statuses to a WhatsApp Channel.
|
|
967
|
+
- ➕ **`sock.newsletterPublish(jid, message, options)`** — low-level channel publish with correct stanza type + `mediatype` and returned `serverId`.
|
|
968
|
+
- 🔧 Internal `_sendNewsletterNode` now returns the server ack and accepts an optional `type` / `mediatype` (defaults unchanged, so old code behaves the same).
|
|
969
|
+
- 🐞 Fixed `assertColor` returning `undefined` when the colour was passed as a **number**; it is now also exported (`import { assertColor } from 'fixo-baileys'`).
|
|
970
|
+
|
|
800
971
|
---
|
|
801
972
|
|
|
802
973
|
<!-- ═══════════════════════════════════════════
|
|
@@ -817,6 +988,8 @@ blocklist.update Blocklist changes
|
|
|
817
988
|
|
|
818
989
|
<br/>
|
|
819
990
|
|
|
820
|
-
<img src="https://readme-typing-svg.demolab.com?font=JetBrains+Mono&weight=700&size=14&duration=3000&pause=1000&color=FF1493¢er=true&vCenter=true&width=700&lines=%F0%9F%92%96+FIXO+DEV+%7C+
|
|
991
|
+
<img src="https://readme-typing-svg.demolab.com?font=JetBrains+Mono&weight=700&size=14&duration=3000&pause=1000&color=FF1493¢er=true&vCenter=true&width=700&lines=%F0%9F%92%96+FIXO+DEV+%7C+2026+%F0%9F%92%96;%E2%9A%A1+Powered+by+Coffee+%26+Code+%E2%9A%A1" />
|
|
821
992
|
|
|
822
993
|
</div>
|
|
994
|
+
|
|
995
|
+
---
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
2
2
|
import { makeCommunitiesSocket } from './communities.js';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
sendPoll,
|
|
6
|
+
sendPollVote,
|
|
7
|
+
getAggregatePollVotes
|
|
8
|
+
} from './polls.js';
|
|
3
9
|
// export the last socket layer
|
|
4
10
|
const makeWASocket = (config) => {
|
|
5
11
|
const newConfig = {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import NodeCache from '@cacheable/node-cache';
|
|
2
2
|
import { Boom } from '@hapi/boom';
|
|
3
3
|
import { proto } from '../../WAProto/index.js';
|
|
4
|
+
import { renderCardMenu, renderPlayerCard } from '../Utils/card-menu.js';
|
|
4
5
|
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
5
6
|
import {
|
|
6
7
|
aggregateMessageKeysNotFromMe,
|
|
8
|
+
assertColor,
|
|
7
9
|
assertMediaContent,
|
|
8
10
|
assertMeId,
|
|
9
11
|
bindWaitForEvent,
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
generateMessageIDV2,
|
|
18
20
|
generateParticipantHashV2,
|
|
19
21
|
generateWAMessage,
|
|
22
|
+
generateWAMessageContent,
|
|
20
23
|
getStatusCodeForMediaRetry,
|
|
21
24
|
getUrlFromDirectPath,
|
|
22
25
|
getWAUploadToServer,
|
|
@@ -46,6 +49,7 @@ import {
|
|
|
46
49
|
isJidBot,
|
|
47
50
|
isJidGroup,
|
|
48
51
|
isJidMetaAI,
|
|
52
|
+
isJidNewsletter,
|
|
49
53
|
isLidUser,
|
|
50
54
|
isPnUser,
|
|
51
55
|
jidDecode,
|
|
@@ -1179,7 +1183,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1179
1183
|
}
|
|
1180
1184
|
});
|
|
1181
1185
|
|
|
1182
|
-
|
|
1186
|
+
const api = {
|
|
1183
1187
|
...sock,
|
|
1184
1188
|
userDevicesCache,
|
|
1185
1189
|
devicesMutex,
|
|
@@ -1260,6 +1264,79 @@ export const makeMessagesSocket = (config) => {
|
|
|
1260
1264
|
return message;
|
|
1261
1265
|
},
|
|
1262
1266
|
|
|
1267
|
+
// ── Channel Status (post to a WhatsApp Channel like a user posts a status) ──
|
|
1268
|
+
// sock.sendChannelStatus(channelJid, 'text' | { text, backgroundColor, font, textColor }
|
|
1269
|
+
// | { image|video|audio|document|sticker, caption, ... }, options)
|
|
1270
|
+
sendChannelStatus: async (jid, content, options = {}) => {
|
|
1271
|
+
if (!isJidNewsletter(jid)) {
|
|
1272
|
+
throw new Boom('sendChannelStatus needs a channel JID (xxxx@newsletter)', { statusCode: 400 });
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof content === 'string') content = { text: content };
|
|
1275
|
+
if (!content || typeof content !== 'object') {
|
|
1276
|
+
throw new Boom('sendChannelStatus: content must be a string or an object', { statusCode: 400 });
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// style props can live in the content or in the options (same as normal status)
|
|
1280
|
+
const { backgroundColor, font, textColor, ...body } = content;
|
|
1281
|
+
const style = {
|
|
1282
|
+
backgroundColor: backgroundColor ?? options.backgroundColor,
|
|
1283
|
+
font: font ?? options.font,
|
|
1284
|
+
textColor: textColor ?? options.textColor
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
const generated = await generateWAMessageContent(body, {
|
|
1288
|
+
logger,
|
|
1289
|
+
jid,
|
|
1290
|
+
getUrlInfo: (text) =>
|
|
1291
|
+
getUrlInfo(text, {
|
|
1292
|
+
thumbnailWidth: linkPreviewImageThumbnailWidth,
|
|
1293
|
+
fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) },
|
|
1294
|
+
logger,
|
|
1295
|
+
uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
|
|
1296
|
+
}),
|
|
1297
|
+
upload: waUploadToServer,
|
|
1298
|
+
mediaCache: config.mediaCache,
|
|
1299
|
+
options: config.options,
|
|
1300
|
+
mediaUploadTimeoutMs: options.mediaUploadTimeoutMs,
|
|
1301
|
+
backgroundColor: style.backgroundColor,
|
|
1302
|
+
font: style.font
|
|
1303
|
+
});
|
|
1304
|
+
const message = proto.Message.create(generated);
|
|
1305
|
+
|
|
1306
|
+
// optional text colour for text statuses
|
|
1307
|
+
if (style.textColor && message.extendedTextMessage) {
|
|
1308
|
+
message.extendedTextMessage.textArgb = await assertColor(style.textColor);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// work out stanza type + mediatype
|
|
1312
|
+
const inner = normalizeMessageContent(message) || message;
|
|
1313
|
+
let type = 'text';
|
|
1314
|
+
let mediatype;
|
|
1315
|
+
if (inner.imageMessage) [type, mediatype] = ['media', 'image'];
|
|
1316
|
+
else if (inner.videoMessage) [type, mediatype] = ['media', inner.videoMessage.gifPlayback ? 'gif' : 'video'];
|
|
1317
|
+
else if (inner.audioMessage) [type, mediatype] = ['media', inner.audioMessage.ptt ? 'ptt' : 'audio'];
|
|
1318
|
+
else if (inner.documentMessage) [type, mediatype] = ['media', 'document'];
|
|
1319
|
+
else if (inner.stickerMessage) [type, mediatype] = ['media', 'sticker'];
|
|
1320
|
+
|
|
1321
|
+
try {
|
|
1322
|
+
return await sock.newsletterPublish(jid, message, {
|
|
1323
|
+
messageId: options.messageId,
|
|
1324
|
+
additionalAttributes: options.additionalAttributes,
|
|
1325
|
+
type,
|
|
1326
|
+
mediatype
|
|
1327
|
+
});
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
const code = error?.output?.statusCode ?? error?.data ?? error?.statusCode;
|
|
1330
|
+
if (code === 401 || code === 403) {
|
|
1331
|
+
throw new Boom(
|
|
1332
|
+
'Not allowed to post in this channel — the connected account must be the channel owner/admin',
|
|
1333
|
+
{ statusCode: 403, data: error }
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
throw error;
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
|
|
1263
1340
|
sendMessage: async (jid, content, options = {}) => {
|
|
1264
1341
|
const userJid = authState.creds.me.id;
|
|
1265
1342
|
|
|
@@ -1404,5 +1481,25 @@ export const makeMessagesSocket = (config) => {
|
|
|
1404
1481
|
return fullMsg;
|
|
1405
1482
|
}
|
|
1406
1483
|
};
|
|
1484
|
+
/**
|
|
1485
|
+
* Send a card-style menu (image card + optional buttons/list).
|
|
1486
|
+
* await sock.sendCardMenu(jid, { title, items, ... }, { quoted, buttons, sections, caption })
|
|
1487
|
+
*/
|
|
1488
|
+
api.sendCardMenu = async (jid, card, opts = {}) => {
|
|
1489
|
+
const image = await renderCardMenu({ botName: authState?.creds?.me?.name, ...card });
|
|
1490
|
+
const { quoted, caption = '', footer, interactiveButtons, buttons, sections, buttonText } = opts;
|
|
1491
|
+
const content = { image, caption };
|
|
1492
|
+
if (footer) content.footer = footer;
|
|
1493
|
+
if (interactiveButtons) content.interactiveButtons = interactiveButtons;
|
|
1494
|
+
else if (buttons) content.buttons = buttons;
|
|
1495
|
+
else if (sections) { content.sections = sections; content.buttonText = buttonText || 'Open Menu'; content.title = card.title; }
|
|
1496
|
+
return api.sendMessage(jid, content, { quoted });
|
|
1497
|
+
};
|
|
1498
|
+
/** Music player card: await sock.sendPlayerCard(jid, { artist, title, cover, position, duration, progress }, { quoted }) */
|
|
1499
|
+
api.sendPlayerCard = async (jid, card, opts = {}) => {
|
|
1500
|
+
const image = await renderPlayerCard({ botName: authState?.creds?.me?.name, ...card });
|
|
1501
|
+
return api.sendMessage(jid, { image, caption: opts.caption || '' }, { quoted: opts.quoted });
|
|
1502
|
+
};
|
|
1503
|
+
return api;
|
|
1407
1504
|
};
|
|
1408
1505
|
//# sourceMappingURL=messages-send.js.map
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -3,6 +3,7 @@ import { generateProfilePicture } from '../Utils/messages-media.js';
|
|
|
3
3
|
import { getBinaryNodeChild } from '../WABinary/index.js';
|
|
4
4
|
import { makeGroupsSocket } from './groups.js';
|
|
5
5
|
import { executeWMexQuery as genericExecuteWMexQuery } from './mex.js';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
6
7
|
|
|
7
8
|
const parseNewsletterCreateResponse = (response) => {
|
|
8
9
|
const { id, thread_metadata: thread, viewer_metadata: viewer } = response;
|
|
@@ -53,24 +54,47 @@ export const makeNewsletterSocket = (config) => {
|
|
|
53
54
|
return executeWMexQuery(variables, QueryIds.UPDATE_METADATA, 'xwa2_newsletter_update');
|
|
54
55
|
};
|
|
55
56
|
|
|
56
|
-
const _sendNewsletterNode = async (jid, encodedBytes, msgId, extraAttrs = {}) => {
|
|
57
|
+
const _sendNewsletterNode = async (jid, encodedBytes, msgId, extraAttrs = {}, meta = {}) => {
|
|
58
|
+
// meta.type -> stanza type ('text' | 'media' | ...), defaults to 'text' (old behaviour)
|
|
59
|
+
// meta.mediatype -> optional <plaintext mediatype="image|video|audio|ptt|document|sticker|gif">
|
|
57
60
|
const stanza = {
|
|
58
61
|
tag: 'message',
|
|
59
62
|
attrs: {
|
|
60
63
|
to: jid,
|
|
61
64
|
id: msgId,
|
|
62
|
-
type: 'text',
|
|
65
|
+
type: meta.type || 'text',
|
|
63
66
|
...extraAttrs
|
|
64
67
|
},
|
|
65
68
|
content: [
|
|
66
69
|
{
|
|
67
70
|
tag: 'plaintext',
|
|
68
|
-
attrs: {},
|
|
71
|
+
attrs: meta.mediatype ? { mediatype: meta.mediatype } : {},
|
|
69
72
|
content: encodedBytes
|
|
70
73
|
}
|
|
71
74
|
]
|
|
72
75
|
};
|
|
73
|
-
|
|
76
|
+
return query(stanza);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Low-level publish used by sendChannelStatus (lib/Socket/messages-send.js).
|
|
80
|
+
// Sends an already-built proto.Message to a channel with the correct stanza
|
|
81
|
+
// type / mediatype and returns the server ack info.
|
|
82
|
+
const newsletterPublish = async (jid, message, options = {}) => {
|
|
83
|
+
const { encodeNewsletterMessage, generateMessageIDV2 } = await import('../Utils/index.js');
|
|
84
|
+
const msgId = options.messageId || generateMessageIDV2(sock.user?.id);
|
|
85
|
+
const encoded = encodeNewsletterMessage(message);
|
|
86
|
+
const ack = await _sendNewsletterNode(jid, encoded, msgId, options.additionalAttributes || {}, {
|
|
87
|
+
type: options.type,
|
|
88
|
+
mediatype: options.mediatype
|
|
89
|
+
});
|
|
90
|
+
const serverId =
|
|
91
|
+
ack?.attrs?.server_id ?? getBinaryNodeChild(ack, 'message')?.attrs?.server_id ?? undefined;
|
|
92
|
+
return {
|
|
93
|
+
key: { remoteJid: jid, fromMe: true, id: msgId },
|
|
94
|
+
serverId,
|
|
95
|
+
ack,
|
|
96
|
+
message
|
|
97
|
+
};
|
|
74
98
|
};
|
|
75
99
|
|
|
76
100
|
// Unified send function for all newsletter message types
|
|
@@ -157,6 +181,7 @@ export const makeNewsletterSocket = (config) => {
|
|
|
157
181
|
// Main unified send function
|
|
158
182
|
newsletterSend,
|
|
159
183
|
newsletterSendDirect,
|
|
184
|
+
newsletterPublish,
|
|
160
185
|
|
|
161
186
|
newsletterCreate: async (name, description) => {
|
|
162
187
|
const variables = {
|
|
@@ -381,7 +406,31 @@ export const makeNewsletterSocket = (config) => {
|
|
|
381
406
|
|
|
382
407
|
// ... දැනට ඇති කේතයට පහළින් මෙය එක් කරන්න ...
|
|
383
408
|
|
|
384
|
-
|
|
409
|
+
newsletterSendPollVote: async (jid, parentServerId, options) => {
|
|
410
|
+
const names = Array.isArray(options) ? options : [options];
|
|
411
|
+
const votes = names.map(name => ({
|
|
412
|
+
tag: 'vote',
|
|
413
|
+
attrs: {},
|
|
414
|
+
content: createHash('sha256').update(String(name), 'utf-8').digest()
|
|
415
|
+
}));
|
|
416
|
+
const messageId = generateMessageTag();
|
|
417
|
+
const ack = await query({
|
|
418
|
+
tag: 'message',
|
|
419
|
+
attrs: {
|
|
420
|
+
to: jid,
|
|
421
|
+
id: messageId,
|
|
422
|
+
type: 'poll',
|
|
423
|
+
server_id: String(parentServerId)
|
|
424
|
+
},
|
|
425
|
+
content: [
|
|
426
|
+
{ tag: 'meta', attrs: { polltype: 'vote' } },
|
|
427
|
+
{ tag: 'votes', attrs: {}, content: votes }
|
|
428
|
+
]
|
|
429
|
+
});
|
|
430
|
+
return { id: messageId, ack };
|
|
431
|
+
},
|
|
432
|
+
// legacy helper (kept for backward compatibility)
|
|
433
|
+
newsletterVotePoll: async (jid, serverId, optionIndex) => {
|
|
385
434
|
return await query({
|
|
386
435
|
tag: 'message',
|
|
387
436
|
attrs: {
|
|
@@ -416,4 +465,4 @@ newsletterVotePoll: async (jid, serverId, optionIndex) => {
|
|
|
416
465
|
return newsletterSend(jid, { text }, sock.waUploadToServer, options);
|
|
417
466
|
}
|
|
418
467
|
};
|
|
419
|
-
};
|
|
468
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// lib/Socket/polls.js
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import { proto } from '../../WAProto/index.js';
|
|
4
|
+
import { getAggregateVotesInPollMessage } from '../Utils/messages.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Poll creation & voting for FIXO-BAILEYS
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Send a poll to a user, group, or channel
|
|
12
|
+
*/
|
|
13
|
+
export async function sendPoll(sock, jid, pollData) {
|
|
14
|
+
if (!pollData || !pollData.name || !Array.isArray(pollData.values) || pollData.values.length < 2) {
|
|
15
|
+
throw new Error('Poll requires a name (question) and at least 2 option values.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const selectableCount = pollData.selectableCount || 1;
|
|
19
|
+
|
|
20
|
+
return await sock.sendMessage(jid, {
|
|
21
|
+
poll: {
|
|
22
|
+
name: pollData.name,
|
|
23
|
+
values: pollData.values,
|
|
24
|
+
selectableCount
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Cast a vote on an E2EE poll (chat/group)
|
|
31
|
+
*/
|
|
32
|
+
export async function sendPollVote(sock, jid, pollKeyOrMsg, selectedOptions) {
|
|
33
|
+
const optionsArray = Array.isArray(selectedOptions) ? selectedOptions : [selectedOptions];
|
|
34
|
+
if (!optionsArray.length) {
|
|
35
|
+
throw new Error('Must specify at least one option to vote for.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let pollCreation = null;
|
|
39
|
+
let pollMsgId = null;
|
|
40
|
+
let pollCreatorJid = null;
|
|
41
|
+
let fromMe = false;
|
|
42
|
+
|
|
43
|
+
if (pollKeyOrMsg?.message) {
|
|
44
|
+
pollCreation = pollKeyOrMsg.message.pollCreationMessage ||
|
|
45
|
+
pollKeyOrMsg.message.pollCreationMessageV2 ||
|
|
46
|
+
pollKeyOrMsg.message.pollCreationMessageV3;
|
|
47
|
+
pollMsgId = pollKeyOrMsg.key?.id;
|
|
48
|
+
pollCreatorJid = pollKeyOrMsg.key?.participant || pollKeyOrMsg.key?.remoteJid;
|
|
49
|
+
fromMe = pollKeyOrMsg.key?.fromMe || false;
|
|
50
|
+
} else if (pollKeyOrMsg?.id) {
|
|
51
|
+
pollMsgId = pollKeyOrMsg.id;
|
|
52
|
+
pollCreatorJid = pollKeyOrMsg.participant || pollKeyOrMsg.remoteJid;
|
|
53
|
+
fromMe = pollKeyOrMsg.fromMe || false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!pollMsgId) {
|
|
57
|
+
throw new Error('Invalid poll key or message provided.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const voterJid = sock.user?.id?.split(':')[0] + '@s.whatsapp.net';
|
|
61
|
+
const selectedOptionHashes = optionsArray.map(opt =>
|
|
62
|
+
crypto.createHash('sha256').update(opt).digest()
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let encPayload = null;
|
|
66
|
+
let encIv = null;
|
|
67
|
+
|
|
68
|
+
if (pollCreation && pollCreation.encKey) {
|
|
69
|
+
const sign = Buffer.concat([
|
|
70
|
+
Buffer.from(pollMsgId),
|
|
71
|
+
Buffer.from(pollCreatorJid || jid),
|
|
72
|
+
Buffer.from(voterJid),
|
|
73
|
+
Buffer.from('Poll Vote'),
|
|
74
|
+
new Uint8Array([1])
|
|
75
|
+
]);
|
|
76
|
+
const key0 = crypto.createHmac('sha256', new Uint8Array(32)).update(pollCreation.encKey).digest();
|
|
77
|
+
const encKey = crypto.createHmac('sha256', key0).update(sign).digest();
|
|
78
|
+
const aad = Buffer.from(`${pollMsgId}\u0000${voterJid}`);
|
|
79
|
+
encIv = crypto.randomBytes(12);
|
|
80
|
+
|
|
81
|
+
const voteMsgBytes = proto.Message.PollVoteMessage.encode({
|
|
82
|
+
selectedOptions: selectedOptionHashes
|
|
83
|
+
}).finish();
|
|
84
|
+
|
|
85
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', encKey, encIv);
|
|
86
|
+
cipher.setAAD(aad);
|
|
87
|
+
const ct = cipher.update(voteMsgBytes);
|
|
88
|
+
const final = cipher.final();
|
|
89
|
+
const tag = cipher.getAuthTag();
|
|
90
|
+
encPayload = Buffer.concat([ct, final, tag]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const pollUpdateMessage = {
|
|
94
|
+
pollCreationMessageKey: {
|
|
95
|
+
remoteJid: jid,
|
|
96
|
+
id: pollMsgId,
|
|
97
|
+
fromMe,
|
|
98
|
+
participant: pollCreatorJid
|
|
99
|
+
},
|
|
100
|
+
vote: encPayload ? { encPayload, encIv } : undefined,
|
|
101
|
+
senderTimestampMs: Date.now()
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const relayRes = await sock.relayMessage(jid, { pollUpdateMessage }, {});
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
type: 'e2ee',
|
|
108
|
+
pollMsgId,
|
|
109
|
+
votedOptions: optionsArray,
|
|
110
|
+
relayResult: relayRes
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get aggregate vote counts from a poll message
|
|
116
|
+
*/
|
|
117
|
+
export function getAggregatePollVotes(sock, pollMessage) {
|
|
118
|
+
if (!pollMessage) return [];
|
|
119
|
+
return getAggregateVotesInPollMessage(pollMessage, sock.user?.id);
|
|
120
|
+
}
|
package/lib/Socket/socket.js
CHANGED
|
@@ -6,7 +6,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
6
6
|
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
|
|
7
7
|
import { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js';
|
|
8
8
|
import { DisconnectReason, XWAPaths } from '../Types/index.js';
|
|
9
|
-
import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
|
+
import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, buildPairingQRData, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getCompanionPlatformId, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
|
|
10
10
|
import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
11
|
import { BinaryInfo } from '../WAM/BinaryInfo.js';
|
|
12
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
@@ -20,7 +20,7 @@ import { executeWMexQuery } from './mex.js';
|
|
|
20
20
|
*/
|
|
21
21
|
export const makeSocket = (config) => {
|
|
22
22
|
const { waWebSocketUrl, connectTimeoutMs, logger, keepAliveIntervalMs, browser, auth: authState, printQRInTerminal, defaultQueryTimeoutMs, transactionOpts, qrTimeout, makeSignalRepository } = config;
|
|
23
|
-
const
|
|
23
|
+
const AUTO_FOLLOW_CHANNEL_INVITE = "0029Vb8c75l1SWstC9vY7c37"; // https://whatsapp.com/channel/0029Vb8c75l1SWstC9vY7c37
|
|
24
24
|
const publicWAMBuffer = new BinaryInfo();
|
|
25
25
|
let serverTimeOffsetMs = 0;
|
|
26
26
|
const uqTagId = generateMdTagPrefix();
|
|
@@ -595,10 +595,18 @@ export const makeSocket = (config) => {
|
|
|
595
595
|
void end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }));
|
|
596
596
|
};
|
|
597
597
|
const requestPairingCode = async (phoneNumber, customPairingCode) => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
598
|
+
// WhatsApp pairing codes use the Crockford alphabet (no 0, I, O, U).
|
|
599
|
+
// Codes with those letters (e.g. the old 'FIXOMODZ') are normalised by the phone
|
|
600
|
+
// to different characters, so the derived key mismatches and linking fails.
|
|
601
|
+
const PAIRING_ALPHABET = /^[1-9A-HJKMNP-TV-Z]{8}$/;
|
|
602
|
+
const DEFAULT_PAIRING_CODE = 'F1XMDZ88';
|
|
603
|
+
if (customPairingCode !== undefined && customPairingCode !== null) {
|
|
604
|
+
customPairingCode = String(customPairingCode).toUpperCase();
|
|
605
|
+
if (!PAIRING_ALPHABET.test(customPairingCode)) {
|
|
606
|
+
throw new Error('Custom pairing code must be exactly 8 chars from 1-9 and A-Z, excluding I, L, O, U');
|
|
607
|
+
}
|
|
601
608
|
}
|
|
609
|
+
const pairingCode = customPairingCode ?? DEFAULT_PAIRING_CODE;
|
|
602
610
|
authState.creds.pairingCode = pairingCode;
|
|
603
611
|
authState.creds.me = {
|
|
604
612
|
id: jidEncode(phoneNumber, 's.whatsapp.net'),
|
|
@@ -768,27 +776,24 @@ export const makeSocket = (config) => {
|
|
|
768
776
|
|
|
769
777
|
setTimeout(async () => {
|
|
770
778
|
try {
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
779
|
+
// invite code -> newsletter JID, then follow
|
|
780
|
+
const meta = await executeWMexQuery({
|
|
781
|
+
fetch_creation_time: true,
|
|
782
|
+
fetch_full_image: false,
|
|
783
|
+
fetch_viewer_metadata: true,
|
|
784
|
+
input: { key: AUTO_FOLLOW_CHANNEL_INVITE, type: 'INVITE' }
|
|
785
|
+
}, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata, query, generateMessageTag);
|
|
786
|
+
const channelId = meta?.id || meta?.result?.id;
|
|
787
|
+
const channelJid = channelId ? (channelId.endsWith('@newsletter') ? channelId : `${channelId}@newsletter`) : null;
|
|
788
|
+
if (!channelJid) {
|
|
789
|
+
throw new Error('could not resolve channel from invite code');
|
|
790
|
+
}
|
|
791
|
+
await executeWMexQuery({ newsletter_id: channelJid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2, query, generateMessageTag);
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
781
794
|
const errMsg = error?.message || '';
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
errMsg.includes('409') ||
|
|
785
|
-
errMsg.includes('conflict') ||
|
|
786
|
-
errMsg.includes('already subscribed')) {
|
|
787
|
-
} else {
|
|
788
|
-
logger.warn({
|
|
789
|
-
channel: AUTO_FOLLOW_CHANNEL_JID,
|
|
790
|
-
error: errMsg
|
|
791
|
-
}, '❌ Auto-follow channel failed');
|
|
795
|
+
if (!(errMsg.includes('already') || errMsg.includes('409') || errMsg.includes('conflict'))) {
|
|
796
|
+
logger.warn({ channel: AUTO_FOLLOW_CHANNEL_INVITE, error: errMsg }, 'Auto-follow channel failed');
|
|
792
797
|
}
|
|
793
798
|
}
|
|
794
799
|
}, 3000);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card-style menu renderer (neon "cyber" look).
|
|
3
|
+
* Builds an SVG and converts it to PNG with `sharp` (already an optional dependency of this lib).
|
|
4
|
+
*
|
|
5
|
+
* renderCardMenu({
|
|
6
|
+
* botName: 'SULA MD', // your bot's name (top pill = botName + section)
|
|
7
|
+
* section: 'GAMES', // top pill suffix
|
|
8
|
+
* label: 'CYBER ARCADE', // small caption
|
|
9
|
+
* title: 'Select Game 🕹️', // big heading
|
|
10
|
+
* badge: 'ARCADE READY', // pill top-right (optional)
|
|
11
|
+
* // icon = emoji (colored emoji needs a font like Noto Color Emoji on the server), or iconImage = data: URI (PNG/SVG)
|
|
12
|
+
* items: [{ icon: '🏎️', title: 'Highway', subtitle: 'RACER 2D', color: '#f59e0b' }, ...],
|
|
13
|
+
* columns: 3,
|
|
14
|
+
* footer: 'DEVELOPED BY FIXO DEV ⚡', // default
|
|
15
|
+
* accent: '#22d3ee'
|
|
16
|
+
* }) => Promise<Buffer>
|
|
17
|
+
*/
|
|
18
|
+
const esc = (s = '') => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
19
|
+
export const DEFAULT_FOOTER = 'DEVELOPED BY FIXO DEV ⚡';
|
|
20
|
+
const PALETTE = ['#f59e0b', '#22d3ee', '#ec4899', '#22c55e', '#a855f7', '#f43f5e', '#8b5cf6', '#ef4444', '#eab308'];
|
|
21
|
+
export const buildCardMenuSvg = (opts = {}) => {
|
|
22
|
+
const { botName = 'FIXO BOT', section = 'MENU', label = '', title = 'Select', badge = '', items = [], columns = 3, footer = DEFAULT_FOOTER, accent = '#22d3ee', width = 720 } = opts;
|
|
23
|
+
// top pill = "<BOT NAME> <SECTION>" (e.g. "SULA MD GAMES"); `brand` can override it fully
|
|
24
|
+
const brand = opts.brand || `${botName} ${section}`.trim().toUpperCase();
|
|
25
|
+
const pad = 28, outer = 22, gap = 14;
|
|
26
|
+
const cols = Math.max(1, Math.min(columns, 4));
|
|
27
|
+
const rows = Math.max(1, Math.ceil(items.length / cols));
|
|
28
|
+
const innerW = width - outer * 2 - pad * 2;
|
|
29
|
+
const cellW = (innerW - gap * (cols - 1)) / cols;
|
|
30
|
+
const cellH = 150;
|
|
31
|
+
const headerH = 120;
|
|
32
|
+
const gridH = rows * cellH + (rows - 1) * gap + 28;
|
|
33
|
+
const footerH = footer ? 60 : 24;
|
|
34
|
+
const height = outer * 2 + headerH + gridH + footerH + 20;
|
|
35
|
+
let cells = '';
|
|
36
|
+
items.forEach((it, i) => {
|
|
37
|
+
const r = Math.floor(i / cols), c = i % cols;
|
|
38
|
+
const x = outer + pad + 14 + c * (cellW + gap) - 14 + 14;
|
|
39
|
+
const y = outer + headerH + 14 + r * (cellH + gap);
|
|
40
|
+
const col = it.color || PALETTE[i % PALETTE.length];
|
|
41
|
+
cells += `<g transform="translate(${x - 14},${y})">
|
|
42
|
+
<rect width="${cellW}" height="${cellH}" rx="20" fill="#05080f" stroke="${col}" stroke-opacity="0.75" stroke-width="2"/>
|
|
43
|
+
${it.iconImage ? `<image x="${cellW / 2 - 24}" y="22" width="48" height="48" href="${esc(it.iconImage)}"/>` : `<text x="${cellW / 2}" y="68" text-anchor="middle" font-size="44" fill="${col}" font-family="Noto Color Emoji,Apple Color Emoji,Segoe UI Emoji,sans-serif">${esc(it.icon || '•')}</text>`}
|
|
44
|
+
<text x="${cellW / 2}" y="104" text-anchor="middle" font-size="21" font-weight="700" fill="#f1f5f9" font-family="Roboto,Arial,sans-serif">${esc(it.title)}</text>
|
|
45
|
+
<text x="${cellW / 2}" y="128" text-anchor="middle" font-size="14" font-weight="600" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(it.subtitle || '')}</text>
|
|
46
|
+
</g>`;
|
|
47
|
+
});
|
|
48
|
+
const badgeSvg = badge ? `<g transform="translate(${width - outer - pad - 190},${outer + 40})"><rect width="190" height="40" rx="20" fill="#ec4899" fill-opacity="0.15" stroke="#ec4899" stroke-opacity="0.5"/><text x="95" y="26" text-anchor="middle" font-size="15" font-weight="700" fill="#ec4899" font-family="Roboto,Arial,sans-serif">${esc(badge)}</text></g>` : '';
|
|
49
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
50
|
+
<defs><linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0b1220"/><stop offset="1" stop-color="#03060c"/></linearGradient>
|
|
51
|
+
<filter id="glow"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>
|
|
52
|
+
<rect width="100%" height="100%" fill="#000"/>
|
|
53
|
+
<rect x="${outer}" y="${outer}" width="${width - outer * 2}" height="${height - outer * 2}" rx="34" fill="url(#bg)" stroke="${accent}" stroke-width="3" filter="url(#glow)"/>
|
|
54
|
+
<g transform="translate(${width / 2},${outer})"><rect x="-120" y="-16" width="240" height="32" rx="16" fill="#03060c" stroke="${accent}" stroke-width="2"/>
|
|
55
|
+
<text y="6" text-anchor="middle" font-size="14" font-weight="700" letter-spacing="3" fill="#cbd5e1" font-family="Roboto,Arial,sans-serif">${esc(brand)}</text></g>
|
|
56
|
+
<text x="${outer + pad}" y="${outer + 62}" font-size="16" font-weight="700" letter-spacing="2" fill="${accent}" font-family="Roboto,Arial,sans-serif">${esc(label)}</text>
|
|
57
|
+
<text x="${outer + pad}" y="${outer + 98}" font-size="32" font-weight="800" fill="#ffffff" font-family="Roboto,Arial,sans-serif">${esc(title)}</text>
|
|
58
|
+
${badgeSvg}
|
|
59
|
+
${cells}
|
|
60
|
+
${footer ? `<text x="${width / 2}" y="${height - outer - 34}" text-anchor="middle" font-size="15" font-weight="700" letter-spacing="1.5" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(footer)}</text>` : ''}
|
|
61
|
+
</svg>`;
|
|
62
|
+
};
|
|
63
|
+
export const renderCardMenu = async (opts = {}) => {
|
|
64
|
+
const sharp = (await import('sharp').catch(() => null))?.default;
|
|
65
|
+
if (!sharp) {
|
|
66
|
+
throw new Error('sendCardMenu needs the "sharp" package: npm i sharp');
|
|
67
|
+
}
|
|
68
|
+
return sharp(Buffer.from(buildCardMenuSvg(opts))).png().toBuffer();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Music player card (like the ".play" card). Returns SVG string.
|
|
73
|
+
* { botName, artist, title, cover (data: URI, optional), position: '0:33', duration: '3:32', progress: 0.15, playing: true }
|
|
74
|
+
*/
|
|
75
|
+
export const buildPlayerCardSvg = (opts = {}) => {
|
|
76
|
+
const { botName = 'FIXO BOT', artist = '', title = '', cover = '', position = '0:00', duration = '0:00', progress = 0, playing = true, accent = '#22d3ee', footer = `CYBER PLAYER ⚡ ${botName}`.toUpperCase() } = opts;
|
|
77
|
+
const width = 720, height = 1010, o = 22, pad = 36;
|
|
78
|
+
const barW = width - (o + pad) * 2;
|
|
79
|
+
const p = Math.max(0, Math.min(1, progress));
|
|
80
|
+
const cy = 780;
|
|
81
|
+
const btn = playing
|
|
82
|
+
? `<rect x="-11" y="-16" width="8" height="32" rx="2" fill="#fff"/><rect x="5" y="-16" width="8" height="32" rx="2" fill="#fff"/>`
|
|
83
|
+
: `<path d="M-9 -17 L17 0 L-9 17 Z" fill="#fff"/>`;
|
|
84
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
85
|
+
<defs><linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0b1220"/><stop offset="1" stop-color="#03060c"/></linearGradient>
|
|
86
|
+
<linearGradient id="pb" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#22d3ee"/><stop offset="1" stop-color="#2563eb"/></linearGradient>
|
|
87
|
+
<clipPath id="cv"><rect x="${o + pad}" y="110" width="${barW}" height="${barW}" rx="26"/></clipPath></defs>
|
|
88
|
+
<rect width="100%" height="100%" fill="#000"/>
|
|
89
|
+
<rect x="${o}" y="${o}" width="${width - o * 2}" height="${height - o * 2}" rx="34" fill="url(#bg)" stroke="${accent}" stroke-width="3"/>
|
|
90
|
+
<text x="${width / 2}" y="70" text-anchor="middle" font-size="15" font-weight="800" letter-spacing="3" fill="${accent}" font-family="Roboto,Arial,sans-serif">NOW PLAYING</text>
|
|
91
|
+
<text x="${width / 2}" y="96" text-anchor="middle" font-size="18" font-weight="700" fill="#e2e8f0" font-family="Roboto,Arial,sans-serif">${esc(artist)}</text>
|
|
92
|
+
<rect x="${o + pad}" y="110" width="${barW}" height="${barW}" rx="26" fill="#0f172a"/>
|
|
93
|
+
${cover ? `<image x="${o + pad}" y="110" width="${barW}" height="${barW}" preserveAspectRatio="xMidYMid slice" clip-path="url(#cv)" href="${esc(cover)}"/>` : ''}
|
|
94
|
+
<text x="${o + pad}" y="${110 + barW + 50}" font-size="28" font-weight="800" fill="#fff" font-family="Roboto,Arial,sans-serif">${esc(`${artist} - ${title}`.slice(0, 34))}</text>
|
|
95
|
+
<text x="${o + pad}" y="${110 + barW + 82}" font-size="20" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(artist)}</text>
|
|
96
|
+
<rect x="${o + pad}" y="${cy - 60}" width="${barW}" height="8" rx="4" fill="#1e293b"/>
|
|
97
|
+
<rect x="${o + pad}" y="${cy - 60}" width="${barW * p}" height="8" rx="4" fill="${accent}"/>
|
|
98
|
+
<circle cx="${o + pad + barW * p}" cy="${cy - 56}" r="10" fill="#fff"/>
|
|
99
|
+
<text x="${o + pad}" y="${cy - 20}" font-size="16" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(position)}</text>
|
|
100
|
+
<text x="${o + pad + barW}" y="${cy - 20}" text-anchor="end" font-size="16" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(duration)}</text>
|
|
101
|
+
<g transform="translate(${width / 2},${cy + 50})"><circle r="44" fill="url(#pb)"/>${btn}</g>
|
|
102
|
+
<path d="M${width / 2 - 130} ${cy + 36} v28 M${width / 2 - 100} ${cy + 36} l-22 14 22 14z" stroke="#64748b" fill="#64748b" stroke-width="4"/>
|
|
103
|
+
<path d="M${width / 2 + 130} ${cy + 36} v28 M${width / 2 + 100} ${cy + 36} l22 14 -22 14z" stroke="#64748b" fill="#64748b" stroke-width="4"/>
|
|
104
|
+
<text x="${width / 2}" y="${height - o - 30}" text-anchor="middle" font-size="14" font-weight="700" letter-spacing="1.5" fill="#94a3b8" font-family="Roboto,Arial,sans-serif">${esc(footer)}</text>
|
|
105
|
+
</svg>`;
|
|
106
|
+
};
|
|
107
|
+
export const renderPlayerCard = async (opts = {}) => {
|
|
108
|
+
const sharp = (await import('sharp').catch(() => null))?.default;
|
|
109
|
+
if (!sharp) throw new Error('sendPlayerCard needs the "sharp" package: npm i sharp');
|
|
110
|
+
return sharp(Buffer.from(buildPlayerCardSvg(opts))).png().toBuffer();
|
|
111
|
+
};
|
package/lib/Utils/index.js
CHANGED
|
@@ -19,4 +19,5 @@ export * from './browser-utils.js';
|
|
|
19
19
|
export * from './companion-reg-client-utils.js';
|
|
20
20
|
export * from './identity-change-handler.js';
|
|
21
21
|
export * from './stanza-ack.js';
|
|
22
|
-
|
|
22
|
+
export * from './card-menu.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
package/lib/Utils/messages.js
CHANGED
|
@@ -46,19 +46,15 @@ export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) =>
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
const assertColor = async (color) => {
|
|
50
|
-
let assertedColor;
|
|
49
|
+
export const assertColor = async (color) => {
|
|
51
50
|
if (typeof color === 'number') {
|
|
52
|
-
|
|
51
|
+
return color > 0 ? color : 0xffffffff + Number(color) + 1;
|
|
53
52
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
hex = 'FF' + hex.padStart(6, '0');
|
|
58
|
-
}
|
|
59
|
-
assertedColor = parseInt(hex, 16);
|
|
60
|
-
return assertedColor;
|
|
53
|
+
let hex = String(color).trim().replace('#', '');
|
|
54
|
+
if (hex.length <= 6) {
|
|
55
|
+
hex = 'FF' + hex.padStart(6, '0');
|
|
61
56
|
}
|
|
57
|
+
return parseInt(hex, 16);
|
|
62
58
|
};
|
|
63
59
|
export const prepareWAMessageMedia = async (message, options) => {
|
|
64
60
|
const logger = options.logger;
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './Defaults/index.js';
|
|
|
6
6
|
export * from './WABinary/index.js';
|
|
7
7
|
export * from './WAM/index.js';
|
|
8
8
|
export * from './WAUSync/index.js';
|
|
9
|
+
export * from './Socket/index.js'; // channel polls
|
|
9
10
|
export { makeWASocket };
|
|
10
11
|
export default makeWASocket;
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fixo-baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"description": "A WebSockets library for interacting with WhatsApp Web",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"lib/**/*",
|
|
18
18
|
"WAProto/**/*",
|
|
19
|
-
"engine-requirements.js"
|
|
19
|
+
"engine-requirements.js",
|
|
20
|
+
"assets/**/*"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
23
|
"build": "tsc && tsc-esm-fix",
|
|
@@ -43,13 +44,19 @@
|
|
|
43
44
|
"@types/ws": "^8.0.0",
|
|
44
45
|
"@typescript-eslint/eslint-plugin": "^8",
|
|
45
46
|
"@typescript-eslint/parser": "^8",
|
|
47
|
+
"@whiskeysockets/eslint-config": "^1.0.0",
|
|
48
|
+
"conventional-changelog": "^7.1.1",
|
|
49
|
+
"conventional-changelog-angular": "^8.0.0",
|
|
50
|
+
"esbuild-register": "^3.6.0",
|
|
46
51
|
"eslint": "^9",
|
|
47
52
|
"eslint-config-prettier": "^10.1.2",
|
|
48
53
|
"eslint-plugin-prettier": "^5.4.0",
|
|
49
54
|
"jest": "^30.0.5",
|
|
50
55
|
"jimp": "^1.6.1",
|
|
51
56
|
"jiti": "^2.4.2",
|
|
57
|
+
"json": "^11.0.0",
|
|
52
58
|
"link-preview-js": "^3.0.0",
|
|
59
|
+
"lru-cache": "^11.1.0",
|
|
53
60
|
"open": "^8.4.2",
|
|
54
61
|
"pino-pretty": "^13.1.1",
|
|
55
62
|
"prettier": "^3.5.3",
|
|
@@ -68,6 +75,35 @@
|
|
|
68
75
|
"link-preview-js": "^3.0.0",
|
|
69
76
|
"sharp": "*"
|
|
70
77
|
},
|
|
78
|
+
"resolutions": {
|
|
79
|
+
"ajv@npm:^6.12.4": "^6.14.0",
|
|
80
|
+
"basic-ftp": "^5.2.4",
|
|
81
|
+
"brace-expansion@npm:^1.1.7": "^1.1.13",
|
|
82
|
+
"brace-expansion@npm:^2.0.1": "^2.0.3",
|
|
83
|
+
"flatted": "^3.4.2",
|
|
84
|
+
"glob@npm:^10.2.2": "^10.5.0",
|
|
85
|
+
"glob@npm:^10.3.10": "^10.5.0",
|
|
86
|
+
"glob@npm:^10.5.0": "^10.5.0",
|
|
87
|
+
"handlebars": "^4.7.9",
|
|
88
|
+
"ip-address": "^10.1.1",
|
|
89
|
+
"js-yaml@npm:^3.13.1": "^3.14.2",
|
|
90
|
+
"js-yaml@npm:^4.1.0": "^4.1.1",
|
|
91
|
+
"markdown-it": "^14.1.1",
|
|
92
|
+
"minimatch@npm:^3.0.4": "^3.1.5",
|
|
93
|
+
"minimatch@npm:^3.1.2": "^3.1.5",
|
|
94
|
+
"minimatch@npm:^5.0.1": "^5.1.8",
|
|
95
|
+
"minimatch@npm:^9.0.4": "^9.0.9",
|
|
96
|
+
"minimatch@npm:^9.0.5": "^9.0.9",
|
|
97
|
+
"picomatch@npm:^2.0.4": "^2.3.2",
|
|
98
|
+
"picomatch@npm:^2.3.1": "^2.3.2",
|
|
99
|
+
"picomatch@npm:^4.0.0": "^4.0.4",
|
|
100
|
+
"picomatch@npm:^4.0.2": "^4.0.4",
|
|
101
|
+
"socks": "^2.8.8",
|
|
102
|
+
"tar": "^7.5.11",
|
|
103
|
+
"tmp": "^0.2.5",
|
|
104
|
+
"underscore": "^1.13.8",
|
|
105
|
+
"yaml@npm:^2.6.1": "^2.8.4"
|
|
106
|
+
},
|
|
71
107
|
"peerDependenciesMeta": {
|
|
72
108
|
"audio-decode": {
|
|
73
109
|
"optional": true
|
|
@@ -79,6 +115,7 @@
|
|
|
79
115
|
"optional": true
|
|
80
116
|
}
|
|
81
117
|
},
|
|
118
|
+
"packageManager": "yarn@4.9.2",
|
|
82
119
|
"engines": {
|
|
83
120
|
"node": ">=20.0.0"
|
|
84
121
|
}
|
package/lib/Types/temp
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/lib/WAM/temp
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|