@prototypevip/baileys 0.1.5 → 0.1.6

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 CHANGED
@@ -1,4 +1,4 @@
1
- # @prototypevip/baileys — Helpers (v0.1.5)
1
+ # @prototypevip/baileys — Helpers (v0.1.6)
2
2
 
3
3
  طبقة helpers فوق المسارات الأصلية في المكتبة (`sock.sendMessage` → `nativeFlow` / `album` / `code` / `event` / `product`).
4
4
  مفيش بروتوكول جديد هنا؛ كل دالة بتفوّض لمسار موجود ومجرَّب.
@@ -105,3 +105,45 @@ const car = sock.carouselBuilder()
105
105
  const rich = sock.aiRich()
106
106
  // chain methods ثم .send(jid)
107
107
  ```
108
+
109
+ ---
110
+
111
+ ## 0.1.6 — View-Once لكل الأنواع
112
+
113
+ الـ viewOnce بقى بيشتغل على **أي** نوع رسالة (نص، ملف، أزرار، قوائم، كاروسيل، منتج، لوكيشن، جهة اتصال، Rich ...) مش صورة/فيديو/صوت بس.
114
+
115
+ ```js
116
+ // 1) جوه الـ content
117
+ await sock.sendMessage(jid, { text: 'x', nativeFlow: [{ text: 'A', id: '#a' }], viewOnce: true })
118
+ // 2) أو في الـ options (شغال مع كل الـ helpers)
119
+ await sock.sendButtons(jid, { text: 'x', buttons }, { viewOnce: true })
120
+ await sock.sendList(jid, { text: 'x', sections }, { viewOnce: true })
121
+ await sock.sendCarousel(jid, { text: 'x', cards }, { viewOnce: true })
122
+ await sock.sendAlbum(jid, items, { viewOnce: true }) // كل عنصر بيتلف لوحده
123
+ await sock.sendDocument(jid, { url, fileName }, { viewOnce: true })
124
+ // 3) smart: بيوجّه لوحده حسب شكل الـ content
125
+ await sock.sendViewOnce(jid, { text: 'x', cards }) // → sendCarousel
126
+ // 4) Builders
127
+ await new Carousel(sock).setBody('x').addCard(card).setViewOnce().send(jid)
128
+ ```
129
+
130
+ قيم الـ flag: `true` (viewOnceMessage) · `"v2"` (viewOnceMessageV2) · `"force"` (يلف حتى استيكر/استطلاع/ألبوم) · `{ version: 2, force: true }`.
131
+
132
+ - رسايل التحكم (edit / react / delete / رد التصويت) **مش بتتلف** أبداً.
133
+ - الاستيكر والاستطلاع والألبوم بيتخطوا إلا مع `"force"`.
134
+ - اللف في البروتوكول مضمون؛ سلوك "يختفي بعد أول فتح" للأزرار والكاروسيل بيعتمد على نسخة واتساب عند المستلم.
135
+ - Utils جديدة: `wrapViewOnce` · `unwrapViewOnce` · `isViewOnceContent` · `canWrapViewOnce` · `parseViewOnce`.
136
+
137
+ ### Helpers جديدة
138
+ | Helper | الوظيفة |
139
+ |---|---|
140
+ | `sendDocument(jid, {url\|document, fileName, caption}, opts)` | ملف مع mimetype تلقائي |
141
+ | `sendEdit(jid, key, content)` | تعديل رسالة |
142
+ | `sendDelete(jid, key)` | حذف للكل |
143
+ | `sendWithTyping(jid, content, {typingMs, presence})` | "بيكتب..." ثم إرسال |
144
+
145
+ ### إصلاحات
146
+ - `text` مع `nativeFlow`/`buttons` كان بيتبعت نص عادي من غير أزرار → اتصلح.
147
+ - `contextInfo`/`mentions` مع viewOnce كانوا بيتكتبوا على الغلاف → بقوا على الرسالة الداخلية.
148
+ - `communities.js`: `isPnUser`/`isLidUser` ناقصين import → اتصلحوا.
149
+ - `sendCarousel` بقى بيرجّع الرسالة المرسلة.
@@ -10,6 +10,8 @@ import {
10
10
  getBinaryNodeChild,
11
11
  getBinaryNodeChildren,
12
12
  getBinaryNodeChildString,
13
+ isLidUser,
14
+ isPnUser,
13
15
  jidEncode,
14
16
  jidNormalizedUser,
15
17
  } from "../WABinary/index.js";
@@ -43,6 +43,7 @@ import {
43
43
  } from "../Utils/index.js";
44
44
  import { AssociationType } from "../Types/index.js";
45
45
  import { getUrlInfo } from "../Utils/link-preview.js";
46
+ import { wrapViewOnce } from "../Utils/view-once.js";
46
47
  import { makeKeyedMutex, makeMutex } from "../Utils/make-mutex.js";
47
48
  import {
48
49
  getMessageReportingToken,
@@ -1678,10 +1679,12 @@ export const makeMessagesSocket = (config) => {
1678
1679
  } else {
1679
1680
  const interactiveHandler = new InteractiveMessageHandler(Utils_1, waUploadToServer, relayMessage, emitUpsert);
1680
1681
  const messageType = interactiveHandler.detectType(content);
1682
+ // View-Once موحَّد (0.1.6): content.viewOnce أو options.viewOnce
1683
+ const viewOnceFlag = content.viewOnce || options.viewOnce;
1681
1684
  if (messageType) {
1682
1685
  switch (messageType) {
1683
1686
  case 'PAYMENT': {
1684
- const paymentContent = await interactiveHandler.handlePayment(content, options.quoted);
1687
+ const paymentContent = wrapViewOnce(await interactiveHandler.handlePayment(content, options.quoted), viewOnceFlag);
1685
1688
  const paymentMsg = generateWAMessageFromContent(jid, paymentContent, { userJid: userJid });
1686
1689
  await relayMessage(jid, paymentMsg.message, {
1687
1690
  messageId: paymentMsg.key.id,
@@ -1691,7 +1694,7 @@ export const makeMessagesSocket = (config) => {
1691
1694
  return paymentMsg;
1692
1695
  }
1693
1696
  case 'PRODUCT': {
1694
- const productContent = await interactiveHandler.handleProduct(content, jid, options.quoted);
1697
+ const productContent = wrapViewOnce(await interactiveHandler.handleProduct(content, jid, options.quoted), viewOnceFlag);
1695
1698
  const productMsg = await generateWAMessageFromContent(jid, productContent, { quoted: options.quoted });
1696
1699
  await relayMessage(jid, productMsg.message, {
1697
1700
  messageId: productMsg.key.id,
@@ -1701,7 +1704,7 @@ export const makeMessagesSocket = (config) => {
1701
1704
  return productMsg;
1702
1705
  }
1703
1706
  case 'INTERACTIVE': {
1704
- const interactiveContent = await interactiveHandler.handleInteractive(content, jid, options.quoted);
1707
+ const interactiveContent = wrapViewOnce(await interactiveHandler.handleInteractive(content, jid, options.quoted), viewOnceFlag);
1705
1708
  const interactiveMsg = await generateWAMessageFromContent(jid, interactiveContent, { quoted: options.quoted });
1706
1709
  await relayMessage(jid, interactiveMsg.message, {
1707
1710
  messageId: interactiveMsg.key.id,
@@ -1711,7 +1714,7 @@ export const makeMessagesSocket = (config) => {
1711
1714
  return interactiveMsg;
1712
1715
  }
1713
1716
  case 'ALBUM':
1714
- return await interactiveHandler.handleAlbum(content, jid, options.quoted);
1717
+ return await interactiveHandler.handleAlbum(content, jid, options.quoted, viewOnceFlag);
1715
1718
  case 'EVENT':
1716
1719
  return await interactiveHandler.handleEvent(content, jid, options.quoted);
1717
1720
  case 'POLL_RESULT':
@@ -1846,7 +1849,12 @@ export const makeMessagesSocket = (config) => {
1846
1849
 
1847
1850
  if ("album" in content) {
1848
1851
  const { delayMs = 1500 } = options;
1849
- for (const albumMedia of content.album) {
1852
+ for (const albumItem of content.album) {
1853
+ // View-Once على الألبوم: كل عنصر (صورة/فيديو) بيتلف لوحده، والـ parent مش بيتلف
1854
+ const albumMedia =
1855
+ content.viewOnce && albumItem.viewOnce === undefined
1856
+ ? { ...albumItem, viewOnce: content.viewOnce }
1857
+ : albumItem;
1850
1858
  const albumMsg = await generateWAMessage(jid, albumMedia, {
1851
1859
  logger: logger,
1852
1860
  userJid: userJid,
@@ -2,6 +2,7 @@ export * from "./make-mutex.js";
2
2
  export * from "./generics.js";
3
3
  export * from "./decode-wa-message.js";
4
4
  export * from "./messages.js";
5
+ export * from "./view-once.js";
5
6
  export * from "./messages-media.js";
6
7
  export * from "./validate-connection.js";
7
8
  export * from "./crypto.js";
@@ -272,7 +272,7 @@ export default class InteractiveMessageHandler {
272
272
  };
273
273
  }
274
274
 
275
- async handleAlbum(content, jid, quoted) {
275
+ async handleAlbum(content, jid, quoted, viewOnce) {
276
276
  const array = content.albumMessage;
277
277
  const album = await this.utils.generateWAMessageFromContent(jid, {
278
278
  messageContextInfo: {
@@ -295,9 +295,11 @@ export default class InteractiveMessageHandler {
295
295
  this.emitUpsert(album);
296
296
 
297
297
  for (let content of array) {
298
- const img = await this.utils.generateWAMessage(jid, content, {
299
- upload: this.waUploadToServer,
300
- });
298
+ const img = await this.utils.generateWAMessage(
299
+ jid,
300
+ viewOnce && content.viewOnce === undefined ? { ...content, viewOnce } : content,
301
+ { upload: this.waUploadToServer },
302
+ );
301
303
 
302
304
  img.message.messageContextInfo = {
303
305
  messageSecret: crypto.randomBytes(32),
@@ -32,6 +32,7 @@ import crypto from 'crypto';
32
32
  import ffmpeg from 'fluent-ffmpeg';
33
33
  import { PassThrough, Readable } from 'stream';
34
34
  import { generateWAMessageFromContent, prepareWAMessageMedia } from './messages.js';
35
+ import { wrapViewOnce } from './view-once.js';
35
36
 
36
37
  // sharp اختيارية (peerDependency) — نحمّلها ديناميكيًا وقت الاستخدام فعليًا
37
38
  // بدل static import، عشان لو حد ركّب المكتبة من غير ما يثبتها، المكتبة كلها
@@ -504,6 +505,13 @@ class BaseBuilder {
504
505
  this._footer = '';
505
506
  this._contextInfo = {};
506
507
  this._extraPayload = {};
508
+ this._viewOnce = false;
509
+ }
510
+
511
+ /** View-Once: true | "v2" | "force" | { version, force } — أو false لإلغائه */
512
+ setViewOnce(flag = true) {
513
+ this._viewOnce = flag;
514
+ return this;
507
515
  }
508
516
 
509
517
  setTitle(title) {
@@ -793,17 +801,21 @@ export class Button extends BaseBuilder {
793
801
 
794
802
  async build(jid, { ...options } = {}) {
795
803
  const message = await this.toCard();
804
+ const { viewOnce, ...buildOptions } = options;
796
805
 
797
806
  return generateWAMessageFromContent(
798
807
  jid,
799
- {
800
- ...this._extraPayload,
801
- interactiveMessage: {
802
- ...message,
803
- contextInfo: this._contextInfo,
808
+ wrapViewOnce(
809
+ {
810
+ ...this._extraPayload,
811
+ interactiveMessage: {
812
+ ...message,
813
+ contextInfo: this._contextInfo,
814
+ },
804
815
  },
805
- },
806
- { ...options }
816
+ viewOnce ?? this._viewOnce
817
+ ),
818
+ { ...buildOptions }
807
819
  );
808
820
  }
809
821
 
@@ -881,9 +893,10 @@ export class ButtonV2 extends BaseBuilder {
881
893
 
882
894
  async build(jid, { ...options } = {}) {
883
895
  let _thumbnail = this._image ? await Toolkit.resize(Buffer.isBuffer(this._image) ? this._image : await Toolkit.fetchBuffer(this._image, {}, { silent: true }), 300, 300) : null;
896
+ const { viewOnce, ...buildOptions } = options;
884
897
  const msg = generateWAMessageFromContent(
885
898
  jid,
886
- {
899
+ wrapViewOnce({
887
900
  ...this._extraPayload,
888
901
  buttonsMessage: {
889
902
  contentText: this._body,
@@ -900,12 +913,11 @@ export class ButtonV2 extends BaseBuilder {
900
913
  jpegThumbnail: _thumbnail,
901
914
  },
902
915
  }),
903
- viewOnce: true,
904
916
  contextInfo: this._contextInfo,
905
917
  buttons: [...this._buttons],
906
918
  },
907
- },
908
- { ...options }
919
+ }, viewOnce ?? this._viewOnce),
920
+ { ...buildOptions }
909
921
  );
910
922
  return msg;
911
923
  }
@@ -963,23 +975,27 @@ export class Carousel extends BaseBuilder {
963
975
  }
964
976
 
965
977
  build(jid, { ...options } = {}) {
978
+ const { viewOnce, ...buildOptions } = options;
966
979
  return generateWAMessageFromContent(
967
980
  jid,
968
- {
969
- ...this._extraPayload,
970
- interactiveMessage: {
971
- header: {
972
- hasMediaAttachment: false,
973
- },
974
- body: { text: this._body },
975
- footer: { text: this._footer },
976
- contextInfo: this._contextInfo,
977
- carouselMessage: {
978
- cards: this._cards,
981
+ wrapViewOnce(
982
+ {
983
+ ...this._extraPayload,
984
+ interactiveMessage: {
985
+ header: {
986
+ hasMediaAttachment: false,
987
+ },
988
+ body: { text: this._body },
989
+ footer: { text: this._footer },
990
+ contextInfo: this._contextInfo,
991
+ carouselMessage: {
992
+ cards: this._cards,
993
+ },
979
994
  },
980
995
  },
981
- },
982
- { ...options }
996
+ viewOnce ?? this._viewOnce
997
+ ),
998
+ { ...buildOptions }
983
999
  );
984
1000
  }
985
1001
 
@@ -37,6 +37,7 @@ import {
37
37
  toBuffer,
38
38
  } from "./messages-media.js";
39
39
  import { shouldIncludeReportingToken } from "./reporting-utils.js";
40
+ import { wrapViewOnce, isViewOnceContent, getInnerContentType, unwrapViewOnce } from "./view-once.js";
40
41
  import { isAnimatedWebP, isWebPBuffer, writeExif } from "./sticker-utils.js";
41
42
  const MIMETYPE_MAP = {
42
43
  image: "image/jpeg",
@@ -705,7 +706,12 @@ export const generateWAMessageContent = async (message, options) => {
705
706
  ) {
706
707
  // مضافة من مكتبة itsliaaa: رسالة Rich Response (Meta AI) — جداول/كود ملوّن
707
708
  m = prepareRichResponseMessage(message);
708
- } else if (hasNonNullishProperty(message, "text")) {
709
+ } else if (
710
+ hasNonNullishProperty(message, "text") &&
711
+ // لو فيه أزرار (nativeFlow / buttons) الرسالة تفاعلية مش نص عادي
712
+ !hasNonNullishProperty(message, "nativeFlow") &&
713
+ !(Array.isArray(message.buttons) && message.buttons.length > 0)
714
+ ) {
709
715
  const extContent = { text: message.text };
710
716
  let urlInfo = message.linkPreview;
711
717
  if (typeof urlInfo === "undefined") {
@@ -1021,19 +1027,25 @@ export const generateWAMessageContent = async (message, options) => {
1021
1027
  } else {
1022
1028
  m = await prepareWAMessageMedia(message, options);
1023
1029
  }
1024
- if (
1025
- hasOptionalProperty(message, "viewOnce") &&
1026
- !!message.viewOnce &&
1027
- !m.viewOnceMessage
1028
- ) {
1029
- m = { viewOnceMessage: { message: m } };
1030
+ // View-Once موحَّد (0.1.6): بيشتغل على أي نوع رسالة (مش صورة/فيديو/صوت بس)،
1031
+ // من content.viewOnce أو options.viewOnce. رسايل التحكم (edit/react/delete...) بتتخطى.
1032
+ {
1033
+ const viewOnceFlag =
1034
+ hasOptionalProperty(message, "viewOnce") && !!message.viewOnce
1035
+ ? message.viewOnce
1036
+ : options?.viewOnce;
1037
+ if (viewOnceFlag && !hasNonNullishProperty(message, "edit") && !isViewOnceContent(m)) {
1038
+ m = wrapViewOnce(m, viewOnceFlag);
1039
+ }
1030
1040
  }
1031
1041
  if (
1032
1042
  (hasOptionalProperty(message, "mentions") && message.mentions?.length) ||
1033
1043
  (hasOptionalProperty(message, "mentionAll") && message.mentionAll)
1034
1044
  ) {
1035
- const target = m.viewOnceMessage ? m.viewOnceMessage.message : m;
1036
- const messageType = Object.keys(target)[0];
1045
+ const target = unwrapViewOnce(m);
1046
+ const messageType = isViewOnceContent(m)
1047
+ ? getInnerContentType(target)
1048
+ : Object.keys(target)[0];
1037
1049
  const key = target[messageType];
1038
1050
  if (key && "contextInfo" in key) {
1039
1051
  key.contextInfo = key.contextInfo || {};
@@ -1061,9 +1073,15 @@ export const generateWAMessageContent = async (message, options) => {
1061
1073
  };
1062
1074
  }
1063
1075
  if (hasOptionalProperty(message, "contextInfo") && !!message.contextInfo) {
1064
- const messageType = Object.keys(m)[0];
1065
- const key = m[messageType];
1066
- if ("contextInfo" in key && !!key.contextInfo) {
1076
+ // لو الرسالة ملفوفة في viewOnce لازم نكتب الـ contextInfo على الرسالة الداخلية مش على الغلاف
1077
+ const ctxTarget = isViewOnceContent(m) ? unwrapViewOnce(m) : m;
1078
+ const messageType = isViewOnceContent(m)
1079
+ ? getInnerContentType(ctxTarget)
1080
+ : Object.keys(m)[0];
1081
+ const key = ctxTarget[messageType];
1082
+ if (!key) {
1083
+ // مفيش نوع رسالة نكتب عليه contextInfo
1084
+ } else if ("contextInfo" in key && !!key.contextInfo) {
1067
1085
  key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1068
1086
  } else if (key) {
1069
1087
  key.contextInfo = message.contextInfo;
@@ -85,12 +85,45 @@ const buildMetaNode = ({ interactionType, parentServerId, responseServerId, aiCo
85
85
  export const withNewsletterStatusAttribution = content => ({
86
86
  ...content,
87
87
  contextInfo: {
88
+ // matches the contextInfo of real channel statuses captured via getNewsletterStatuses
89
+ forwardingScore: 0,
88
90
  statusAttributions: [{ type: proto.StatusAttribution.Type.NEWSLETTER_STATUS }],
89
- featureEligibilities: { canBeReshared: true },
91
+ featureEligibilities: { cannotBeRanked: false, canBeReshared: true, canReceiveMultiReact: true },
90
92
  ...(content?.contextInfo || {})
91
93
  }
92
94
  })
93
95
 
96
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
97
+
98
+ // StatusSourceType: IMAGE 0, VIDEO 1, GIF 2, AUDIO 3, TEXT 4. PairedMediaType: NOT_PAIRED_MEDIA 0.
99
+ // Real statuses (captured from a live channel): text carries textArgb/backgroundArgb/font SYSTEM/previewType NONE
100
+ // and statusSourceType TEXT; media carries pairedMediaType NOT_PAIRED_MEDIA and no statusSourceType.
101
+ export const NATIVE_STATUS_TEXT_ARGB = 0xFFFFFFFF
102
+ export const NATIVE_STATUS_BACKGROUND_ARGB = 0xFF8B6990
103
+
104
+ export const applyNativeStatusShape = (message, { textArgb, backgroundArgb } = {}) => {
105
+ const content = normalizeMessageContent(message)
106
+ if (!content) return message
107
+
108
+ const text = content.extendedTextMessage
109
+ if (text) {
110
+ if (!hasOwn(text, 'textArgb')) text.textArgb = textArgb ?? NATIVE_STATUS_TEXT_ARGB
111
+ if (!hasOwn(text, 'backgroundArgb')) text.backgroundArgb = backgroundArgb ?? NATIVE_STATUS_BACKGROUND_ARGB
112
+ if (!hasOwn(text, 'font')) text.font = 0 // SYSTEM
113
+ text.previewType = 0 // NONE, even when a link preview was generated
114
+ text.contextInfo = text.contextInfo || {}
115
+ text.contextInfo.statusSourceType = 4 // TEXT
116
+ return message
117
+ }
118
+
119
+ const media = content.imageMessage || content.videoMessage || content.audioMessage
120
+ if (media) {
121
+ media.contextInfo = media.contextInfo || {}
122
+ media.contextInfo.pairedMediaType = 0 // NOT_PAIRED_MEDIA
123
+ }
124
+ return message
125
+ }
126
+
94
127
  const snapshotCallbackNode = (event, node) => ({
95
128
  event,
96
129
  tag: node?.tag,
@@ -376,6 +409,8 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
376
409
  interactionType: requestedInteractionType,
377
410
  aiContent,
378
411
  statusAttribution = true,
412
+ statusShape = 'native',
413
+ statusStyle,
379
414
  messageId: requestedMessageId,
380
415
  ackTimeoutMs,
381
416
  resolveServerId,
@@ -409,6 +444,8 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
409
444
  messageId: requestedMessageId || generateMessageIDV2(userJid)
410
445
  })
411
446
 
447
+ if (statusAttribution && statusShape !== 'legacy') applyNativeStatusShape(fullMsg.message, statusStyle)
448
+
412
449
  const normalized = normalizeMessageContent(fullMsg.message)
413
450
  const mediaType = getNewsletterStatusMediaType(fullMsg.message)
414
451
  if (!mediaType && (normalized?.documentMessage || normalized?.stickerMessage)) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Prototype VIP Baileys Helpers (0.1.5)
2
+ * Prototype VIP Baileys Helpers (0.1.6)
3
3
  *
4
4
  * طبقة helpers سهلة فوق المسارات الأصلية الموجودة في المكتبة
5
5
  * (sock.sendMessage → nativeFlow / album / code / event / product ...).
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { proto } from "../../WAProto/index.js";
10
10
  import { generateWAMessageFromContent } from "./messages.js";
11
+ import { wrapViewOnce } from "./view-once.js";
11
12
  // الكلاسات دي مصدَّرة أصلاً من message-builder.js عن طريق Utils/index.js
12
13
  import { Button, ButtonV2, Carousel, AIRich, Toolkit } from "./message-builder.js";
13
14
 
@@ -26,6 +27,36 @@ const assertSock = (sock, method = "sendMessage") => {
26
27
  }
27
28
  };
28
29
 
30
+ /** View-Once من content.viewOnce أو options.viewOnce (true | "v2" | "force" | {version,force}) */
31
+ const pickViewOnce = (content, options) => content?.viewOnce ?? options?.viewOnce;
32
+
33
+ const MIME_BY_EXT = {
34
+ pdf: "application/pdf",
35
+ zip: "application/zip",
36
+ rar: "application/vnd.rar",
37
+ "7z": "application/x-7z-compressed",
38
+ txt: "text/plain",
39
+ json: "application/json",
40
+ csv: "text/csv",
41
+ html: "text/html",
42
+ js: "text/javascript",
43
+ apk: "application/vnd.android.package-archive",
44
+ doc: "application/msword",
45
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
46
+ xls: "application/vnd.ms-excel",
47
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
48
+ ppt: "application/vnd.ms-powerpoint",
49
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
50
+ mp3: "audio/mpeg",
51
+ mp4: "video/mp4",
52
+ png: "image/png",
53
+ jpg: "image/jpeg",
54
+ jpeg: "image/jpeg",
55
+ webp: "image/webp",
56
+ };
57
+
58
+ const mimeFromName = (name = "") => MIME_BY_EXT[String(name).split("?")[0].split(".").pop()?.toLowerCase()] || "application/octet-stream";
59
+
29
60
  const defaultRowId = (label) => `#${String(label || "row").trim().replace(/\s+/g, "_")}`;
30
61
 
31
62
  /**
@@ -140,7 +171,7 @@ export async function sendList(sock, jid, content = {}, options = {}) {
140
171
  if (legacy) {
141
172
  const msg = generateWAMessageFromContent(
142
173
  jid,
143
- {
174
+ wrapViewOnce({
144
175
  listMessage: {
145
176
  title,
146
177
  description: text,
@@ -156,7 +187,7 @@ export async function sendList(sock, jid, content = {}, options = {}) {
156
187
  })),
157
188
  })),
158
189
  },
159
- },
190
+ }, pickViewOnce(content, options)),
160
191
  { userJid: sock.user?.id, quoted: options.quoted, messageId: options.messageId }
161
192
  );
162
193
  const additionalNodes = isPrivateJid(jid)
@@ -232,7 +263,8 @@ export async function sendAlbum(sock, jid, mediaItems = [], options = {}) {
232
263
  expectedImageCount: mediaItems.filter((i) => i.image).length,
233
264
  expectedVideoCount: mediaItems.filter((i) => i.video).length,
234
265
  });
235
- return sock.sendMessage(jid, { album }, options);
266
+ const { viewOnce, ...sendOptions } = options;
267
+ return sock.sendMessage(jid, { album, ...(viewOnce ? { viewOnce } : {}) }, sendOptions);
236
268
  }
237
269
 
238
270
  /* ───────────────────────────── carousel ───────────────────────────── */
@@ -255,6 +287,8 @@ export async function sendCarousel(sock, jid, content = {}, options = {}) {
255
287
 
256
288
  const carousel = new Carousel(sock).setBody(text);
257
289
  if (footer) carousel.setFooter(footer);
290
+ const viewOnce = pickViewOnce(content, options);
291
+ if (viewOnce) carousel.setViewOnce(viewOnce);
258
292
 
259
293
  for (const [i, card] of cards.entries()) {
260
294
  const media = card.image ?? card.video;
@@ -275,7 +309,8 @@ export async function sendCarousel(sock, jid, content = {}, options = {}) {
275
309
  carousel.addCard(await b.toCard());
276
310
  }
277
311
 
278
- await carousel.send(jid, { userJid: sock.user?.id, ...options });
312
+ const { viewOnce: _vo, ...sendOptions } = options;
313
+ return carousel.send(jid, { userJid: sock.user?.id, ...sendOptions });
279
314
  }
280
315
 
281
316
  /* ───────────────────────────── rich (AI) ───────────────────────────── */
@@ -328,10 +363,89 @@ export async function sendRichCode(sock, jid, { code, language = "javascript", t
328
363
 
329
364
  /* ───────────────────────────── misc senders ───────────────────────────── */
330
365
 
366
+ /**
367
+ * View-Once لأي نوع رسالة (0.1.6) — مش صورة/فيديو/صوت بس:
368
+ * نص، ملف، أزرار، قائمة، كاروسيل، ألبوم، منتج، لوكيشن، جهة اتصال، Rich ...
369
+ *
370
+ * await sendViewOnce(sock, jid, { image: { url }, caption: "x" })
371
+ * await sendViewOnce(sock, jid, { text: "x", buttons: [...] }) // → sendButtons
372
+ * await sendViewOnce(sock, jid, { text: "x", sections: [...] }) // → sendList
373
+ * await sendViewOnce(sock, jid, { text: "x", cards: [...] }) // → sendCarousel
374
+ * await sendViewOnce(sock, jid, [{ image }, { video }]) // → sendAlbum
375
+ * options.viewOnce = true | "v2" | "force" (الافتراضي true)
376
+ */
331
377
  export async function sendViewOnce(sock, jid, content, options = {}) {
332
378
  assertSock(sock);
379
+ const flag = (typeof content === "object" && !Array.isArray(content) && content?.viewOnce) || options.viewOnce || true;
380
+ const opts = { ...options, viewOnce: flag };
381
+
382
+ if (Array.isArray(content)) return sendAlbum(sock, jid, content, opts);
383
+
384
+ const payload = typeof content === "string" ? { text: content } : { ...content };
385
+ delete payload.viewOnce;
386
+
387
+ if (Array.isArray(payload.cards)) return sendCarousel(sock, jid, payload, opts);
388
+ if (Array.isArray(payload.sections)) return sendList(sock, jid, payload, opts);
389
+ if (Array.isArray(payload.buttons) && payload.buttons.length) return sendButtons(sock, jid, payload, opts);
390
+ return sock.sendMessage(jid, { ...payload, viewOnce: flag }, options);
391
+ }
392
+
393
+ /* ───────────────────────────── extras (0.1.6) ───────────────────────────── */
394
+
395
+ /**
396
+ * ملف/مستند. source: { url } | Buffer | رابط نصي | { document, fileName, mimetype, caption }
397
+ * await sendDocument(sock, jid, { url: "https://x/a.pdf", fileName: "a.pdf", caption: "تفضل" }, { viewOnce: true })
398
+ */
399
+ export async function sendDocument(sock, jid, content = {}, options = {}) {
400
+ assertSock(sock);
401
+ const src = typeof content === "string" ? { url: content } : Buffer.isBuffer(content) ? { document: content } : { ...content };
402
+ const doc = src.document ?? src.file ?? src.buffer ?? (src.url ? { url: src.url } : undefined);
403
+ if (!doc) throw new Error("sendDocument requires document | file | buffer | url");
404
+ const document = typeof doc === "string" ? { url: doc } : doc;
405
+ const urlLike = document?.url || (typeof src.url === "string" ? src.url : "");
406
+ const fileName = src.fileName || src.filename || src.name || (urlLike ? String(urlLike).split("?")[0].split("/").pop() : "file");
407
+ const mimetype = src.mimetype || src.mime || mimeFromName(fileName);
408
+ const { caption, text } = src;
409
+ return sock.sendMessage(
410
+ jid,
411
+ { document, fileName, mimetype, ...(caption || text ? { caption: caption ?? text } : {}) },
412
+ options
413
+ );
414
+ }
415
+
416
+ /** تعديل رسالة بعتّها قبل كده (نص/كابشن) */
417
+ export async function sendEdit(sock, jid, key, content, options = {}) {
418
+ assertSock(sock);
419
+ if (!key) throw new Error("sendEdit requires the original message key");
333
420
  const payload = typeof content === "string" ? { text: content } : { ...content };
334
- return sock.sendMessage(jid, { ...payload, viewOnce: true }, options);
421
+ return sock.sendMessage(jid, { ...payload, edit: key }, options);
422
+ }
423
+
424
+ /** حذف رسالة للكل */
425
+ export async function sendDelete(sock, jid, key) {
426
+ assertSock(sock);
427
+ if (!key) throw new Error("sendDelete requires the message key");
428
+ return sock.sendMessage(jid, { delete: key });
429
+ }
430
+
431
+ /**
432
+ * يبعت بعد ما يعمل "بيكتب..." (أو "بيسجل..." لو recording) لمدة typingMs
433
+ * await sendWithTyping(sock, jid, { text: "أهلا" }, { typingMs: 1200 })
434
+ */
435
+ export async function sendWithTyping(sock, jid, content, options = {}) {
436
+ assertSock(sock);
437
+ const { typingMs = 1000, presence = "composing", ...sendOptions } = options;
438
+ const canPresence = typeof sock.sendPresenceUpdate === "function";
439
+ try {
440
+ if (canPresence) {
441
+ await sock.sendPresenceUpdate(presence, jid);
442
+ await new Promise((r) => setTimeout(r, Math.max(0, Number(typingMs) || 0)));
443
+ }
444
+ const payload = typeof content === "string" ? { text: content } : content;
445
+ return await sock.sendMessage(jid, payload, sendOptions);
446
+ } finally {
447
+ if (canPresence) await sock.sendPresenceUpdate("paused", jid).catch(() => {});
448
+ }
335
449
  }
336
450
 
337
451
  export async function sendReaction(sock, jid, key, emoji = "👍") {
@@ -553,6 +667,10 @@ export function attachPrototypeHelpers(sock) {
553
667
  sock.sendQuickButtons = (jid, text, labels, opts) => sendQuickButtons(sock, jid, text, labels, opts);
554
668
  sock.sendCarousel = (jid, content, opts) => sendCarousel(sock, jid, content, opts);
555
669
  sock.sendViewOnce = (jid, content, opts) => sendViewOnce(sock, jid, content, opts);
670
+ sock.sendDocument = (jid, content, opts) => sendDocument(sock, jid, content, opts);
671
+ sock.sendEdit = (jid, key, content, opts) => sendEdit(sock, jid, key, content, opts);
672
+ sock.sendDelete = (jid, key) => sendDelete(sock, jid, key);
673
+ sock.sendWithTyping = (jid, content, opts) => sendWithTyping(sock, jid, content, opts);
556
674
  sock.sendReaction = (jid, key, emoji) => sendReaction(sock, jid, key, emoji);
557
675
  sock.sendPoll = (jid, name, values, opts) => sendPoll(sock, jid, name, values, opts);
558
676
  sock.sendMentionAll = (jid, text, opts) => sendMentionAll(sock, jid, text, opts);
@@ -592,6 +710,10 @@ export default {
592
710
  sendQuickButtons,
593
711
  sendCarousel,
594
712
  sendViewOnce,
713
+ sendDocument,
714
+ sendEdit,
715
+ sendDelete,
716
+ sendWithTyping,
595
717
  sendReaction,
596
718
  sendPoll,
597
719
  sendMentionAll,
@@ -0,0 +1,160 @@
1
+ /**
2
+ * View-Once موحَّد (0.1.6)
3
+ *
4
+ * نقطة واحدة بتلفّ أي محتوى رسالة (نص / صورة / فيديو / صوت / ملف / أزرار / قوائم /
5
+ * كاروسيل / منتج / لوكيشن / جهة اتصال / Rich ...) جوه viewOnceMessage.
6
+ * الدوال دي مفيهاش أي dependency خارجي عشان تتستعمل من messages.js و
7
+ * messages-send.js و message-builder.js و prototype-helpers.js من غير circular imports.
8
+ *
9
+ * طرق التفعيل (كلها بتدخل على نفس الدالة):
10
+ * sock.sendMessage(jid, { ...content, viewOnce: true })
11
+ * sock.sendMessage(jid, content, { viewOnce: true })
12
+ * sock.sendButtons / sendList / sendCarousel / sendAlbum / ... (jid, content, { viewOnce: true })
13
+ * new Carousel(sock).setViewOnce() / new Button(sock).setViewOnce()
14
+ *
15
+ * قيم الـ flag:
16
+ * true | "v1" → viewOnceMessage (الافتراضي — نفس السلوك القديم)
17
+ * "v2" | 2 → viewOnceMessageV2
18
+ * "force" | "v2-force" → نفس اللي فوق، بس بيلف حتى الأنواع اللي مش مدعومة رسمياً (استيكر / استطلاع / ألبوم)
19
+ * { version: 1|2, force: boolean }
20
+ */
21
+
22
+ export const VIEW_ONCE_KEYS = ["viewOnceMessage", "viewOnceMessageV2", "viewOnceMessageV2Extension"];
23
+
24
+ /** رسائل تحكّم/بروتوكول: مينفعش تتلف في viewOnce أبداً (حتى مع force) */
25
+ const NEVER_WRAP = new Set([
26
+ "protocolMessage",
27
+ "reactionMessage",
28
+ "encReactionMessage",
29
+ "pollUpdateMessage",
30
+ "encEventResponseMessage",
31
+ "keepInChatMessage",
32
+ "pinInChatMessage",
33
+ "buttonsResponseMessage",
34
+ "listResponseMessage",
35
+ "interactiveResponseMessage",
36
+ "templateButtonReplyMessage",
37
+ "senderKeyDistributionMessage",
38
+ "editedMessage",
39
+ "ephemeralMessage",
40
+ "groupStatusMessage",
41
+ "groupStatusMessageV2",
42
+ "associatedChildMessage",
43
+ "documentWithCaptionMessage",
44
+ ]);
45
+
46
+ /** أنواع واتساب نفسه مبيعرضهاش كـ view-once → بتتخطى إلا مع force */
47
+ const UNSUPPORTED_UNLESS_FORCED = new Set([
48
+ "albumMessage",
49
+ "stickerMessage",
50
+ "stickerPackMessage",
51
+ "pollCreationMessage",
52
+ "pollCreationMessageV2",
53
+ "pollCreationMessageV3",
54
+ "pollCreationMessageV5",
55
+ "pollCreationMessageV6",
56
+ "requestPhoneNumberMessage",
57
+ ]);
58
+
59
+ /** أنواع تفاعلية: واتساب بيطلب messageContextInfo جوه الـ viewOnce عشان تتعرض */
60
+ const INTERACTIVE_TYPES = new Set([
61
+ "interactiveMessage",
62
+ "buttonsMessage",
63
+ "listMessage",
64
+ "templateMessage",
65
+ "productMessage",
66
+ "eventMessage",
67
+ "richResponseMessage",
68
+ ]);
69
+
70
+ /** ميديا بيحطلها واتساب نفسه viewOnce=true جوه الرسالة كمان (مش الغلاف بس) */
71
+ const MEDIA_TYPES = new Set(["imageMessage", "videoMessage", "audioMessage"]);
72
+
73
+ /** نفس منطق getContentType (متكرر هنا عشان مفيش import دائري) */
74
+ export function getInnerContentType(content) {
75
+ if (!content || typeof content !== "object") return undefined;
76
+ return Object.keys(content).find(
77
+ (k) => (k === "conversation" || k.includes("Message")) && k !== "senderKeyDistributionMessage" && content[k] != null
78
+ );
79
+ }
80
+
81
+ /** يحوّل أي صيغة flag لـ { key, force } أو null لو مفيش viewOnce */
82
+ export function parseViewOnce(flag) {
83
+ if (!flag) return null;
84
+ if (flag === true || flag === "v1" || flag === 1) return { key: "viewOnceMessage", force: false };
85
+ if (flag === "v2" || flag === 2) return { key: "viewOnceMessageV2", force: false };
86
+ if (flag === "force") return { key: "viewOnceMessage", force: true };
87
+ if (flag === "v2-force") return { key: "viewOnceMessageV2", force: true };
88
+ if (typeof flag === "object") {
89
+ return {
90
+ key: Number(flag.version) === 2 || flag.version === "v2" ? "viewOnceMessageV2" : "viewOnceMessage",
91
+ force: !!flag.force,
92
+ };
93
+ }
94
+ return { key: "viewOnceMessage", force: false }; // أي قيمة truthy تانية
95
+ }
96
+
97
+ export const isViewOnceContent = (content) => !!content && VIEW_ONCE_KEYS.some((k) => content[k]);
98
+
99
+ /** يرجّع المحتوى الداخلي لو ملفوف في viewOnce، وإلا نفس المحتوى */
100
+ export function unwrapViewOnce(content) {
101
+ if (!content) return content;
102
+ for (const k of VIEW_ONCE_KEYS) {
103
+ if (content[k]?.message) return content[k].message;
104
+ }
105
+ return content;
106
+ }
107
+
108
+ /**
109
+ * يلف محتوى الرسالة (شكل proto: { xxxMessage: {...} }) في viewOnce.
110
+ * - idempotent: لو الرسالة ملفوفة أصلاً بيرجّعها زي ما هي.
111
+ * - بيتخطى رسائل التحكم (react / delete / edit / poll vote ...) دايماً.
112
+ * - بيتخطى الاستيكر/الاستطلاع/الألبوم إلا مع "force".
113
+ * - بيضيف messageContextInfo الداخلي للأنواع التفاعلية (أزرار/قوائم/كاروسيل...).
114
+ */
115
+ export function wrapViewOnce(content, flag = true) {
116
+ const opt = parseViewOnce(flag);
117
+ if (!opt || !content || typeof content !== "object") return content;
118
+ if (isViewOnceContent(content)) return content;
119
+
120
+ const type = getInnerContentType(content);
121
+ if (!type) return content;
122
+ if (NEVER_WRAP.has(type)) return content;
123
+ if (UNSUPPORTED_UNLESS_FORCED.has(type) && !opt.force) return content;
124
+
125
+ const inner = { ...content };
126
+ if (MEDIA_TYPES.has(type) && inner[type] && typeof inner[type] === "object") {
127
+ inner[type].viewOnce = true;
128
+ }
129
+ if (INTERACTIVE_TYPES.has(type) && !inner.messageContextInfo) {
130
+ inner.messageContextInfo = { deviceListMetadata: {}, deviceListMetadataVersion: 2 };
131
+ }
132
+ return { [opt.key]: { message: inner } };
133
+ }
134
+
135
+ /** هل النوع ده بيتلف فعلاً مع الـ flag ده؟ (للتشخيص / الاختبارات) */
136
+ export function canWrapViewOnce(content, flag = true) {
137
+ const opt = parseViewOnce(flag);
138
+ if (!opt) return false;
139
+ const type = getInnerContentType(unwrapViewOnce(content));
140
+ if (!type || NEVER_WRAP.has(type)) return false;
141
+ return opt.force || !UNSUPPORTED_UNLESS_FORCED.has(type);
142
+ }
143
+
144
+ /** يجيب نوع المحتوى الحقيقي + المحتوى نفسه حتى لو ملفوف في viewOnce */
145
+ export function getViewOnceTarget(content) {
146
+ const inner = unwrapViewOnce(content);
147
+ const type = getInnerContentType(inner) ?? Object.keys(inner || {})[0];
148
+ return { inner, type, node: type ? inner?.[type] : undefined };
149
+ }
150
+
151
+ export default {
152
+ VIEW_ONCE_KEYS,
153
+ parseViewOnce,
154
+ wrapViewOnce,
155
+ unwrapViewOnce,
156
+ isViewOnceContent,
157
+ canWrapViewOnce,
158
+ getInnerContentType,
159
+ getViewOnceTarget,
160
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prototypevip/baileys",
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)",
3
+ "version": "0.1.6",
4
+ "description": "Baileys fork (@prototypevip/baileys) — clean + Channel/Group/Personal Status + Interactive Buttons/Lists/Album/Carousel + Rich code responses + Call Invites + Universal View-Once (0.1.6)",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -24,24 +24,26 @@
24
24
  "HELPERS.md"
25
25
  ],
26
26
  "keywords": [
27
+ "album",
27
28
  "automation",
28
29
  "baileys",
29
30
  "baileys-fork",
30
31
  "baileys-mod",
32
+ "buttons",
33
+ "call-invite",
34
+ "interactive-messages",
31
35
  "js-whatsapp",
32
36
  "multi-device",
37
+ "native-flow",
38
+ "rich-response",
39
+ "view-once",
40
+ "viewonce",
33
41
  "whatsapp",
34
42
  "whatsapp-api",
35
43
  "whatsapp-bot",
36
44
  "whatsapp-chat",
37
45
  "whatsapp-group",
38
- "whatsapp-web",
39
- "interactive-messages",
40
- "buttons",
41
- "album",
42
- "native-flow",
43
- "call-invite",
44
- "rich-response"
46
+ "whatsapp-web"
45
47
  ],
46
48
  "homepage": "https://github.com/GSK-F/baileys",
47
49
  "author": "The Prototype",