@prototypevip/baileys 0.1.4 → 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 ADDED
@@ -0,0 +1,149 @@
1
+ # @prototypevip/baileys — Helpers (v0.1.6)
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
+ ```
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";
@@ -34,3 +35,4 @@ export * from "./integrity-guard.js";
34
35
  export * from "./newsletter-status.js";
35
36
  export * from "./status-helpers.js";
36
37
  export * from "./modern-messages.js";
38
+ export * from "./prototype-helpers.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
 
@@ -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,
@@ -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",
@@ -402,6 +403,7 @@ export const prepareWAMessageMedia = async (message, options) => {
402
403
  fileEncSha256B64: fileSha256B64,
403
404
  mediaType: mediaType,
404
405
  timeoutMs: options.mediaUploadTimeoutMs,
406
+ newsletter: true,
405
407
  });
406
408
  await fs.unlink(filePath);
407
409
  const obj = WAProto.Message.fromObject({
@@ -704,7 +706,12 @@ export const generateWAMessageContent = async (message, options) => {
704
706
  ) {
705
707
  // مضافة من مكتبة itsliaaa: رسالة Rich Response (Meta AI) — جداول/كود ملوّن
706
708
  m = prepareRichResponseMessage(message);
707
- } 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
+ ) {
708
715
  const extContent = { text: message.text };
709
716
  let urlInfo = message.linkPreview;
710
717
  if (typeof urlInfo === "undefined") {
@@ -1020,19 +1027,25 @@ export const generateWAMessageContent = async (message, options) => {
1020
1027
  } else {
1021
1028
  m = await prepareWAMessageMedia(message, options);
1022
1029
  }
1023
- if (
1024
- hasOptionalProperty(message, "viewOnce") &&
1025
- !!message.viewOnce &&
1026
- !m.viewOnceMessage
1027
- ) {
1028
- 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
+ }
1029
1040
  }
1030
1041
  if (
1031
1042
  (hasOptionalProperty(message, "mentions") && message.mentions?.length) ||
1032
1043
  (hasOptionalProperty(message, "mentionAll") && message.mentionAll)
1033
1044
  ) {
1034
- const target = m.viewOnceMessage ? m.viewOnceMessage.message : m;
1035
- const messageType = Object.keys(target)[0];
1045
+ const target = unwrapViewOnce(m);
1046
+ const messageType = isViewOnceContent(m)
1047
+ ? getInnerContentType(target)
1048
+ : Object.keys(target)[0];
1036
1049
  const key = target[messageType];
1037
1050
  if (key && "contextInfo" in key) {
1038
1051
  key.contextInfo = key.contextInfo || {};
@@ -1060,9 +1073,15 @@ export const generateWAMessageContent = async (message, options) => {
1060
1073
  };
1061
1074
  }
1062
1075
  if (hasOptionalProperty(message, "contextInfo") && !!message.contextInfo) {
1063
- const messageType = Object.keys(m)[0];
1064
- const key = m[messageType];
1065
- 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) {
1066
1085
  key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1067
1086
  } else if (key) {
1068
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,11 +409,14 @@ 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,
382
417
  serverIdTimeoutMs,
383
418
  transport,
419
+ wrapAdminProfile,
384
420
  ...messageOptions
385
421
  } = options
386
422
 
@@ -408,6 +444,8 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
408
444
  messageId: requestedMessageId || generateMessageIDV2(userJid)
409
445
  })
410
446
 
447
+ if (statusAttribution && statusShape !== 'legacy') applyNativeStatusShape(fullMsg.message, statusStyle)
448
+
411
449
  const normalized = normalizeMessageContent(fullMsg.message)
412
450
  const mediaType = getNewsletterStatusMediaType(fullMsg.message)
413
451
  if (!mediaType && (normalized?.documentMessage || normalized?.stickerMessage)) {
@@ -424,7 +462,7 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
424
462
 
425
463
  const node = buildNewsletterStatusNode({
426
464
  jid,
427
- message: fullMsg.message,
465
+ message: wrapAdminProfile ? buildNewsletterAdminProfileStatusMessage(fullMsg.message) : fullMsg.message,
428
466
  messageId: fullMsg.key.id,
429
467
  mediaType,
430
468
  mediaHandle: requestedMediaHandle ?? mediaId ?? uploadedMediaHandle,