ishumdz-bail 1.0.27 → 1.0.29

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
@@ -698,12 +698,15 @@ Action buttons (`url` / `copy` / `call`) never send a reply — nothing to handl
698
698
  ### Base Compatibility — cantarella / Case X Style Code (v1.0.27+)
699
699
 
700
700
  Code copied from other Baileys bases (cantarella-baileys, Case X Plugin YOUHU MD, etc.)
701
- runs without changes. Three shapes are accepted:
701
+ runs without changes — and renders as a **horizontal one-line row** just like those
702
+ bases. Three shapes are accepted:
702
703
 
703
- **1) Raw `buttonsMessage` classic menu card — even the risky mix.** A raw classic
704
- message that mixes `nativeFlowInfo` (single_select sheet) or `cta_*` buttons with
705
- plain buttons is **auto-upgraded to a native-flow interactive card**, so it can never
706
- render the "version doesn't support it" bubble:
704
+ **1) Raw `buttonsMessage` classic menu card — mixed extras are flattened.** A raw
705
+ classic message that mixes `nativeFlowInfo` (single_select sheet) or `cta_*` buttons
706
+ with plain buttons **stays classic horizontal**: the header (location/image/video)
707
+ is preserved, every extra becomes a plain button with its original `buttonId`, and
708
+ its click arrives as a normal `buttonsResponseMessage` — so your bot can send the
709
+ list sheet / URL action itself (e.g. a separate `nativeFlow: true` message):
707
710
 
708
711
  ```javascript
709
712
  // Case X / cantarella style — works as-is:
@@ -745,8 +748,60 @@ await sock.sendMessage(jid, { interactiveMessage: { buttons: [{ name: 'quick_rep
745
748
 
746
749
  **3) Plain raw `buttonsMessage`** (no native-flow extras) stays classic and gets the
747
750
  automatic invisible-PNG header when you omit one. Flow params are accepted as
748
- `paramsJson` or `buttonParamsJson`, and `single_select` / `cta_url` / `cta_copy` /
749
- `cta_call` / `quick_reply` names are preserved as-is.
751
+ `paramsJson` or `buttonParamsJson`.
752
+
753
+ > 💡 **Want the tap-to-open sheet too?** Send it as a second message when the button
754
+ > is clicked (see `examples/menu-buttons.js` — the `allmenus` case). Mixing a sheet
755
+ > into the same classic row is what produced the "doesn't support it" bubble; the
756
+ > library keeps the row safe by flattening extras, and the two-message pattern gives
757
+ > you both layouts.
758
+
759
+ ### One-Tap Horizontal Button → Automatic List Sheet — v1.0.28+
760
+
761
+ `attachSheetAutoReply` does the two-message pattern **for you**: register it once,
762
+ and when the user taps a horizontal button, the list sheet is sent automatically —
763
+ no extra code in your `messages.upsert` handler:
764
+
765
+ ```javascript
766
+ const { makeWASocket, attachSheetAutoReply } = require('ishumdz-bail')
767
+
768
+ const sock = makeWASocket({ auth: state, ... })
769
+
770
+ // register ONCE, right after creating the socket:
771
+ attachSheetAutoReply(sock, {
772
+ // click id → full sheet (auto-sent, click never reaches your parser)
773
+ allmenus: {
774
+ text: '📂 *ALL MENUS* — tap a section 👇',
775
+ footer: 'ISHAN-X MD PRO',
776
+ buttonText: '📂 open menu',
777
+ sectionTitle: 'All Menus',
778
+ highlightLabel: '⭐ MAIN',
779
+ rows: [
780
+ { title: '📥 Download Menu', description: 'Downloader commands', rowId: '.downloadmenu' },
781
+ { title: '✨ AI Menu', description: 'AI commands', rowId: '.aimenu' },
782
+ { title: '🎮 Game Menu', description: 'Fun & games', rowId: '.gamemenu' }
783
+ ]
784
+ },
785
+ // shorthand: click id → rewrite into a normal command for your parser
786
+ support: '.owner'
787
+ })
788
+
789
+ // then your horizontal row stays 100% plain → guaranteed one-line render:
790
+ await sock.sendMessage(jid, {
791
+ location: { degreesLatitude: 0, degreesLongitude: 0,
792
+ name: 'ISHAN-X MD PRO', address: '📂 Select Menu 👇' },
793
+ text: menuText, footer,
794
+ buttons: [
795
+ { buttonText: { displayText: '📂 allmenus' }, buttonId: 'allmenus', type: 1 },
796
+ { buttonText: { displayText: '👤 owner' }, buttonId: 'owner', type: 1 },
797
+ { buttonText: { displayText: '⚡ ping' }, buttonId: 'ping', type: 1 }
798
+ ]
799
+ }, { quoted: msg })
800
+ // user taps "allmenus" → sheet opens instantly; taps "owner" → your parser gets '.owner'
801
+ ```
802
+
803
+ Also exported: `sendSheet(sock, jid, entry, quoted)` (send a sheet manually) and
804
+ `decodeButtonClick(msg)` (the click bridge, shipped with the library).
750
805
 
751
806
  ---
752
807
 
@@ -0,0 +1,179 @@
1
+ /**
2
+ * sheet-buttons.js — one-tap horizontal button → automatic list sheet (v1.0.28+)
3
+ *
4
+ * Classic horizontal rows cannot carry a native-flow sheet inside the SAME
5
+ * message on current clients (that mix renders as "version doesn't support
6
+ * it"). The working pattern is TWO messages: the horizontal row, and — when
7
+ * the user clicks the menu button — a native-flow list sheet sent
8
+ * automatically. This module does the second half for you:
9
+ *
10
+ * const { attachSheetAutoReply } = require('ishumdz-bail')
11
+ *
12
+ * attachSheetAutoReply(sock, {
13
+ * // click id → full sheet definition
14
+ * allmenus: {
15
+ * text: '📂 *ALL MENUS* — tap a section 👇',
16
+ * footer: 'ISHAN-X MD PRO',
17
+ * buttonText: '📂 open menu',
18
+ * highlightLabel: '⭐ MAIN',
19
+ * sectionTitle: 'All Menus',
20
+ * rows: [
21
+ * { title: '📥 Download Menu', description: 'Downloaders', rowId: '.downloadmenu' },
22
+ * { title: '✨ AI Menu', description: 'AI commands', rowId: '.aimenu' }
23
+ * ]
24
+ * },
25
+ * // or a shorthand: rewrite the click into another command
26
+ * support: '.owner'
27
+ * })
28
+ *
29
+ * Now a horizontal row can stay 100% plain (guaranteed one-line render):
30
+ *
31
+ * await sock.sendMessage(jid, {
32
+ * location: { degreesLatitude: 0, degreesLongitude: 0,
33
+ * name: 'ISHAN-X MD PRO', address: '📂 Select Menu 👇' },
34
+ * text: menuText, footer,
35
+ * buttons: [
36
+ * { buttonText: { displayText: '📂 allmenus' }, buttonId: 'allmenus', type: 1 },
37
+ * { buttonText: { displayText: '👤 owner' }, buttonId: 'owner', type: 1 }
38
+ * ]
39
+ * })
40
+ *
41
+ * User taps "allmenus" → the sheet opens instantly, and your own
42
+ * messages.upsert handler is NOT called for that click (consumed).
43
+ * A shorthand entry rewrites the click into the given command text and
44
+ * lets your normal command parser handle it.
45
+ */
46
+
47
+ // Decode any button / sheet-row / list-row click into its id (same bridge
48
+ // the examples use, now shipped with the library).
49
+ export function decodeButtonClick(msg) {
50
+ // classic horizontal quick-reply buttons
51
+ const classic = msg?.message?.buttonsResponseMessage?.selectedButtonId
52
+ if (classic) return classic
53
+
54
+ // native flow (sheet rows / card quick replies)
55
+ const nf = msg?.message?.interactiveResponseMessage?.nativeFlowResponseMessage
56
+ if (nf?.paramsJson) {
57
+ try {
58
+ const p = JSON.parse(nf.paramsJson)
59
+ return p.id || p.selectedId || p.selected_row_id || null
60
+ } catch { /* ignore */ }
61
+ }
62
+
63
+ // classic listMessage rows
64
+ const list = msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId
65
+ if (list) return list
66
+
67
+ return null
68
+ }
69
+
70
+ const normalizeRows = (rows) => (rows || []).map(r => ({
71
+ title: r.title,
72
+ ...(r.description ? { description: r.description } : {}),
73
+ id: r.rowId ?? r.id ?? r.title
74
+ }))
75
+
76
+ const buildSections = (entry) => {
77
+ if (Array.isArray(entry.sections) && entry.sections.length) {
78
+ return entry.sections.map(s => ({
79
+ ...(s.title ? { title: s.title } : {}),
80
+ ...(s.highlight_label || s.highlightLabel ? { highlight_label: s.highlight_label || s.highlightLabel } : {}),
81
+ rows: normalizeRows(s.rows)
82
+ }))
83
+ }
84
+ return [{
85
+ title: entry.sectionTitle || entry.buttonText || 'Menu',
86
+ ...(entry.highlightLabel ? { highlight_label: entry.highlightLabel } : {}),
87
+ rows: normalizeRows(entry.rows)
88
+ }]
89
+ }
90
+
91
+ const normalizeHandlers = (handlers) => {
92
+ const sheets = {}
93
+ const commands = {}
94
+ for (const [id, val] of Object.entries(handlers || {})) {
95
+ if (typeof val === 'string') {
96
+ commands[id] = val
97
+ } else if (val && (Array.isArray(val.rows) || Array.isArray(val.sections))) {
98
+ sheets[id] = val
99
+ }
100
+ }
101
+ return { sheets, commands }
102
+ }
103
+
104
+ /**
105
+ * Manually send a native-flow list sheet (the tap-to-open menu).
106
+ * attachSheetAutoReply calls this for you on a matching click.
107
+ */
108
+ export async function sendSheet(sock, jid, entry, quoted) {
109
+ const sections = buildSections(entry)
110
+ return sock.sendMessage(jid, {
111
+ text: entry.text || '📋 Select an option 👇',
112
+ ...(entry.footer ? { footer: entry.footer } : {}),
113
+ nativeFlow: true,
114
+ buttons: [{
115
+ buttonText: { displayText: entry.buttonText || '☰ menu' },
116
+ buttonId: entry.buttonId || 'sheet',
117
+ type: 1,
118
+ sections
119
+ }]
120
+ }, quoted ? { quoted } : {})
121
+ }
122
+
123
+ /**
124
+ * Register automatic sheet replies on a socket.
125
+ *
126
+ * @param {object} sock the makeWASocket() instance
127
+ * @param {object} handlers map of clickId → sheet definition { text?, footer?,
128
+ * buttonText?, sectionTitle?, highlightLabel?, rows|sections }
129
+ * OR clickId → '.command' string shorthand
130
+ * @returns {Function} uninstall — restores the original sock.ev.on
131
+ */
132
+ export function attachSheetAutoReply(sock, handlers) {
133
+ const { sheets, commands } = normalizeHandlers(handlers)
134
+
135
+ async function handleUpsert(payload) {
136
+ const { messages, type } = payload || {}
137
+ if (type !== 'notify' || !Array.isArray(messages)) return false
138
+ for (const msg of messages) {
139
+ if (!msg?.message || msg.key?.fromMe) continue
140
+ const clicked = decodeButtonClick(msg)
141
+ if (!clicked) continue
142
+
143
+ // 1) full sheet definition → send the sheet, consume the click
144
+ const entry = sheets[clicked]
145
+ if (entry) {
146
+ try {
147
+ await sendSheet(sock, msg.key.remoteJid, { ...entry, buttonId: clicked }, msg)
148
+ } catch (e) {
149
+ try { sock.logger?.error?.({ err: e }, 'sheet auto-reply failed') } catch { /* ignore */ }
150
+ }
151
+ return true
152
+ }
153
+
154
+ // 2) command shorthand → rewrite into normal command text for the parser
155
+ const cmd = commands[clicked]
156
+ if (cmd) {
157
+ msg.message = { conversation: cmd }
158
+ return false
159
+ }
160
+ }
161
+ return false
162
+ }
163
+
164
+ // Wrap the user's messages.upsert registration so the sheet fires first
165
+ // and consumed clicks never reach the bot's command parser.
166
+ const origOn = sock.ev.on.bind(sock.ev)
167
+ sock.ev.on = function (ev, handler) {
168
+ if (ev === 'messages.upsert') {
169
+ return origOn(ev, async (...args) => {
170
+ const consumed = await handleUpsert(args[0]).catch(() => false)
171
+ if (consumed) return true
172
+ return handler(...args)
173
+ })
174
+ }
175
+ return origOn(ev, handler)
176
+ }
177
+
178
+ return () => { sock.ev.on = origOn }
179
+ }
@@ -699,56 +699,23 @@ export const generateWAMessageContent = async (message, options) => {
699
699
  const rawExtras = (inputButtons || []).filter(b =>
700
700
  b?.nativeFlowInfo?.name || Number(b?.type) === proto.Message.ButtonsMessage.Button.Type.NATIVE_FLOW)
701
701
  if (rawExtras.length) {
702
- const rawLoc = raw.locationMessage
703
- const flowButtons = inputButtons.map(b => {
704
- const name = b?.nativeFlowInfo?.name
705
- if (name === 'single_select') {
706
- let params = b.nativeFlowInfo.paramsJson
707
- try { params = JSON.stringify(JSON.parse(params)) } catch { params = typeof params === 'string' ? params : '{}' }
708
- return { name: 'single_select', buttonParamsJson: params }
709
- }
710
- if (name) {
711
- return { name, buttonParamsJson: b.nativeFlowInfo.buttonParamsJson || b.nativeFlowInfo.paramsJson || '{}' }
712
- }
713
- return {
714
- name: 'quick_reply',
715
- buttonParamsJson: JSON.stringify({
716
- display_text: b.buttonText?.displayText || '',
717
- id: b.buttonId || b.buttonText?.displayText || ''
718
- })
719
- }
720
- })
721
- const interactiveMessage = {
722
- nativeFlowMessage: { buttons: flowButtons, messageParamsJson: '', messageVersion: 1 }
723
- }; const bodyText = raw.contentText || (raw.imageMessage?.caption ?? raw.videoMessage?.caption ?? '') || ''
724
- if (bodyText) interactiveMessage.body = { text: bodyText };
725
- if (raw.footerText) interactiveMessage.footer = { text: raw.footerText };
726
- const header = { title: raw.text || (interactiveMessage.body.text ? String(interactiveMessage.body.text).split('\n')[0].slice(0, 42) : '') || 'Menu', subtitle: '', hasMediaAttachment: false };
727
- if (rawLoc) {
728
- header.locationMessage = {
729
- degreesLatitude: rawLoc.degreesLatitude ?? 0,
730
- degreesLongitude: rawLoc.degreesLongitude ?? 0,
731
- name: rawLoc.name ?? null,
732
- address: rawLoc.address ?? null,
733
- ...(rawLoc.jpegThumbnail ? { jpegThumbnail: rawLoc.jpegThumbnail } : {})
734
- };
735
- header.hasMediaAttachment = true;
736
- } else if (raw.imageMessage) {
737
- header.imageMessage = raw.imageMessage;
738
- header.hasMediaAttachment = true;
739
- } else if (raw.videoMessage) {
740
- header.videoMessage = raw.videoMessage;
741
- header.hasMediaAttachment = true;
742
- } else if (raw.documentMessage) {
743
- header.documentMessage = raw.documentMessage;
744
- header.hasMediaAttachment = true;
745
- }
746
- if (header.title || header.hasMediaAttachment) interactiveMessage.header = header;
747
- if (raw.contextInfo?.mentionedJid?.length) {
748
- interactiveMessage.contextInfo = { mentionedJid: raw.contextInfo.mentionedJid };
749
- }
750
- m = { interactiveMessage };
751
- } else {
702
+ // PATCH (1.0.28): a raw classic message that MIXES native-flow extras
703
+ // (single_select nativeFlowInfo, cta_* buttons) into a ButtonsMessage is
704
+ // the Case X / cantarella menu-card pattern — on current clients it
705
+ // renders as "version doesn't support it" (real-device evidence).
706
+ // Instead of upgrading to the vertical native-flow card, keep the
707
+ // HORIZONTAL classic layout: every extra becomes a plain RESPONSE
708
+ // button with its original buttonId, clicks arrive as
709
+ // buttonsResponseMessage, and the bot sends the list sheet / URL /
710
+ // copy action itself (e.g. a separate nativeFlow message). The header
711
+ // (location/image/video/document) is preserved untouched — that is the
712
+ // proven horizontal Case X / cantarella render.
713
+ raw.buttons = inputButtons.map(b => proto.Message.ButtonsMessage.Button.fromObject({
714
+ buttonId: b?.buttonId || b?.buttonText?.displayText || 'button',
715
+ buttonText: { displayText: b?.buttonText?.displayText || b?.displayText || 'Button' },
716
+ type: proto.Message.ButtonsMessage.Button.Type.RESPONSE
717
+ }))
718
+ }
752
719
  if (!hasHeader) {
753
720
  try {
754
721
  const media = await prepareWAMessageMedia({ image: BUTTONS_INVISIBLE_PNG() }, options)
@@ -757,7 +724,6 @@ export const generateWAMessageContent = async (message, options) => {
757
724
  } catch { /* upload failed — send as-is */ }
758
725
  }
759
726
  m = { buttonsMessage: raw };
760
- }
761
727
  }
762
728
  else if ('buttons' in message && !!message.buttons) {
763
729
  // PATCH: `buttons` API — HORIZONTAL button row via classic ButtonsMessage
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@ import makeWASocket from './Socket/index.js';
2
2
  import chalk from "chalk";
3
3
  import { enableCallAutoAnswer, getActiveVoipClient, CallState } from './Caller/auto-answer.js';
4
4
  import { attachProMethods } from './Pro/index.js';
5
+ import { attachSheetAutoReply, sendSheet, decodeButtonClick } from './Pro/sheet-buttons.js';
5
6
  import * as PollsPro from './Pro/polls.js';
6
7
  import * as NewsletterPro from './Pro/newsletter.js';
7
8
  import * as StatusPro from './Pro/status.js';
@@ -91,5 +92,8 @@ export const removeProfilePicture = ProfilePro.removeProfilePicture;
91
92
  export const rejectCall = ProfilePro.rejectCall;
92
93
 
93
94
  export { enableCallAutoAnswer, getActiveVoipClient, CallState, attachProMethods, makeWASocket };
95
+
96
+ // One-tap horizontal button → automatic list sheet (v1.0.28+)
97
+ export { attachSheetAutoReply, sendSheet, decodeButtonClick };
94
98
  export default makeWASocket;
95
99
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ishumdz-bail",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "WhatsApp API Modification By Lovely",
5
5
  "keywords": [
6
6
  "whatsapp",