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 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.0-FF1493?style=for-the-badge&labelColor=000000&logo=vercel&logoColor=FF1493" />
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
- | 🚫 | Auto-follow newsletters |
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&center=true&vCenter=true&width=700&lines=%F0%9F%92%96+FIXO+DEV+%7C+2024+%F0%9F%92%96;%E2%9A%A1+Powered+by+Coffee+%26+Code+%E2%9A%A1" />
991
+ <img src="https://readme-typing-svg.demolab.com?font=JetBrains+Mono&weight=700&size=14&duration=3000&pause=1000&color=FF1493&center=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
+ ---
@@ -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
- return {
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
@@ -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
- await query(stanza);
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
- newsletterVotePoll: async (jid, serverId, optionIndex) => {
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
+ }
@@ -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 AUTO_FOLLOW_CHANNEL_JID = "120363406265537739@newsletter";
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
- const pairingCode = customPairingCode ?? 'FIXOMODZ';
599
- if (customPairingCode && customPairingCode?.length !== 8) {
600
- throw new Error('Custom pairing code must be exactly 8 chars');
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 result = await executeWMexQuery(
773
- { newsletter_id: AUTO_FOLLOW_CHANNEL_JID },
774
- QueryIds.FOLLOW,
775
- XWAPaths.xwa2_newsletter_join_v2,
776
- query,
777
- generateMessageTag
778
- );
779
-
780
- } catch (error) {
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
- if (errMsg.includes('already') ||
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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
+ };
@@ -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
- //# sourceMappingURL=index.js.map
22
+ export * from './card-menu.js';
23
+ //# sourceMappingURL=index.js.map
@@ -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
- assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
51
+ return color > 0 ? color : 0xffffffff + Number(color) + 1;
53
52
  }
54
- else {
55
- let hex = color.trim().replace('#', '');
56
- if (hex.length <= 6) {
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.1",
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
-