ishumdz-bail 1.0.21 → 1.0.24

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
@@ -423,6 +423,19 @@ await sock.sendMessage(jid, {
423
423
  > card + horizontal row, and a **custom image or GIF** works with one media upload.
424
424
  > Set `{ nativeFlow: true }` for vertical native-flow quick_reply buttons instead.
425
425
 
426
+ ### Button Types — Quick Reference
427
+
428
+ | Button property | Tap behavior | Since |
429
+ | ------------------------- | --------------------------------------------- | ------- |
430
+ | `buttonId` + `buttonText` | Quick reply — sends the id back to your bot | v1.0.19 |
431
+ | `rows` / `sections` | Opens a highlighted list sheet on tap | v1.0.22 |
432
+ | `url` | Opens the link / WhatsApp channel immediately | v1.0.23 |
433
+ | `copy` | Copies the code to the clipboard | v1.0.23 |
434
+ | `call` | Opens the dialer with the number | v1.0.23 |
435
+
436
+ All of them mix freely in one message and render as a single horizontal row
437
+ (when the message has a header) — see the full example below.
438
+
426
439
  ### Text + Horizontal Buttons (location header = zero uploads)
427
440
 
428
441
  ```javascript
@@ -483,6 +496,87 @@ await sock.sendMessage(jid, {
483
496
  })
484
497
  ```
485
498
 
499
+ ### Tap-to-Open List Sheet (rows on a button) — v1.0.22+
500
+
501
+ Give any horizontal button a `rows` or `sections` array and **tapping it opens a
502
+ highlighted list sheet** — the same sheet classic `listMessage` shows, with section
503
+ titles, a `highlight_label` badge and row descriptions:
504
+
505
+ ```javascript
506
+ await sock.sendMessage(jid, {
507
+ image: { url: './banner.jpg' }, // or gif / location / video header
508
+ caption: 'Pong 9 Ms ⚡',
509
+ footer: 'ISHAN-X MD PRO',
510
+ buttons: [
511
+ { buttonText: { displayText: '☰ menu' }, buttonId: 'menu', type: 1 },
512
+ { buttonText: { displayText: '🔍 owner' }, buttonId: 'owner', type: 1 },
513
+ // 📂 this button opens the sheet on tap:
514
+ {
515
+ buttonText: { displayText: '📂 allmenus' },
516
+ buttonId: 'allmenus', type: 1,
517
+ highlightLabel: '⭐ MAIN', // optional — badge on the section
518
+ sectionTitle: 'All Menus', // optional — section heading
519
+ rows: [
520
+ { title: '📥 Download Menu', description: 'Downloader commands', rowId: '.downloadmenu' },
521
+ { title: '✨ AI Menu', description: 'AI commands', rowId: '.aimenu' }
522
+ ]
523
+ },
524
+ // multiple sections? pass `sections` instead of `rows`:
525
+ {
526
+ buttonText: { displayText: '⚙️ settings' }, buttonId: 'settings', type: 1,
527
+ sections: [
528
+ { title: 'Main', highlight_label: '⭐ MAIN', rows: [ /* ... */ ] },
529
+ { title: 'More', rows: [ /* ... */ ] }
530
+ ]
531
+ }
532
+ ]
533
+ })
534
+ ```
535
+
536
+ **How it works:**
537
+
538
+ - `rows` — single-section shortcut (optional `sectionTitle` and `highlightLabel` on the button)
539
+ - `sections` — full control: multiple sections, each with its own `title` and `highlight_label`
540
+ - Each row accepts `{ title, description?, rowId }` (`id` also works)
541
+ - Tapping a row sends back an `interactiveResponseMessage` — parse its `paramsJson`
542
+ for the row id (the click bridge below handles this)
543
+ - Works in both layouts: the classic horizontal row (with header) and `{ nativeFlow: true }` vertical
544
+
545
+ ### One-Click Action Buttons (URL / Copy / Call) — v1.0.23+
546
+
547
+ Give a button a `url`, `copy` or `call` value and it becomes a **one-click action
548
+ button** — tapping performs the action directly instead of sending a reply back
549
+ to your bot (opens a link/channel, copies a code, opens the dialer):
550
+
551
+ ```javascript
552
+ await sock.sendMessage(jid, {
553
+ image: { url: './banner.jpg' }, // location / gif / video header um ok
554
+ caption: 'Pong 9 Ms ⚡',
555
+ footer: 'ISHAN-X MD PRO',
556
+ buttons: [
557
+ { buttonText: { displayText: '☰ menu' }, buttonId: 'menu', type: 1 },
558
+ // 📢 one tap → channel/page opens:
559
+ { buttonText: { displayText: '📢 Channel' }, url: 'https://whatsapp.com/channel/0029V...' },
560
+ // 🎁 one tap → code copies to clipboard:
561
+ { buttonText: { displayText: '🎁 Voucher' }, copy: 'ISHAN2026' },
562
+ // 📞 one tap → dialer opens:
563
+ { buttonText: { displayText: '📞 Call Owner' }, call: '919999999999' },
564
+ // a tap-to-open sheet button can share the same message:
565
+ { buttonText: { displayText: '📂 allmenus' }, buttonId: 'allmenus',
566
+ highlightLabel: '⭐ MAIN', rows: [ /* ... */ ] }
567
+ ]
568
+ })
569
+ ```
570
+
571
+ **How it works:**
572
+
573
+ - `url` → native-flow `cta_url` (WhatsApp channel link, website, wa.me — anything)
574
+ - `copy` → `cta_copy` (coupon codes, voucher codes, pairing codes)
575
+ - `call` → `cta_call` (phone number including country code)
576
+ - Plain buttons + sheet buttons + action buttons all mix in **one horizontal row**
577
+ - Action buttons never send a reply to your bot — nothing to handle in `messages.upsert`
578
+ - Works in groups too — the group auto-converter preserves the native-flow names
579
+
486
580
  ### Text-only (TEXT header, best-effort) & native flow escape hatch
487
581
 
488
582
  ```javascript
@@ -500,36 +594,106 @@ await sock.sendMessage(jid, {
500
594
  })
501
595
  ```
502
596
 
597
+ ### Full Example — Everything in One Message
598
+
599
+ A complete bot reply: location map-card header, a plain quick-reply, a highlighted
600
+ list-sheet button and a one-click channel button — all in one horizontal row:
601
+
602
+ ```javascript
603
+ await sock.sendMessage(jid, {
604
+ location: {
605
+ degreesLatitude: 9.9312,
606
+ degreesLongitude: 76.2673,
607
+ name: 'ISHAN-X MD PRO',
608
+ address: 'Select an option below'
609
+ },
610
+ text: 'Pong 9 Ms ⚡\nDeveloper: ISHAN-X × LOVELY',
611
+ footer: 'ISHAN-X MD PRO',
612
+ buttons: [
613
+ // 1) plain quick-reply → sends 'menu' back to your bot
614
+ { buttonText: { displayText: '☰ menu' }, buttonId: 'menu', type: 1 },
615
+ // 2) tap-to-open highlighted list sheet
616
+ {
617
+ buttonText: { displayText: '📂 allmenus' },
618
+ buttonId: 'allmenus', type: 1,
619
+ highlightLabel: '⭐ MAIN',
620
+ sectionTitle: 'All Menus',
621
+ rows: [
622
+ { title: '📥 Download Menu', description: 'Downloader commands', rowId: '.downloadmenu' },
623
+ { title: '✨ AI Menu', description: 'AI commands', rowId: '.aimenu' }
624
+ ]
625
+ },
626
+ // 3) one-click action → opens your channel, nothing is sent back
627
+ { buttonText: { displayText: '📢 Channel' }, url: 'https://whatsapp.com/channel/0029V...' }
628
+ ]
629
+ }, { quoted: msg })
630
+ ```
631
+
632
+ > ⚠️ **Limit:** the classic horizontal row renders up to **3 buttons** per message on
633
+ > most clients. Need more? Use `{ nativeFlow: true }` for a vertical layout that
634
+ > supports more buttons, or split across two messages.
635
+
503
636
  ### Handling Button Clicks
504
637
 
505
- Button replies arrive in `messages.upsert` — native flow uses
506
- `interactiveResponseMessage`, classic uses `buttonsResponseMessage`:
638
+ Button and row taps arrive in `messages.upsert`. Three shapes are possible:
639
+ classic quick replies → `buttonsResponseMessage`, native-flow taps (sheet rows and
640
+ vertical quick replies) → `interactiveResponseMessage`, classic list rows →
641
+ `listResponseMessage`. This one bridge converts all three into a plain command id —
642
+ drop it above your command parser so every existing case keeps working:
507
643
 
508
644
  ```javascript
645
+ function resolveButtonResponse(msg) {
646
+ // classic horizontal quick-reply buttons
647
+ const classic = msg.message?.buttonsResponseMessage?.selectedButtonId
648
+ if (classic) return classic
649
+
650
+ // native flow: sheet rows and vertical quick replies
651
+ const nf = msg.message?.interactiveResponseMessage?.nativeFlowResponseMessage
652
+ if (nf?.paramsJson) {
653
+ try {
654
+ const p = JSON.parse(native.paramsJson)
655
+ return p.id || p.selectedId || p.selected_row_id || null
656
+ } catch { /* ignore */ }
657
+ }
658
+
659
+ // classic listMessage rows
660
+ const list = msg.message?.listResponseMessage?.singleSelectReply?.selectedRowId
661
+ if (list) return list
662
+
663
+ return null
664
+ }
665
+
509
666
  sock.ev.on('messages.upsert', async ({ messages, type }) => {
510
667
  if (type !== 'notify') return
511
668
  const msg = messages[0]
512
669
  if (!msg.message || msg.key.fromMe) return
513
670
 
514
- let clickedId = msg.message.buttonsResponseMessage?.selectedButtonId
515
- const native = msg.message.interactiveResponseMessage?.nativeFlowResponseMessage
516
- if (!clickedId && native?.paramsJson) {
517
- try { clickedId = JSON.parse(native.paramsJson).id } catch {}
518
- }
671
+ // turn any button / row tap into normal command text:
672
+ const clicked = resolveButtonResponse(msg)
673
+ if (clicked) msg.message = { conversation: clicked }
519
674
 
520
- if (clickedId === 'menu') await sock.sendMessage(msg.key.remoteJid, { text: '📋 MENU...' })
521
- if (clickedId === 'owner') await sock.sendMessage(msg.key.remoteJid, { text: '👑 Owner...' })
675
+ // your existing command parser keeps working untouched:
676
+ // switch (command) { case 'menu': ... case 'owner': ... }
522
677
  })
523
678
  ```
524
679
 
680
+ Action buttons (`url` / `copy` / `call`) never send a reply — nothing to handle here.
681
+
525
682
  > **Notes**
526
- > - Groups: `listMessage` / `buttonsMessage` / `templateMessage` are auto-converted to
527
- > interactive native flow on send — buttons work in groups too.
528
- > - WhatsApp Web and very old clients don't render native flow cards — mobile clients
529
- > (recent Android/iOS) render them fully.
530
- > - Full working demo: [`examples/ping-buttons.js`](examples/ping-buttons.js)
531
- > (`.ping` → image + horizontal buttons + click handling) and a visual preview in
532
- > [`banner-preview.html`](banner-preview.html).
683
+ > - **Groups:** `listMessage` / `buttonsMessage` / `templateMessage` are auto-converted
684
+ > to interactive native flow on send — and buttons that carry a native-flow action
685
+ > (list sheet, URL, copy, call) are preserved as-is, so sheets and one-click
686
+ > actions work in groups too (v1.0.23+).
687
+ > - **Client support:** modern Android / iOS / Business clients render everything on
688
+ > this page. WhatsApp Web and very old clients may show only the vertical
689
+ > native-flow layout.
690
+ > - **Media headers:** one media header per message (image, GIF or video). The
691
+ > location map card needs **no media upload at all**.
692
+ > - Full working demos: [`examples/ping-buttons.js`](examples/ping-buttons.js)
693
+ > (`.ping` → image + horizontal buttons + click handling) and
694
+ > [`examples/menu-buttons.js`](examples/menu-buttons.js)
695
+ > (`.menu` → custom image/GIF + buttons + highlighted menu sheet), plus a visual
696
+ > preview in [`banner-preview.html`](banner-preview.html).
533
697
 
534
698
  ---
535
699
 
@@ -1157,7 +1321,8 @@ Made with ❤️ by **Lovely**
1157
1321
 
1158
1322
  ## 🚀 Hosting / Troubleshooting (OptikLink, Pterodactyl, etc.)
1159
1323
 
1160
- Bot hosting panels la `npm install` fail aana, idhu dhaan common reasons:
1324
+ If `npm install` fails on bot hosting panels (OptikLink, Pterodactyl, etc.), these are
1325
+ the most common reasons:
1161
1326
 
1162
1327
  ### ❌ `npm error code EALLOWGIT`
1163
1328
 
@@ -602,16 +602,24 @@ export const makeMessagesSocket = (config) => {
602
602
  header: list.title? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
603
603
  contextInfo: list.contextInfo
604
604
  }
605
- }
606
- } else if (message.buttonsMessage) {
607
- const bMsg = message.buttonsMessage
608
- const buttons = (bMsg.buttons || []).map(btn => ({
609
- name: 'quick_reply',
610
- buttonParamsJson: JSON.stringify({
611
- display_text: btn.buttonText?.displayText || btn.buttonText || '',
612
- id: btn.buttonId || btn.buttonText?.displayText || ''
613
- })
614
- }))
605
+ } } else if (message.buttonsMessage) {
606
+ const bMsg = message.buttonsMessage
607
+ // PATCH: buttons that carry nativeFlowInfo (single_select sheet /
608
+ // cta_url / cta_copy / cta_call) keep their native flow name so
609
+ // sheets and one-click actions still work in groups — only plain
610
+ // buttons degrade to quick_reply.
611
+ const buttons = (bMsg.buttons || []).map(btn => {
612
+ if (btn.nativeFlowInfo?.name) {
613
+ return { name: btn.nativeFlowInfo.name, buttonParamsJson: btn.nativeFlowInfo.paramsJson || '{}' }
614
+ }
615
+ return {
616
+ name: 'quick_reply',
617
+ buttonParamsJson: JSON.stringify({
618
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
619
+ id: btn.buttonId || btn.buttonText?.displayText || ''
620
+ })
621
+ }
622
+ })
615
623
  message = {
616
624
  interactiveMessage: {
617
625
  nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
@@ -524,6 +524,62 @@ const BUTTONS_INVISIBLE_PNG = () => Buffer.from(
524
524
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
525
525
  'base64'
526
526
  )
527
+ // PATCH: rows/sections on a button → tapping it opens a native-flow list
528
+ // sheet (the same sheet classic listMessage shows). A button may carry
529
+ // inline `rows` (auto-wrapped in one section, optional button-level
530
+ // `highlightLabel` / `sectionTitle`) or a full `sections` array where each
531
+ // section is { title?, highlight_label?, rows: [{ title, description?, rowId|id }] }.
532
+ const buildButtonSections = (b) => {
533
+ let sections = null
534
+ if (Array.isArray(b?.sections) && b.sections.length) {
535
+ sections = b.sections
536
+ } else if (Array.isArray(b?.rows) && b.rows.length) {
537
+ sections = [{
538
+ title: b.sectionTitle || b.buttonText?.displayText || b.displayText || 'Menu',
539
+ ...(b.highlightLabel ? { highlight_label: b.highlightLabel } : {}),
540
+ rows: b.rows
541
+ }]
542
+ }
543
+ if (!sections) return null
544
+ return sections.map(s => ({
545
+ ...(s.title ? { title: s.title } : {}),
546
+ ...(s.highlight_label || s.highlightLabel ? { highlight_label: s.highlight_label || s.highlightLabel } : {}),
547
+ rows: (s.rows || []).map(r => ({
548
+ title: r.title,
549
+ ...(r.description ? { description: r.description } : {}),
550
+ id: r.rowId ?? r.id ?? r.title
551
+ }))
552
+ }))
553
+ }
554
+ // PATCH: one-click action buttons — `url` opens a link/channel, `copy`
555
+ // copies a code, `call` dials a number. Sent as native-flow CTA buttons
556
+ // (classic: type NATIVE_FLOW + nativeFlowInfo; nativeFlow: cta_* names).
557
+ const buildActionFlow = (b) => {
558
+ if (b?.url) return { name: 'cta_url', params: { display_text: b.buttonText?.displayText || b.displayText || 'Open', url: b.url, merchant_url: b.url } }
559
+ if (b?.copy) return { name: 'cta_copy', params: { display_text: b.buttonText?.displayText || b.displayText || 'Copy', id: b.buttonId || b.buttonText?.displayText || 'copy', copy_code: b.copy } }
560
+ if (b?.call) return { name: 'cta_call', params: { display_text: b.buttonText?.displayText || b.displayText || 'Call', phone_number: String(b.call) } }
561
+ return null
562
+ }
563
+ const normalizeClassicButton = (b) => {
564
+ const out = { ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE }
565
+ const action = buildActionFlow(b)
566
+ if (action) {
567
+ out.type = proto.Message.ButtonsMessage.Button.Type.NATIVE_FLOW
568
+ out.nativeFlowInfo = { name: action.name, paramsJson: JSON.stringify(action.params) }
569
+ return out
570
+ }
571
+ const sections = buildButtonSections(b)
572
+ if (sections) {
573
+ out.nativeFlowInfo = {
574
+ name: 'single_select',
575
+ paramsJson: JSON.stringify({
576
+ title: b.buttonText?.displayText || b.displayText || 'Menu',
577
+ sections
578
+ })
579
+ }
580
+ }
581
+ return out
582
+ }
527
583
  export const generateWAMessageContent = async (message, options) => {
528
584
  var _a, _b;
529
585
  let m = {};
@@ -610,13 +666,29 @@ export const generateWAMessageContent = async (message, options) => {
610
666
  // thumbnail for text-only). Set { nativeFlow: true } to opt out and get
611
667
  // vertical native-flow quick_reply buttons instead.
612
668
  if (message.nativeFlow === true) {
613
- const flowButtons = message.buttons.map(b => ({
614
- name: 'quick_reply',
615
- buttonParamsJson: JSON.stringify({
616
- display_text: b.buttonText?.displayText || b.displayText || b.text || '',
617
- id: b.buttonId || b.id || b.buttonText?.displayText || ''
618
- })
619
- }));
669
+ const flowButtons = message.buttons.map(b => {
670
+ const action = buildActionFlow(b);
671
+ if (action) {
672
+ return { name: action.name, buttonParamsJson: JSON.stringify(action.params) };
673
+ }
674
+ const sections = buildButtonSections(b);
675
+ if (sections) {
676
+ return {
677
+ name: 'single_select',
678
+ buttonParamsJson: JSON.stringify({
679
+ title: b.buttonText?.displayText || b.displayText || 'Menu',
680
+ sections
681
+ })
682
+ };
683
+ }
684
+ return {
685
+ name: 'quick_reply',
686
+ buttonParamsJson: JSON.stringify({
687
+ display_text: b.buttonText?.displayText || b.displayText || b.text || '',
688
+ id: b.buttonId || b.id || b.buttonText?.displayText || ''
689
+ })
690
+ };
691
+ });
620
692
  const interactiveMessage = {
621
693
  nativeFlowMessage: {
622
694
  buttons: flowButtons,
@@ -638,7 +710,7 @@ export const generateWAMessageContent = async (message, options) => {
638
710
  else if ('location' in message && !!message.location) {
639
711
  const loc = message.location;
640
712
  const buttonsMessage = {
641
- buttons: message.buttons.map(b => ({ ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE })),
713
+ buttons: message.buttons.map(b => normalizeClassicButton(b)),
642
714
  contentText: message.text || message.caption || '',
643
715
  headerType: ButtonType.LOCATION,
644
716
  locationMessage: {
@@ -662,7 +734,7 @@ export const generateWAMessageContent = async (message, options) => {
662
734
  }
663
735
  else {
664
736
  const buttonsMessage = {
665
- buttons: message.buttons.map(b => ({ ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE }))
737
+ buttons: message.buttons.map(b => normalizeClassicButton(b))
666
738
  };
667
739
  if ('footer' in message && !!message.footer) {
668
740
  buttonsMessage.footerText = message.footer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ishumdz-bail",
3
- "version": "1.0.21",
3
+ "version": "1.0.24",
4
4
  "description": "WhatsApp API Modification By Lovely",
5
5
  "keywords": [
6
6
  "whatsapp",