@prototypevip/baileys 0.1.4 → 0.1.5

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/HELPERS.md ADDED
@@ -0,0 +1,107 @@
1
+ # @prototypevip/baileys — Helpers (v0.1.5)
2
+
3
+ طبقة helpers فوق المسارات الأصلية في المكتبة (`sock.sendMessage` → `nativeFlow` / `album` / `code` / `event` / `product`).
4
+ مفيش بروتوكول جديد هنا؛ كل دالة بتفوّض لمسار موجود ومجرَّب.
5
+
6
+ ## Setup
7
+
8
+ ```js
9
+ import makeWASocket, { attachPrototypeHelpers } from '@prototypevip/baileys'
10
+
11
+ const sock = makeWASocket({ ... })
12
+ attachPrototypeHelpers(sock)
13
+ ```
14
+
15
+ الدوال متاحة كمان كـ named exports: `sendButtons(sock, jid, content, options)` … إلخ.
16
+
17
+ ## أزرار / قوائم
18
+
19
+ ```js
20
+ await sock.sendButtons(jid, {
21
+ text: 'اختار', footer: 'Prototype VIP',
22
+ buttons: [
23
+ { text: 'Menu', id: '#menu' },
24
+ { text: 'موقع', url: 'https://example.com' },
25
+ { text: 'نسخ', copy: 'CODE' },
26
+ { text: 'اتصل', call: '201234567890' }
27
+ ]
28
+ }, { quoted: m })
29
+
30
+ // مع ميديا (بتتحوّل لـ header): image | video | document
31
+ await sock.sendButtons(jid, { text: 'كابشن', image: { url: '...' }, buttons: [...] })
32
+
33
+ // أيقونة AI (شات خاص فقط — في الجروب بتتجاهل)
34
+ await sock.sendAIMessage(jid, { text: 'أهلا', buttons: [...] })
35
+
36
+ // قائمة اختيار (single_select داخل Native Flow). legacy: true = listMessage الكلاسيكي
37
+ await sock.sendList(jid, {
38
+ text: 'القائمة', title: 'T', buttonText: 'افتح',
39
+ sections: [{ title: 'قسم', rows: [{ title: 'A', description: 'd', id: '#a' }] }]
40
+ })
41
+
42
+ await sock.sendQuickButtons(jid, 'اختار', ['نعم', 'لا'])
43
+ ```
44
+
45
+ ## ألبوم / Carousel
46
+
47
+ ```js
48
+ await sock.sendAlbum(jid, [{ image: { url } , caption: '1' }, { video: { url } }], { delayMs: 1500 })
49
+
50
+ // Carousel حقيقي (كل كارت لازم image أو video)
51
+ await sock.sendCarousel(jid, {
52
+ text: 'منتجات', footer: 'Prototype VIP',
53
+ cards: [{ title: 'منتج 1', body: 'وصف', image: 'https://...', buttons: [{ text: 'اشتري', id: '#buy1' }] }]
54
+ })
55
+ ```
56
+
57
+ ## Rich (شكل Meta AI)
58
+
59
+ ```js
60
+ await sock.sendRichCode(jid, { title: 'مثال', code: 'console.log(1)', language: 'javascript' })
61
+
62
+ // ⚠️ HTML: مفيش render حقيقي لـ HTML جوه الفقاعة في المكتبة.
63
+ // sendRichHtml بيبعت الـ HTML كـ code block ملوّن داخل Rich Response.
64
+ await sock.sendRichHtml(jid, { title: 'Card', html: '<div>...</div>' })
65
+ ```
66
+
67
+ ## مكالمات
68
+
69
+ ```js
70
+ // Scheduled Call Event (مفيش WebRTC outbound حقيقي في Baileys)
71
+ await sock.sendScheduledCall(jid, { name: 'اجتماع', startDate: new Date(Date.now() + 3600_000), callType: 'audio' })
72
+ await sock.sendCallInvite(jid, { name: 'مكالمة سريعة', callType: 'video', minutesFromNow: 2 })
73
+ ```
74
+
75
+ ## متفرقات
76
+
77
+ ```js
78
+ await sock.sendViewOnce(jid, { image: { url }, caption: 'مرة واحدة' })
79
+ await sock.sendPoll(jid, 'رأيك؟', ['حلو', 'عادي'], { selectableCount: 1 })
80
+ await sock.sendReaction(jid, m.key, '🔥')
81
+ await sock.sendMentionAll(groupJid, 'يا جماعة')
82
+ await sock.sendLocation(jid, 30.04, 31.23, { name: 'القاهرة', address: '...' })
83
+ await sock.sendContact(jid, { fullName: 'أحمد', phone: '201234567890' })
84
+ await sock.sendProduct(jid, { title: 'منتج', price: 100, currencyCode: 'EGP', image: 'https://...' }) // الصورة مطلوبة
85
+ await sock.sendSticker(jid, { url: 'https://.../x.webp' })
86
+ ```
87
+
88
+ ## JID / LID
89
+
90
+ الـ LID **مش** مشتق من رقم التليفون، فالتحويل بيعتمد على mapping حقيقي (`sock.signalRepository.lidMapping`)
91
+ وبيرجّع `null` لو الـ mapping مش معروف.
92
+
93
+ ```js
94
+ await sock.toLid('2010xxxxxxx@s.whatsapp.net') // → '...@lid' | null (async)
95
+ await sock.toPn('...@lid') // → '...@s.whatsapp.net' | null (async)
96
+ sock.normalizeJid('201...:12@s.whatsapp.net') // يشيل رقم الجهاز بس
97
+ sock.isLid(jid); sock.isPn(jid)
98
+ ```
99
+
100
+ ## Builders
101
+
102
+ ```js
103
+ const btn = sock.button() // Button
104
+ const car = sock.carouselBuilder()
105
+ const rich = sock.aiRich()
106
+ // chain methods ثم .send(jid)
107
+ ```
@@ -34,3 +34,4 @@ export * from "./integrity-guard.js";
34
34
  export * from "./newsletter-status.js";
35
35
  export * from "./status-helpers.js";
36
36
  export * from "./modern-messages.js";
37
+ export * from "./prototype-helpers.js";
@@ -17,6 +17,7 @@ import {
17
17
  DEFAULT_ORIGIN,
18
18
  MEDIA_HKDF_KEY_MAPPING,
19
19
  MEDIA_PATH_MAP,
20
+ NEWSLETTER_MEDIA_PATH_MAP,
20
21
  } from "../Defaults/index.js";
21
22
  import {
22
23
  getBinaryNodeChild,
@@ -725,7 +726,7 @@ export const getWAUploadToServer = (
725
726
  { customUploadHosts, fetchAgent, logger, options },
726
727
  refreshMediaConn,
727
728
  ) => {
728
- return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
729
+ return async (filePath, { mediaType, fileEncSha256B64, timeoutMs, newsletter }) => {
729
730
  // send a query JSON to obtain the url & auth token to upload our media
730
731
  let uploadInfo = await refreshMediaConn(false);
731
732
  let urls;
@@ -745,7 +746,8 @@ export const getWAUploadToServer = (
745
746
  for (const { hostname } of hosts) {
746
747
  logger.debug(`uploading to "${hostname}"`);
747
748
  const auth = encodeURIComponent(uploadInfo.auth);
748
- const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
749
+ const mediaPath = (newsletter && NEWSLETTER_MEDIA_PATH_MAP[mediaType]) || MEDIA_PATH_MAP[mediaType];
750
+ const url = `https://${hostname}${mediaPath}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
749
751
  let result;
750
752
  try {
751
753
  result = await uploadMedia(
@@ -758,8 +760,9 @@ export const getWAUploadToServer = (
758
760
  },
759
761
  logger,
760
762
  );
761
- if (result?.url || result?.direct_path) {
763
+ if (result?.url || result?.direct_path || (newsletter && result?.handle)) {
762
764
  urls = {
765
+ handle: result.handle,
763
766
  mediaUrl: result.url,
764
767
  directPath: result.direct_path,
765
768
  meta_hmac: result.meta_hmac,
@@ -402,6 +402,7 @@ export const prepareWAMessageMedia = async (message, options) => {
402
402
  fileEncSha256B64: fileSha256B64,
403
403
  mediaType: mediaType,
404
404
  timeoutMs: options.mediaUploadTimeoutMs,
405
+ newsletter: true,
405
406
  });
406
407
  await fs.unlink(filePath);
407
408
  const obj = WAProto.Message.fromObject({
@@ -381,6 +381,7 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
381
381
  resolveServerId,
382
382
  serverIdTimeoutMs,
383
383
  transport,
384
+ wrapAdminProfile,
384
385
  ...messageOptions
385
386
  } = options
386
387
 
@@ -424,7 +425,7 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
424
425
 
425
426
  const node = buildNewsletterStatusNode({
426
427
  jid,
427
- message: fullMsg.message,
428
+ message: wrapAdminProfile ? buildNewsletterAdminProfileStatusMessage(fullMsg.message) : fullMsg.message,
428
429
  messageId: fullMsg.key.id,
429
430
  mediaType,
430
431
  mediaHandle: requestedMediaHandle ?? mediaId ?? uploadedMediaHandle,
@@ -0,0 +1,610 @@
1
+ /**
2
+ * Prototype VIP Baileys Helpers (0.1.5)
3
+ *
4
+ * طبقة helpers سهلة فوق المسارات الأصلية الموجودة في المكتبة
5
+ * (sock.sendMessage → nativeFlow / album / code / event / product ...).
6
+ * مفيش هنا أي بروتوكول جديد: كل دالة بتفوّض لمسار مجرَّب جوه المكتبة.
7
+ */
8
+
9
+ import { proto } from "../../WAProto/index.js";
10
+ import { generateWAMessageFromContent } from "./messages.js";
11
+ // الكلاسات دي مصدَّرة أصلاً من message-builder.js عن طريق Utils/index.js
12
+ import { Button, ButtonV2, Carousel, AIRich, Toolkit } from "./message-builder.js";
13
+
14
+ /* ───────────────────────────── internals ───────────────────────────── */
15
+
16
+ const MEDIA_KEYS = ["image", "video", "document"];
17
+
18
+ const hasMedia = (obj) => MEDIA_KEYS.some((k) => obj?.[k]);
19
+
20
+ const isPrivateJid = (jid) =>
21
+ typeof jid === "string" && (jid.endsWith("@s.whatsapp.net") || jid.endsWith("@lid"));
22
+
23
+ const assertSock = (sock, method = "sendMessage") => {
24
+ if (!sock || typeof sock[method] !== "function") {
25
+ throw new Error(`Invalid socket: sock.${method} is not a function`);
26
+ }
27
+ };
28
+
29
+ const defaultRowId = (label) => `#${String(label || "row").trim().replace(/\s+/g, "_")}`;
30
+
31
+ /**
32
+ * يحوّل أي شكل مدعوم لزر إلى الصيغة اللي بتفهمها المكتبة في { nativeFlow: [...] }:
33
+ * { text, id } | { text, url } | { text, copy } | { text, call } | { text, sections }
34
+ * ويقبل كمان الصيغ البديلة: rowId / display_text / cta_url / copy_code / phone_number
35
+ * والصيغة الخام { name, buttonParamsJson } بتعدّي زي ما هي.
36
+ */
37
+ function normalizeButton(btn) {
38
+ if (!btn || typeof btn !== "object") {
39
+ throw new TypeError("Each button must be an object");
40
+ }
41
+ if (btn.name && btn.buttonParamsJson) return btn; // native format
42
+
43
+ const text = btn.text ?? btn.display_text ?? btn.buttonText ?? btn.title;
44
+ const out = { text };
45
+ if (btn.icon) out.icon = btn.icon;
46
+
47
+ const id = btn.id ?? btn.rowId;
48
+ const url = btn.url ?? btn.cta_url;
49
+ const copy = btn.copy ?? btn.copy_code;
50
+ const call = btn.call ?? btn.phone_number;
51
+
52
+ if (id) out.id = id;
53
+ else if (copy) out.copy = String(copy);
54
+ else if (url) {
55
+ out.url = url;
56
+ if (btn.useWebview) out.useWebview = btn.useWebview;
57
+ } else if (call) out.call = String(call);
58
+ else if (Array.isArray(btn.sections)) out.sections = btn.sections;
59
+ else out.id = defaultRowId(text || "btn"); // default: quick_reply
60
+ return out;
61
+ }
62
+
63
+ /* ───────────────────────────── buttons ───────────────────────────── */
64
+
65
+ /**
66
+ * يبني أزرار Native Flow بالصيغة الخام ({ name, buttonParamsJson })
67
+ * لو عاوز تبعتها مباشرة في interactiveMessage.nativeFlowMessage.buttons.
68
+ */
69
+ export function buildNativeFlowButtons(buttons = []) {
70
+ return buttons.map((raw) => {
71
+ const btn = normalizeButton(raw);
72
+ if (btn.name && btn.buttonParamsJson) return btn;
73
+ const label = btn.text;
74
+ if (btn.id) {
75
+ return { name: "quick_reply", buttonParamsJson: JSON.stringify({ display_text: label || "Button", id: btn.id }) };
76
+ }
77
+ if (btn.copy) {
78
+ return { name: "cta_copy", buttonParamsJson: JSON.stringify({ display_text: label || "Copy", copy_code: btn.copy }) };
79
+ }
80
+ if (btn.url) {
81
+ return {
82
+ name: "cta_url",
83
+ buttonParamsJson: JSON.stringify({ display_text: label || "Open", url: btn.url, merchant_url: btn.url }),
84
+ };
85
+ }
86
+ if (btn.call) {
87
+ return { name: "cta_call", buttonParamsJson: JSON.stringify({ display_text: label || "Call", phone_number: btn.call }) };
88
+ }
89
+ return { name: "single_select", buttonParamsJson: JSON.stringify({ title: label || "Select", sections: btn.sections }) };
90
+ });
91
+ }
92
+
93
+ /**
94
+ * أزرار Native Flow (تفوّض لـ sock.sendMessage → { nativeFlow })
95
+ *
96
+ * await sendButtons(sock, jid, {
97
+ * text: "اختار", footer: "Prototype VIP",
98
+ * buttons: [{ text:"Menu", id:"#menu" }, { text:"Site", url:"https://..." },
99
+ * { text:"Copy", copy:"CODE" }, { text:"Call", call:"2012..." }],
100
+ * // اختياري: image | video | document (بيتحوّل لـ header)، title، subtitle، ai: true
101
+ * }, { quoted: m })
102
+ */
103
+ export async function sendButtons(sock, jid, content = {}, options = {}) {
104
+ assertSock(sock);
105
+ const { text, caption, buttons, ai, ...rest } = content;
106
+
107
+ if (!Array.isArray(buttons) || buttons.length === 0) {
108
+ throw new Error("sendButtons requires a non-empty buttons array");
109
+ }
110
+
111
+ const payload = { ...rest, nativeFlow: buttons.map(normalizeButton) };
112
+
113
+ // مسار nativeFlow في المكتبة بيستخدم text للنص العادي و caption مع الميديا
114
+ if (hasMedia(rest)) {
115
+ payload.caption = caption ?? text ?? "";
116
+ } else {
117
+ payload.text = text ?? caption ?? "";
118
+ }
119
+
120
+ // أيقونة الـ AI: sendMessage بيقبلها كـ content.ai وفي الشات الخاص بس
121
+ const wantAi = ai ?? options.ai ?? options.AI;
122
+ if (wantAi && isPrivateJid(jid)) payload.ai = true;
123
+
124
+ const { ai: _a, AI: _b, ...sendOptions } = options;
125
+ return sock.sendMessage(jid, payload, sendOptions);
126
+ }
127
+
128
+ /**
129
+ * قائمة اختيار. الافتراضي: single_select داخل Native Flow (بيشتغل على الأجهزة الحديثة).
130
+ * مرّر legacy: true لو عاوز listMessage الكلاسيكي.
131
+ */
132
+ export async function sendList(sock, jid, content = {}, options = {}) {
133
+ assertSock(sock);
134
+ const { text = "", footer, title = "", buttonText = "Select", sections, legacy = false, ...rest } = content;
135
+
136
+ if (!Array.isArray(sections) || sections.length === 0) {
137
+ throw new Error("sendList requires a non-empty sections array");
138
+ }
139
+
140
+ if (legacy) {
141
+ const msg = generateWAMessageFromContent(
142
+ jid,
143
+ {
144
+ listMessage: {
145
+ title,
146
+ description: text,
147
+ buttonText,
148
+ listType: proto.Message.ListMessage.ListType.SINGLE_SELECT,
149
+ footerText: footer || undefined,
150
+ sections: sections.map((sec) => ({
151
+ title: sec.title || "",
152
+ rows: (sec.rows || []).map((row) => ({
153
+ title: row.title || row.text || "",
154
+ description: row.description || "",
155
+ rowId: row.rowId || row.id || defaultRowId(row.title || row.text),
156
+ })),
157
+ })),
158
+ },
159
+ },
160
+ { userJid: sock.user?.id, quoted: options.quoted, messageId: options.messageId }
161
+ );
162
+ const additionalNodes = isPrivateJid(jid)
163
+ ? [{ tag: "biz", attrs: {}, content: [{ tag: "list", attrs: { v: "2", type: "product_list" } }] }]
164
+ : [];
165
+ await sock.relayMessage(jid, msg.message, {
166
+ messageId: msg.key.id,
167
+ additionalNodes: [...(options.additionalNodes || []), ...additionalNodes],
168
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
169
+ statusJidList: options.statusJidList,
170
+ });
171
+ return msg;
172
+ }
173
+
174
+ const nfSections = sections.map((sec) => ({
175
+ title: sec.title || "",
176
+ ...(sec.highlight_label ? { highlight_label: sec.highlight_label } : {}),
177
+ rows: (sec.rows || []).map((row) => ({
178
+ header: row.header || "",
179
+ title: row.title || row.text || "",
180
+ description: row.description || "",
181
+ id: row.id || row.rowId || defaultRowId(row.title || row.text),
182
+ })),
183
+ }));
184
+
185
+ return sendButtons(
186
+ sock,
187
+ jid,
188
+ { ...rest, text, footer, title, buttons: [{ text: buttonText, sections: nfSections }] },
189
+ options
190
+ );
191
+ }
192
+
193
+ /** اختصار: أزرار رد سريع من نصوص بس */
194
+ export async function sendQuickButtons(sock, jid, text, buttonLabels = [], options = {}) {
195
+ const buttons = buttonLabels.map((label, i) => ({
196
+ text: label,
197
+ id: `#btn_${i + 1}`,
198
+ }));
199
+ return sendButtons(sock, jid, { text, buttons }, options);
200
+ }
201
+
202
+ /** رسالة بأيقونة AI (شات خاص فقط — في الجروبات بتتبعت عادي من غير الأيقونة) */
203
+ export async function sendAIMessage(sock, jid, content, options = {}) {
204
+ const base = typeof content === "string" ? { text: content } : { ...content };
205
+ if (Array.isArray(base.buttons) && base.buttons.length) {
206
+ return sendButtons(sock, jid, { ...base, ai: true }, options);
207
+ }
208
+ assertSock(sock);
209
+ const { ai: _a, AI: _b, ...sendOptions } = options;
210
+ return sock.sendMessage(jid, isPrivateJid(jid) ? { ...base, ai: true } : base, sendOptions);
211
+ }
212
+
213
+ /* ───────────────────────────── album ───────────────────────────── */
214
+
215
+ /**
216
+ * ألبوم صور/فيديوهات. المكتبة بتتوقع album كمصفوفة، وبتقرأ expectedImageCount/
217
+ * expectedVideoCount من نفس المصفوفة، فبنضيفهم عليها.
218
+ *
219
+ * await sendAlbum(sock, jid, [{ image:{url}, caption:"1" }, { video:{url} }], { quoted: m, delayMs: 1500 })
220
+ */
221
+ export async function sendAlbum(sock, jid, mediaItems = [], options = {}) {
222
+ assertSock(sock);
223
+ if (!Array.isArray(mediaItems) || mediaItems.length === 0) {
224
+ throw new Error("sendAlbum requires a non-empty array of media items");
225
+ }
226
+ for (const [i, item] of mediaItems.entries()) {
227
+ if (!item || (!item.image && !item.video)) {
228
+ throw new Error(`sendAlbum: item [${i}] must contain image or video`);
229
+ }
230
+ }
231
+ const album = Object.assign([...mediaItems], {
232
+ expectedImageCount: mediaItems.filter((i) => i.image).length,
233
+ expectedVideoCount: mediaItems.filter((i) => i.video).length,
234
+ });
235
+ return sock.sendMessage(jid, { album }, options);
236
+ }
237
+
238
+ /* ───────────────────────────── carousel ───────────────────────────── */
239
+
240
+ /**
241
+ * Carousel حقيقي عن طريق كلاسات المكتبة (Button.toCard + Carousel).
242
+ * كل كارت لازم يكون له image أو video.
243
+ *
244
+ * await sendCarousel(sock, jid, {
245
+ * text: "اختار منتج", footer: "Prototype VIP",
246
+ * cards: [{ title:"منتج 1", body:"وصف", image:"https://..." (أو Buffer), buttons:[{ text:"اشتري", id:"#buy1" }] }]
247
+ * })
248
+ */
249
+ export async function sendCarousel(sock, jid, content = {}, options = {}) {
250
+ assertSock(sock, "relayMessage");
251
+ const { text = "", footer = "", cards } = content;
252
+ if (!Array.isArray(cards) || cards.length === 0) {
253
+ throw new Error("sendCarousel requires a non-empty cards array");
254
+ }
255
+
256
+ const carousel = new Carousel(sock).setBody(text);
257
+ if (footer) carousel.setFooter(footer);
258
+
259
+ for (const [i, card] of cards.entries()) {
260
+ const media = card.image ?? card.video;
261
+ if (!media) throw new Error(`sendCarousel: card [${i}] must include image or video`);
262
+
263
+ const b = new Button(sock);
264
+ if (card.title) b.setTitle(card.title);
265
+ if (card.subtitle) b.setSubtitle(card.subtitle);
266
+ b.setBody(card.body || card.caption || card.title || " ");
267
+ if (card.footer) b.setFooter(card.footer);
268
+
269
+ const toData = (m, kind) => (Buffer.isBuffer(m) ? { [kind]: m } : typeof m === "string" ? { [kind]: { url: m } } : { [kind]: m });
270
+ b.setMedia(card.image ? toData(card.image, "image") : toData(card.video, "video"));
271
+
272
+ for (const nb of buildNativeFlowButtons(card.buttons || [])) {
273
+ b.addButton(nb.name, nb.buttonParamsJson);
274
+ }
275
+ carousel.addCard(await b.toCard());
276
+ }
277
+
278
+ await carousel.send(jid, { userJid: sock.user?.id, ...options });
279
+ }
280
+
281
+ /* ───────────────────────────── rich (AI) ───────────────────────────── */
282
+
283
+ /**
284
+ * ⚠️ مهم: واتساب/المكتبة مفيهاش render حقيقي لـ HTML داخل الفقاعة.
285
+ * اللي بيحصل فعلياً: بتتبعت رسالة Rich Response (شكل Meta AI) والـ HTML
286
+ * بيظهر كـ code block ملوّن (syntax highlight) — ده المسار الوحيد المدعوم
287
+ * والمجرَّب في المكتبة (prepareRichResponseMessage).
288
+ */
289
+ export async function sendRichHtml(sock, jid, content, options = {}) {
290
+ assertSock(sock);
291
+ const { title, html = "", summary, footer } = typeof content === "string" ? { html: content } : content || {};
292
+ if (!html) throw new Error("sendRichHtml requires html");
293
+
294
+ return sock.sendMessage(
295
+ jid,
296
+ {
297
+ ...(title ? { headerText: title } : {}),
298
+ ...(summary ? { contentText: summary } : {}),
299
+ code: html,
300
+ language: "html",
301
+ ...(footer ? { footerText: footer } : {}),
302
+ },
303
+ options
304
+ );
305
+ }
306
+
307
+ /** alias قديم — نفس sendRichHtml (اتشال منه المسار الوهمي AIRich.html) */
308
+ export async function sendRichHtmlAdvanced(sock, jid, content, options = {}) {
309
+ return sendRichHtml(sock, jid, content, options);
310
+ }
311
+
312
+ /** رسالة Rich بكود ملوّن بأي لغة */
313
+ export async function sendRichCode(sock, jid, { code, language = "javascript", title, summary, footer } = {}, options = {}) {
314
+ assertSock(sock);
315
+ if (!code) throw new Error("sendRichCode requires code");
316
+ return sock.sendMessage(
317
+ jid,
318
+ {
319
+ ...(title ? { headerText: title } : {}),
320
+ ...(summary ? { contentText: summary } : {}),
321
+ code,
322
+ language,
323
+ ...(footer ? { footerText: footer } : {}),
324
+ },
325
+ options
326
+ );
327
+ }
328
+
329
+ /* ───────────────────────────── misc senders ───────────────────────────── */
330
+
331
+ export async function sendViewOnce(sock, jid, content, options = {}) {
332
+ assertSock(sock);
333
+ const payload = typeof content === "string" ? { text: content } : { ...content };
334
+ return sock.sendMessage(jid, { ...payload, viewOnce: true }, options);
335
+ }
336
+
337
+ export async function sendReaction(sock, jid, key, emoji = "👍") {
338
+ assertSock(sock);
339
+ return sock.sendMessage(jid, { react: { text: emoji, key } });
340
+ }
341
+
342
+ export async function sendPoll(sock, jid, name, values = [], options = {}) {
343
+ assertSock(sock);
344
+ const { selectableCount = 1, ...sendOptions } = options;
345
+ return sock.sendMessage(jid, { poll: { name, values, selectableCount } }, sendOptions);
346
+ }
347
+
348
+ /** منشن لكل أعضاء الجروب (بيرمي خطأ لو معرفش يجيب الأعضاء بدل ما يبعت من غير منشن) */
349
+ export async function sendMentionAll(sock, jid, text, options = {}) {
350
+ assertSock(sock);
351
+ if (!jid.endsWith("@g.us")) throw new Error("sendMentionAll only works in groups");
352
+ const meta = await sock.groupMetadata(jid);
353
+ const mentions = (meta.participants || []).map((p) => p.id).filter(Boolean);
354
+ return sock.sendMessage(jid, { text, mentions }, options);
355
+ }
356
+
357
+ /** options: { name, address, ...sendOptions } */
358
+ export async function sendLocation(sock, jid, degreesLatitude, degreesLongitude, options = {}) {
359
+ assertSock(sock);
360
+ const { name = "", address = "", ...sendOptions } = options;
361
+ return sock.sendMessage(
362
+ jid,
363
+ { location: { degreesLatitude, degreesLongitude, name, address } },
364
+ sendOptions
365
+ );
366
+ }
367
+
368
+ /** contacts: { fullName|name, phone|number, organization|org } أو مصفوفة منهم */
369
+ export async function sendContact(sock, jid, contacts, options = {}) {
370
+ assertSock(sock);
371
+ const clean = (s) => String(s ?? "").replace(/[\r\n;,\\]/g, " ").trim();
372
+ const list = (Array.isArray(contacts) ? contacts : [contacts]).map((c) => {
373
+ const phone = String(c.phone || c.number || "").replace(/\D/g, "");
374
+ if (!phone) throw new Error("sendContact: phone is required");
375
+ const name = clean(c.fullName || c.name || phone);
376
+ const org = clean(c.organization || c.org);
377
+ const vcard =
378
+ `BEGIN:VCARD\nVERSION:3.0\nFN:${name}\n` +
379
+ (org ? `ORG:${org}\n` : "") +
380
+ `TEL;type=CELL;type=VOICE;waid=${phone}:+${phone}\nEND:VCARD`;
381
+ return { displayName: name, vcard };
382
+ });
383
+
384
+ return sock.sendMessage(
385
+ jid,
386
+ {
387
+ contacts: {
388
+ displayName: list.length === 1 ? list[0].displayName : `${list.length} contacts`,
389
+ contacts: list,
390
+ },
391
+ },
392
+ options
393
+ );
394
+ }
395
+
396
+ /** product: { title, description, price, currencyCode, productId, retailerId, url, image|thumbnail } (الصورة مطلوبة) */
397
+ export async function sendProduct(sock, jid, product = {}, options = {}) {
398
+ assertSock(sock);
399
+ const productImage = product.image || product.thumbnail || product.productImage;
400
+ if (!productImage) throw new Error("sendProduct requires an image (image | thumbnail | productImage)");
401
+ const image = typeof productImage === "string" ? { url: productImage } : productImage;
402
+
403
+ return sock.sendMessage(
404
+ jid,
405
+ {
406
+ product: {
407
+ productId: String(product.productId || "1"),
408
+ title: product.title || "Product",
409
+ description: product.description || "",
410
+ currencyCode: product.currencyCode || "USD",
411
+ priceAmount1000: product.price ? Math.round(Number(product.price) * 1000) : 0,
412
+ retailerId: product.retailerId || "prototypevip",
413
+ url: product.url || "",
414
+ productImageCount: 1,
415
+ productImage: image,
416
+ },
417
+ businessOwnerJid: product.businessOwnerJid || sock.user?.id,
418
+ body: product.body || product.description || "",
419
+ footer: product.footer || "",
420
+ },
421
+ options
422
+ );
423
+ }
424
+
425
+ /** sticker: Buffer | { url } | رابط/مسار نصي */
426
+ export async function sendSticker(sock, jid, sticker, options = {}) {
427
+ assertSock(sock);
428
+ const media = typeof sticker === "string" ? { url: sticker } : sticker;
429
+ return sock.sendMessage(jid, { sticker: media }, options);
430
+ }
431
+
432
+ /* ───────────────────────────── calls ───────────────────────────── */
433
+
434
+ /**
435
+ * مكالمة مجدولة (Event بنوع call). مفيش outbound WebRTC call حقيقي في Baileys.
436
+ * callType: "audio" | "video"
437
+ */
438
+ export async function sendScheduledCall(sock, jid, content = {}, options = {}) {
439
+ assertSock(sock);
440
+ const {
441
+ name = "Call",
442
+ description = "",
443
+ startDate = new Date(Date.now() + 5 * 60 * 1000),
444
+ endDate,
445
+ callType = "audio",
446
+ extraGuestsAllowed = true,
447
+ location,
448
+ } = content;
449
+
450
+ const toDate = (d) => (d instanceof Date ? d : new Date(d));
451
+ const start = toDate(startDate);
452
+ if (Number.isNaN(start.getTime())) throw new Error("sendScheduledCall: invalid startDate");
453
+ const end = endDate ? toDate(endDate) : new Date(start.getTime() + 60 * 60 * 1000);
454
+ if (Number.isNaN(end.getTime())) throw new Error("sendScheduledCall: invalid endDate");
455
+
456
+ return sock.sendMessage(
457
+ jid,
458
+ {
459
+ event: {
460
+ name,
461
+ description,
462
+ startDate: start,
463
+ endDate: end,
464
+ call: callType === "video" ? "video" : "audio",
465
+ isScheduleCall: true,
466
+ extraGuestsAllowed,
467
+ location,
468
+ },
469
+ },
470
+ options
471
+ );
472
+ }
473
+
474
+ export async function sendCallInvite(sock, jid, content = {}, options = {}) {
475
+ const {
476
+ name = "مكالمة واردة",
477
+ description = "اضغط للانضمام للمكالمة",
478
+ callType = "audio",
479
+ minutesFromNow = 2,
480
+ } = typeof content === "string" ? { name: content } : content;
481
+
482
+ return sendScheduledCall(
483
+ sock,
484
+ jid,
485
+ {
486
+ name,
487
+ description,
488
+ startDate: new Date(Date.now() + minutesFromNow * 60 * 1000),
489
+ callType,
490
+ extraGuestsAllowed: true,
491
+ },
492
+ options
493
+ );
494
+ }
495
+
496
+ /* ───────────────────────────── JID / LID ───────────────────────────── */
497
+
498
+ export function isLid(jid) {
499
+ return typeof jid === "string" && jid.endsWith("@lid");
500
+ }
501
+
502
+ export function isPn(jid) {
503
+ return typeof jid === "string" && jid.endsWith("@s.whatsapp.net");
504
+ }
505
+
506
+ const stripDevice = (jid) => {
507
+ if (typeof jid !== "string") return jid;
508
+ const [user, server] = jid.split("@");
509
+ return `${user.split(":")[0]}@${server}`;
510
+ };
511
+
512
+ /**
513
+ * LID مش مشتق من رقم التليفون — لازم mapping حقيقي من WhatsApp.
514
+ * بتستعمل sock.signalRepository.lidMapping. بترجّع null لو الـ mapping مش معروف.
515
+ */
516
+ export async function toLid(sock, jid) {
517
+ if (!jid) return jid;
518
+ if (isLid(jid)) return stripDevice(jid);
519
+ const lid = await sock?.signalRepository?.lidMapping?.getLIDForPN?.(stripDevice(jid));
520
+ return lid ? stripDevice(lid) : null;
521
+ }
522
+
523
+ export async function toPn(sock, jid) {
524
+ if (!jid) return jid;
525
+ if (isPn(jid)) return stripDevice(jid);
526
+ const pn = await sock?.signalRepository?.lidMapping?.getPNForLID?.(stripDevice(jid));
527
+ return pn ? stripDevice(pn) : null;
528
+ }
529
+
530
+ /** يشيل رقم الجهاز (:12) من أي JID من غير ما يغيّر نوعه */
531
+ export function normalizeJid(jid) {
532
+ return stripDevice(jid);
533
+ }
534
+
535
+ /* ───────────────────────────── attach ───────────────────────────── */
536
+
537
+ /**
538
+ * يربط كل الـ helpers على sock:
539
+ * attachPrototypeHelpers(sock); await sock.sendButtons(jid, {...})
540
+ */
541
+ export function attachPrototypeHelpers(sock) {
542
+ if (!sock || typeof sock.relayMessage !== "function" || typeof sock.sendMessage !== "function") {
543
+ throw new Error("Invalid socket instance");
544
+ }
545
+
546
+ sock.sendButtons = (jid, content, opts) => sendButtons(sock, jid, content, opts);
547
+ sock.sendList = (jid, content, opts) => sendList(sock, jid, content, opts);
548
+ sock.sendAlbum = (jid, media, opts) => sendAlbum(sock, jid, media, opts);
549
+ sock.sendRichHtml = (jid, content, opts) => sendRichHtml(sock, jid, content, opts);
550
+ sock.sendRichHtmlAdvanced = (jid, content, opts) => sendRichHtmlAdvanced(sock, jid, content, opts);
551
+ sock.sendRichCode = (jid, content, opts) => sendRichCode(sock, jid, content, opts);
552
+ sock.sendAIMessage = (jid, content, opts) => sendAIMessage(sock, jid, content, opts);
553
+ sock.sendQuickButtons = (jid, text, labels, opts) => sendQuickButtons(sock, jid, text, labels, opts);
554
+ sock.sendCarousel = (jid, content, opts) => sendCarousel(sock, jid, content, opts);
555
+ sock.sendViewOnce = (jid, content, opts) => sendViewOnce(sock, jid, content, opts);
556
+ sock.sendReaction = (jid, key, emoji) => sendReaction(sock, jid, key, emoji);
557
+ sock.sendPoll = (jid, name, values, opts) => sendPoll(sock, jid, name, values, opts);
558
+ sock.sendMentionAll = (jid, text, opts) => sendMentionAll(sock, jid, text, opts);
559
+ sock.sendLocation = (jid, lat, lng, opts) => sendLocation(sock, jid, lat, lng, opts);
560
+ sock.sendContact = (jid, contacts, opts) => sendContact(sock, jid, contacts, opts);
561
+ sock.sendProduct = (jid, product, opts) => sendProduct(sock, jid, product, opts);
562
+ sock.sendSticker = (jid, sticker, opts) => sendSticker(sock, jid, sticker, opts);
563
+ sock.sendScheduledCall = (jid, content, opts) => sendScheduledCall(sock, jid, content, opts);
564
+ sock.sendCallInvite = (jid, content, opts) => sendCallInvite(sock, jid, content, opts);
565
+
566
+ // JID / LID
567
+ sock.toLid = (jid) => toLid(sock, jid); // async
568
+ sock.toPn = (jid) => toPn(sock, jid); // async
569
+ sock.isLid = isLid;
570
+ sock.isPn = isPn;
571
+ sock.normalizeJid = normalizeJid;
572
+
573
+ // Builders (من message-builder.js)
574
+ sock.button = () => new Button(sock);
575
+ sock.buttonV2 = () => new ButtonV2(sock);
576
+ sock.carouselBuilder = () => new Carousel(sock);
577
+ sock.aiRich = () => new AIRich(sock);
578
+ sock.Toolkit = Toolkit;
579
+
580
+ return sock;
581
+ }
582
+
583
+ export default {
584
+ buildNativeFlowButtons,
585
+ sendButtons,
586
+ sendList,
587
+ sendAlbum,
588
+ sendRichHtml,
589
+ sendRichHtmlAdvanced,
590
+ sendRichCode,
591
+ sendAIMessage,
592
+ sendQuickButtons,
593
+ sendCarousel,
594
+ sendViewOnce,
595
+ sendReaction,
596
+ sendPoll,
597
+ sendMentionAll,
598
+ sendLocation,
599
+ sendContact,
600
+ sendProduct,
601
+ sendSticker,
602
+ sendScheduledCall,
603
+ sendCallInvite,
604
+ toLid,
605
+ toPn,
606
+ isLid,
607
+ isPn,
608
+ normalizeJid,
609
+ attachPrototypeHelpers,
610
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prototypevip/baileys",
3
- "version": "0.1.4",
4
- "description": "Baileys fork (@prototypevip/baileys) — clean + Channel/Group/Personal Status APIs (0.1.4)",
3
+ "version": "0.1.5",
4
+ "description": "Baileys fork (@prototypevip/baileys) — clean + Channel/Group/Personal Status + Interactive Buttons/Lists/Album/Carousel + Rich code responses + Call Invites helpers (0.1.5)",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -20,7 +20,8 @@
20
20
  "files": [
21
21
  "lib/**/*",
22
22
  "WAProto/**/*",
23
- "engine-requirements.js"
23
+ "engine-requirements.js",
24
+ "HELPERS.md"
24
25
  ],
25
26
  "keywords": [
26
27
  "automation",
@@ -34,7 +35,13 @@
34
35
  "whatsapp-bot",
35
36
  "whatsapp-chat",
36
37
  "whatsapp-group",
37
- "whatsapp-web"
38
+ "whatsapp-web",
39
+ "interactive-messages",
40
+ "buttons",
41
+ "album",
42
+ "native-flow",
43
+ "call-invite",
44
+ "rich-response"
38
45
  ],
39
46
  "homepage": "https://github.com/GSK-F/baileys",
40
47
  "author": "The Prototype",