@aseppxyzz12/baileys 1.1.8

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.
Files changed (185) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +2579 -0
  3. package/WAProto/AICommon/AICommon.js +19396 -0
  4. package/WAProto/AICommon/AICommon.proto +820 -0
  5. package/WAProto/Adv/Adv.js +1137 -0
  6. package/WAProto/Adv/Adv.proto +42 -0
  7. package/WAProto/BotMetadata/BotMetadata.js +8975 -0
  8. package/WAProto/BotMetadata/BotMetadata.proto +484 -0
  9. package/WAProto/Cert/Cert.js +893 -0
  10. package/WAProto/Cert/Cert.proto +30 -0
  11. package/WAProto/ChatLockSettings/ChatLockSettings.js +673 -0
  12. package/WAProto/ChatLockSettings/ChatLockSettings.proto +9 -0
  13. package/WAProto/CompanionReg/CompanionReg.js +2457 -0
  14. package/WAProto/CompanionReg/CompanionReg.proto +103 -0
  15. package/WAProto/DeviceCapabilities/DeviceCapabilities.js +846 -0
  16. package/WAProto/DeviceCapabilities/DeviceCapabilities.proto +38 -0
  17. package/WAProto/E2E/E2E.js +78728 -0
  18. package/WAProto/E2E/E2E.proto +1970 -0
  19. package/WAProto/Ephemeral/Ephemeral.js +143 -0
  20. package/WAProto/Ephemeral/Ephemeral.proto +7 -0
  21. package/WAProto/HistorySync/HistorySync.js +95931 -0
  22. package/WAProto/HistorySync/HistorySync.proto +229 -0
  23. package/WAProto/LidMigrationSyncPayload/LidMigrationSyncPayload.js +414 -0
  24. package/WAProto/LidMigrationSyncPayload/LidMigrationSyncPayload.proto +13 -0
  25. package/WAProto/MdStorageChatRowOpaqueData/MdStorageChatRowOpaqueData.js +980 -0
  26. package/WAProto/MdStorageChatRowOpaqueData/MdStorageChatRowOpaqueData.proto +38 -0
  27. package/WAProto/MdStorageMsgRowOpaqueData/MdStorageMsgRowOpaqueData.js +81185 -0
  28. package/WAProto/MdStorageMsgRowOpaqueData/MdStorageMsgRowOpaqueData.proto +88 -0
  29. package/WAProto/MmsRetry/MmsRetry.js +310 -0
  30. package/WAProto/MmsRetry/MmsRetry.proto +19 -0
  31. package/WAProto/Protocol/Protocol.js +399 -0
  32. package/WAProto/Protocol/Protocol.proto +22 -0
  33. package/WAProto/Reporting/Reporting.js +535 -0
  34. package/WAProto/Reporting/Reporting.proto +22 -0
  35. package/WAProto/ServerSync/ServerSync.js +1830 -0
  36. package/WAProto/ServerSync/ServerSync.proto +70 -0
  37. package/WAProto/SignalLocalStorageProtocol/SignalLocalStorageProtocol.js +3267 -0
  38. package/WAProto/SignalLocalStorageProtocol/SignalLocalStorageProtocol.proto +95 -0
  39. package/WAProto/SignalWhisperTextProtocol/SignalWhisperTextProtocol.js +1140 -0
  40. package/WAProto/SignalWhisperTextProtocol/SignalWhisperTextProtocol.proto +44 -0
  41. package/WAProto/StatusAttributions/StatusAttributions.js +1604 -0
  42. package/WAProto/StatusAttributions/StatusAttributions.proto +89 -0
  43. package/WAProto/SyncAction/SyncAction.js +17424 -0
  44. package/WAProto/SyncAction/SyncAction.proto +663 -0
  45. package/WAProto/UserPassword/UserPassword.js +544 -0
  46. package/WAProto/UserPassword/UserPassword.proto +28 -0
  47. package/WAProto/VnameCert/VnameCert.js +1466 -0
  48. package/WAProto/VnameCert/VnameCert.proto +65 -0
  49. package/WAProto/Wa6/Wa6.js +4601 -0
  50. package/WAProto/Wa6/Wa6.proto +241 -0
  51. package/WAProto/Web/Web.js +90819 -0
  52. package/WAProto/Web/Web.proto +605 -0
  53. package/WAProto/index.js +30 -0
  54. package/engine-requirements.js +9 -0
  55. package/lib/Defaults/baileys-version.json +3 -0
  56. package/lib/Defaults/connection.js +39 -0
  57. package/lib/Defaults/constants.js +50 -0
  58. package/lib/Defaults/history.js +13 -0
  59. package/lib/Defaults/index.js +36 -0
  60. package/lib/Defaults/media.js +43 -0
  61. package/lib/Defaults/phonenumber-mcc.json +223 -0
  62. package/lib/Defaults/prefix.js +12 -0
  63. package/lib/Function/Download/tiktok.js +19 -0
  64. package/lib/Function/Tools/bypass.js +19 -0
  65. package/lib/Function/index.js +13 -0
  66. package/lib/Signal/Group/ciphertext-message.js +14 -0
  67. package/lib/Signal/Group/group-session-builder.js +46 -0
  68. package/lib/Signal/Group/group_cipher.js +104 -0
  69. package/lib/Signal/Group/index.js +42 -0
  70. package/lib/Signal/Group/keyhelper.js +21 -0
  71. package/lib/Signal/Group/queue-job.js +57 -0
  72. package/lib/Signal/Group/sender-chain-key.js +34 -0
  73. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  74. package/lib/Signal/Group/sender-key-message.js +78 -0
  75. package/lib/Signal/Group/sender-key-name.js +49 -0
  76. package/lib/Signal/Group/sender-key-record.js +45 -0
  77. package/lib/Signal/Group/sender-key-state.js +100 -0
  78. package/lib/Signal/Group/sender-message-key.js +28 -0
  79. package/lib/Signal/libsignal.js +364 -0
  80. package/lib/Signal/lid-mapping.js +164 -0
  81. package/lib/Socket/Client/abstract-socket-client.js +13 -0
  82. package/lib/Socket/Client/index.js +31 -0
  83. package/lib/Socket/Client/mobile-socket-client.js +65 -0
  84. package/lib/Socket/Client/types.js +12 -0
  85. package/lib/Socket/Client/web-socket-client.js +62 -0
  86. package/lib/Socket/Client/websocket.js +67 -0
  87. package/lib/Socket/business.js +291 -0
  88. package/lib/Socket/chats.js +874 -0
  89. package/lib/Socket/community.js +454 -0
  90. package/lib/Socket/dugong.js +658 -0
  91. package/lib/Socket/groups.js +357 -0
  92. package/lib/Socket/index.js +13 -0
  93. package/lib/Socket/messages-recv.js +1509 -0
  94. package/lib/Socket/messages-send.js +1505 -0
  95. package/lib/Socket/mex.js +54 -0
  96. package/lib/Socket/newsletter.js +171 -0
  97. package/lib/Socket/registration.js +167 -0
  98. package/lib/Socket/rich-content.js +279 -0
  99. package/lib/Socket/socket.js +905 -0
  100. package/lib/Socket/username.js +157 -0
  101. package/lib/Socket/usync.js +70 -0
  102. package/lib/Store/index.js +37 -0
  103. package/lib/Store/keyed-db.js +114 -0
  104. package/lib/Store/make-cache-manager-store.js +77 -0
  105. package/lib/Store/make-in-memory-store.js +423 -0
  106. package/lib/Store/make-ordered-dictionary.js +78 -0
  107. package/lib/Store/object-repository.js +26 -0
  108. package/lib/Types/Auth.js +2 -0
  109. package/lib/Types/Bussines.js +2 -0
  110. package/lib/Types/Call.js +2 -0
  111. package/lib/Types/Chat.js +10 -0
  112. package/lib/Types/Contact.js +2 -0
  113. package/lib/Types/Events.js +2 -0
  114. package/lib/Types/GroupMetadata.js +2 -0
  115. package/lib/Types/Label.js +25 -0
  116. package/lib/Types/LabelAssociation.js +4 -0
  117. package/lib/Types/Message.js +11 -0
  118. package/lib/Types/Mex.js +40 -0
  119. package/lib/Types/MexUpdates.js +15 -0
  120. package/lib/Types/Newsletter.js +32 -0
  121. package/lib/Types/Product.js +2 -0
  122. package/lib/Types/Signal.js +2 -0
  123. package/lib/Types/Socket.js +2 -0
  124. package/lib/Types/State.js +9 -0
  125. package/lib/Types/USync.js +2 -0
  126. package/lib/Types/index.js +60 -0
  127. package/lib/Utils/auth-utils.js +256 -0
  128. package/lib/Utils/baileys-event-stream.js +63 -0
  129. package/lib/Utils/bot-toolkit.js +455 -0
  130. package/lib/Utils/browser-utils.js +29 -0
  131. package/lib/Utils/business.js +233 -0
  132. package/lib/Utils/chat-utils.js +904 -0
  133. package/lib/Utils/companion-reg-client-utils.js +41 -0
  134. package/lib/Utils/crypto.js +144 -0
  135. package/lib/Utils/decode-wa-message.js +305 -0
  136. package/lib/Utils/event-buffer.js +555 -0
  137. package/lib/Utils/generics.js +411 -0
  138. package/lib/Utils/history.js +100 -0
  139. package/lib/Utils/identity-change-handler.js +53 -0
  140. package/lib/Utils/index.js +57 -0
  141. package/lib/Utils/link-preview.js +76 -0
  142. package/lib/Utils/logger.js +4 -0
  143. package/lib/Utils/lt-hash.js +45 -0
  144. package/lib/Utils/make-mutex.js +33 -0
  145. package/lib/Utils/message-composer.js +289 -0
  146. package/lib/Utils/message-retry-manager.js +134 -0
  147. package/lib/Utils/messages-media.js +806 -0
  148. package/lib/Utils/messages.js +1908 -0
  149. package/lib/Utils/noise-handler.js +157 -0
  150. package/lib/Utils/offline-node-processor.js +42 -0
  151. package/lib/Utils/pre-key-manager.js +86 -0
  152. package/lib/Utils/process-message.js +814 -0
  153. package/lib/Utils/reporting-utils.js +264 -0
  154. package/lib/Utils/signal.js +183 -0
  155. package/lib/Utils/stanza-ack.js +36 -0
  156. package/lib/Utils/sync-action-utils.js +52 -0
  157. package/lib/Utils/tc-token-utils.js +167 -0
  158. package/lib/Utils/use-multi-file-auth-state.js +104 -0
  159. package/lib/Utils/validate-connection.js +229 -0
  160. package/lib/Utils/vialeys-event-stream.js +41 -0
  161. package/lib/WABinary/constants.js +1308 -0
  162. package/lib/WABinary/decode.js +233 -0
  163. package/lib/WABinary/encode.js +212 -0
  164. package/lib/WABinary/generic-utils.js +124 -0
  165. package/lib/WABinary/index.js +36 -0
  166. package/lib/WABinary/jid-utils.js +101 -0
  167. package/lib/WABinary/types.js +2 -0
  168. package/lib/WAM/BinaryInfo.js +12 -0
  169. package/lib/WAM/constants.js +20491 -0
  170. package/lib/WAM/encode.js +148 -0
  171. package/lib/WAM/index.js +34 -0
  172. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +62 -0
  173. package/lib/WAUSync/Protocols/USyncContactProtocol.js +38 -0
  174. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +50 -0
  175. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +25 -0
  176. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +24 -0
  177. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +32 -0
  178. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  179. package/lib/WAUSync/Protocols/index.js +38 -0
  180. package/lib/WAUSync/USyncQuery.js +97 -0
  181. package/lib/WAUSync/USyncUser.js +33 -0
  182. package/lib/WAUSync/index.js +34 -0
  183. package/lib/index.js +113 -0
  184. package/lib/messages-send.js +1496 -0
  185. package/package.json +100 -0
@@ -0,0 +1,1908 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const { Boom: Boom } = require("@hapi/boom");
4
+ const { randomBytes: randomBytes } = require("crypto");
5
+ const { promises: promises } = require("fs");
6
+ const sharp = require("sharp");
7
+ const { proto: proto } = require("../../WAProto");
8
+ const {
9
+ URL_REGEX: URL_REGEX,
10
+ WA_DEFAULT_EPHEMERAL: WA_DEFAULT_EPHEMERAL,
11
+ } = require("../Defaults/constants");
12
+ const { MEDIA_KEYS: MEDIA_KEYS } = require("../Defaults/media");
13
+ const { WAProto: WAProto, WAMessageStatus: WAMessageStatus } = require("../Types");
14
+ const {
15
+ isJidGroup: isJidGroup,
16
+ isJidNewsletter: isJidNewsletter,
17
+ isJidStatusBroadcast: isJidStatusBroadcast,
18
+ jidNormalizedUser: jidNormalizedUser,
19
+ } = require("../WABinary");
20
+ const { sha256: sha256 } = require("./crypto");
21
+ const {
22
+ generateMessageID: generateMessageID,
23
+ getKeyAuthor: getKeyAuthor,
24
+ unixTimestampSeconds: unixTimestampSeconds,
25
+ } = require("./generics");
26
+ const {
27
+ downloadContentFromMessage: downloadContentFromMessage,
28
+ encryptedStream: encryptedStream,
29
+ generateThumbnail: generateThumbnail,
30
+ getAudioDuration: getAudioDuration,
31
+ getAudioWaveform: getAudioWaveform,
32
+ getRawMediaUploadData: getRawMediaUploadData,
33
+ getStream: getStream,
34
+ toBuffer: toBuffer,
35
+ } = require("./messages-media");
36
+ const MIMETYPE_MAP = {
37
+ image: "image/jpeg",
38
+ video: "video/mp4",
39
+ document: "application/pdf",
40
+ audio: "audio/ogg codecs=opus",
41
+ sticker: "image/webp",
42
+ "product-catalog-image": "image/jpeg",
43
+ };
44
+ async function processLocationThumbnail(input, options = {}) {
45
+ const {
46
+ maxWidth = 300,
47
+ maxHeight = 300,
48
+ quality = 80
49
+ } = options
50
+
51
+ let buffer
52
+
53
+ if (
54
+ typeof input === "string" &&
55
+ (input.startsWith("http://") || input.startsWith("https://"))
56
+ ) {
57
+
58
+ const stream = await getStream(input)
59
+ buffer = await toBuffer(stream)
60
+
61
+ } else if (typeof input === "string") {
62
+
63
+ buffer = await promises.readFile(input)
64
+
65
+ } else if (Buffer.isBuffer(input)) {
66
+
67
+ buffer = input
68
+
69
+ } else {
70
+
71
+ throw new Error("thumbnail must be URL, path or Buffer")
72
+
73
+ }
74
+
75
+ return await sharp(buffer)
76
+ .resize(maxWidth, maxHeight, {
77
+ fit: "inside",
78
+ withoutEnlargement: true
79
+ })
80
+ .jpeg({ quality })
81
+ .toBuffer()
82
+ }
83
+ const MessageTypeProto = {
84
+ image: WAProto.Message.ImageMessage,
85
+ video: WAProto.Message.VideoMessage,
86
+ audio: WAProto.Message.AudioMessage,
87
+ sticker: WAProto.Message.StickerMessage,
88
+ document: WAProto.Message.DocumentMessage,
89
+ };
90
+ const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
91
+ const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
92
+ const url = extractUrlFromText(text);
93
+ if (!!getUrlInfo && url) {
94
+ try {
95
+ const urlInfo = await getUrlInfo(url);
96
+ return urlInfo;
97
+ } catch (error) {
98
+ logger?.warn({ trace: error.stack }, "url generation failed");
99
+ }
100
+ }
101
+ };
102
+ const assertColor = async (color) => {
103
+ let assertedColor;
104
+ if (typeof color === "number") {
105
+ assertedColor = color > 0 ? color : 4294967295 + Number(color) + 1;
106
+ } else {
107
+ let hex = color.trim().replace("#", "");
108
+ if (hex.length <= 6) {
109
+ hex = "FF" + hex.padStart(6, "0");
110
+ }
111
+ assertedColor = parseInt(hex, 16);
112
+ return assertedColor;
113
+ }
114
+ };
115
+ const prepareWAMessageMedia = async (message, options) => {
116
+ const logger = options.logger;
117
+ let mediaType;
118
+ for (const key of MEDIA_KEYS) {
119
+ if (key in message) {
120
+ mediaType = key;
121
+ }
122
+ }
123
+ if (!mediaType) {
124
+ throw new Boom("Invalid media type", { statusCode: 400 });
125
+ }
126
+ const uploadData = { ...message, media: message[mediaType] };
127
+ delete uploadData[mediaType];
128
+ const cacheableKey =
129
+ typeof uploadData.media === "object" &&
130
+ "url" in uploadData.media &&
131
+ !!uploadData.media.url &&
132
+ !!options.mediaCache &&
133
+ mediaType + ":" + uploadData.media.url.toString();
134
+ if (mediaType === "document" && !uploadData.fileName) {
135
+ uploadData.fileName = "file";
136
+ }
137
+ if (!uploadData.mimetype) {
138
+ uploadData.mimetype = MIMETYPE_MAP[mediaType];
139
+ }
140
+ if (cacheableKey) {
141
+ const mediaBuff = await options.mediaCache.get(cacheableKey);
142
+ if (mediaBuff) {
143
+ logger?.debug({ cacheableKey: cacheableKey }, "got media cache hit");
144
+ const obj = proto.Message.decode(mediaBuff);
145
+ const key = `${mediaType}Message`;
146
+ Object.assign(obj[key], { ...uploadData, media: undefined });
147
+ return obj;
148
+ }
149
+ }
150
+ const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
151
+ if (isNewsletter) {
152
+ logger?.info({ key: cacheableKey }, "Preparing raw media for newsletter");
153
+ const {
154
+ filePath: filePath,
155
+ fileSha256: fileSha256,
156
+ fileLength: fileLength,
157
+ } = await getRawMediaUploadData(
158
+ uploadData.media,
159
+ options.mediaTypeOverride || mediaType,
160
+ logger
161
+ );
162
+ const fileSha256B64 = fileSha256.toString("base64");
163
+ const { mediaUrl: mediaUrl, directPath: directPath } = await options.upload(filePath, {
164
+ fileEncSha256B64: fileSha256B64,
165
+ mediaType: mediaType,
166
+ timeoutMs: options.mediaUploadTimeoutMs,
167
+ });
168
+ await promises.unlink(filePath);
169
+ const obj = WAProto.Message.fromObject({
170
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
171
+ url: mediaUrl,
172
+ directPath: directPath,
173
+ fileSha256: fileSha256,
174
+ fileLength: fileLength,
175
+ ...uploadData,
176
+ media: undefined,
177
+ }),
178
+ });
179
+ if (uploadData.ptv) {
180
+ obj.ptvMessage = obj.videoMessage;
181
+ delete obj.videoMessage;
182
+ }
183
+ if (obj.stickerMessage) {
184
+ obj.stickerMessage.stickerSentTs = Date.now();
185
+ }
186
+ if (cacheableKey) {
187
+ logger?.debug({ cacheableKey: cacheableKey }, "set cache");
188
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
189
+ }
190
+ return obj;
191
+ }
192
+ const requiresDurationComputation =
193
+ mediaType === "audio" && typeof uploadData.seconds === "undefined";
194
+ const requiresThumbnailComputation =
195
+ (mediaType === "image" || mediaType === "video") &&
196
+ typeof uploadData["jpegThumbnail"] === "undefined";
197
+ const requiresWaveformProcessing = mediaType === "audio" && uploadData.ptt === true;
198
+ const requiresAudioBackground =
199
+ options.backgroundColor && mediaType === "audio" && uploadData.ptt === true;
200
+ const requiresOriginalForSomeProcessing =
201
+ requiresDurationComputation || requiresThumbnailComputation;
202
+ const {
203
+ mediaKey: mediaKey,
204
+ encFilePath: encFilePath,
205
+ originalFilePath: originalFilePath,
206
+ fileEncSha256: fileEncSha256,
207
+ fileSha256: fileSha256,
208
+ fileLength: fileLength,
209
+ } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
210
+ logger: logger,
211
+ saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
212
+ opts: options.options,
213
+ });
214
+ const fileEncSha256B64 = fileEncSha256.toString("base64");
215
+ const [{ mediaUrl: mediaUrl, directPath: directPath }] = await Promise.all([
216
+ (async () => {
217
+ const result = await options.upload(encFilePath, {
218
+ fileEncSha256B64: fileEncSha256B64,
219
+ mediaType: mediaType,
220
+ timeoutMs: options.mediaUploadTimeoutMs,
221
+ });
222
+ logger?.debug({ mediaType: mediaType, cacheableKey: cacheableKey }, "uploaded media");
223
+ return result;
224
+ })(),
225
+ (async () => {
226
+ try {
227
+ if (requiresThumbnailComputation) {
228
+ const {
229
+ thumbnail: thumbnail,
230
+ originalImageDimensions: originalImageDimensions,
231
+ } = await generateThumbnail(originalFilePath, mediaType, options);
232
+ uploadData.jpegThumbnail = thumbnail;
233
+ if (!uploadData.width && originalImageDimensions) {
234
+ uploadData.width = originalImageDimensions.width;
235
+ uploadData.height = originalImageDimensions.height;
236
+ logger?.debug("set dimensions");
237
+ }
238
+ logger?.debug("generated thumbnail");
239
+ }
240
+ if (requiresDurationComputation) {
241
+ uploadData.seconds = await getAudioDuration(originalFilePath);
242
+ logger?.debug("computed audio duration");
243
+ }
244
+ if (requiresWaveformProcessing) {
245
+ uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
246
+ logger?.debug("processed waveform");
247
+ }
248
+ if (requiresAudioBackground) {
249
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
250
+ logger?.debug("computed backgroundColor audio status");
251
+ }
252
+ } catch (error) {
253
+ logger?.warn({ trace: error.stack }, "failed to obtain extra info");
254
+ }
255
+ })(),
256
+ ]).finally(async () => {
257
+ try {
258
+ await promises.unlink(encFilePath);
259
+ if (originalFilePath) {
260
+ await promises.unlink(originalFilePath);
261
+ }
262
+ logger?.debug("removed tmp files");
263
+ } catch (error) {
264
+ logger?.warn("failed to remove tmp file");
265
+ }
266
+ });
267
+ const obj = WAProto.Message.fromObject({
268
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
269
+ url: mediaUrl,
270
+ directPath: directPath,
271
+ mediaKey: mediaKey,
272
+ fileEncSha256: fileEncSha256,
273
+ fileSha256: fileSha256,
274
+ fileLength: fileLength,
275
+ mediaKeyTimestamp: unixTimestampSeconds(),
276
+ ...uploadData,
277
+ media: undefined,
278
+ }),
279
+ });
280
+ if (uploadData.ptv) {
281
+ obj.ptvMessage = obj.videoMessage;
282
+ delete obj.videoMessage;
283
+ }
284
+ if (cacheableKey) {
285
+ logger?.debug({ cacheableKey: cacheableKey }, "set cache");
286
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
287
+ }
288
+ return obj;
289
+ };
290
+ const prepareAlbumMessageContent = async (jid, albums, options) => {
291
+ if (!Array.isArray(albums)) {
292
+ throw new Error("albums must be an array containing media objects.");
293
+ }
294
+ if (albums.length === 0) {
295
+ throw new Error("albums cannot be empty. At least one media item is required.");
296
+ }
297
+ const validCount = albums.filter((m) => "image" in m || "video" in m).length;
298
+ if (validCount === 0) {
299
+ throw new Error("albums contains no valid media. Use 'image' or 'video' keys.");
300
+ }
301
+ let mediaHandle;
302
+ let mediaMsg;
303
+ const message = [];
304
+ const albumMsg = generateWAMessageFromContent(
305
+ jid,
306
+ {
307
+ albumMessage: {
308
+ expectedImageCount: albums.filter((item) => "image" in item).length,
309
+ expectedVideoCount: albums.filter((item) => "video" in item).length,
310
+ },
311
+ },
312
+ options
313
+ );
314
+ await options.conn.relayMessage(jid, albumMsg.message, {
315
+ messageId: albumMsg.key.id,
316
+ });
317
+ for (const media of albums) {
318
+ let content = {};
319
+ if ("image" in media) {
320
+ content = { image: media.image };
321
+ } else if ("video" in media) {
322
+ content = { video: media.video };
323
+ } else {
324
+ continue;
325
+ }
326
+ mediaMsg = await generateWAMessage(
327
+ jid,
328
+ { ...content, ...media },
329
+ {
330
+ userJid: options.userJid,
331
+ upload: async (encFilePath, opts) => {
332
+ const up = await options.conn.waUploadToServer(encFilePath, {
333
+ ...opts,
334
+ newsletter: isJidNewsletter(jid),
335
+ });
336
+ mediaHandle = up.handle;
337
+ return up;
338
+ },
339
+ ...options,
340
+ }
341
+ );
342
+ if (mediaMsg) {
343
+ mediaMsg.message.messageContextInfo = {
344
+ messageSecret: randomBytes(32),
345
+ messageAssociation: {
346
+ associationType: proto.MessageAssociation.AssociationType.MEDIA_ALBUM,
347
+ parentMessageKey: albumMsg.key,
348
+ },
349
+ };
350
+ }
351
+ message.push(mediaMsg);
352
+ }
353
+ return message;
354
+ };
355
+ const prepareDisappearingMessageSettingContent = (expiration) => {
356
+ const content = {
357
+ ephemeralMessage: {
358
+ message: {
359
+ protocolMessage: {
360
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
361
+ ephemeralExpiration: expiration ? expiration : 0,
362
+ },
363
+ },
364
+ },
365
+ };
366
+ return WAProto.Message.fromObject(content);
367
+ };
368
+ const generateForwardMessageContent = (message, forceForward) => {
369
+ let content = message.message;
370
+ if (!content) {
371
+ throw new Boom("no content in message", { statusCode: 400 });
372
+ }
373
+ content = normalizeMessageContent(content);
374
+ content = proto.Message.decode(proto.Message.encode(content).finish());
375
+ let key = Object.keys(content)[0];
376
+ let score = content[key].contextInfo?.forwardingScore || 0;
377
+ if (forceForward) score += forceForward ? forceForward : 1;
378
+ if (key === "conversation") {
379
+ content.extendedTextMessage = { text: content[key] };
380
+ delete content.conversation;
381
+ key = "extendedTextMessage";
382
+ }
383
+ if (score > 0) {
384
+ content[key].contextInfo = { forwardingScore: score, isForwarded: true };
385
+ } else {
386
+ content[key].contextInfo = {};
387
+ }
388
+ return content;
389
+ };
390
+ const hasNonNullishProperty = (message, key) =>
391
+ typeof message === "object" &&
392
+ message !== null &&
393
+ key in message &&
394
+ message[key] !== null &&
395
+ message[key] !== undefined;
396
+ function hasOptionalProperty(obj, key) {
397
+ return typeof obj === "object" && obj !== null && key in obj && obj[key] !== null;
398
+ }
399
+ const generateWAMessageContent = async (message, options) => {
400
+ let m = {};
401
+ if (hasNonNullishProperty(message, "text")) {
402
+ const extContent = { text: message.text };
403
+ let urlInfo = message.linkPreview;
404
+ if (typeof urlInfo === "undefined") {
405
+ urlInfo = await generateLinkPreviewIfRequired(
406
+ message.text,
407
+ options.getUrlInfo,
408
+ options.logger
409
+ );
410
+ }
411
+ if (urlInfo) {
412
+ extContent.canonicalUrl = urlInfo["canonical-url"];
413
+ extContent.matchedText = urlInfo["matched-text"];
414
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail;
415
+ extContent.description = urlInfo.description;
416
+ extContent.title = urlInfo.title;
417
+ extContent.previewType = 0;
418
+ const img = urlInfo.highQualityThumbnail;
419
+ if (img) {
420
+ extContent.thumbnailDirectPath = img.directPath;
421
+ extContent.mediaKey = img.mediaKey;
422
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
423
+ extContent.thumbnailWidth = img.width;
424
+ extContent.thumbnailHeight = img.height;
425
+ extContent.thumbnailSha256 = img.fileSha256;
426
+ extContent.thumbnailEncSha256 = img.fileEncSha256;
427
+ }
428
+ }
429
+ if (options.backgroundColor) {
430
+ extContent.backgroundArgb = await assertColor(options.backgroundColor);
431
+ }
432
+ if (options.textColor) {
433
+ extContent.textArgb = await assertColor(options.textColor);
434
+ }
435
+ if (options.font) {
436
+ extContent.font = options.font;
437
+ }
438
+ m.extendedTextMessage = extContent;
439
+ } else if (hasNonNullishProperty(message, "contacts")) {
440
+ const contactLen = message.contacts.contacts.length;
441
+ let contactMessage;
442
+ if (!contactLen) {
443
+ throw new Boom("require atleast 1 contact", { statusCode: 400 });
444
+ }
445
+ if (contactLen === 1) {
446
+ contactMessage = {
447
+ contactMessage: WAProto.Message.ContactMessage.fromObject(
448
+ message.contacts.contacts[0]
449
+ ),
450
+ };
451
+ } else {
452
+ contactMessage = {
453
+ contactsArrayMessage: WAProto.Message.ContactsArrayMessage.fromObject(
454
+ message.contacts
455
+ ),
456
+ };
457
+ }
458
+ m = contactMessage;
459
+
460
+ } else if (hasNonNullishProperty(message, "location")) {
461
+
462
+ if (
463
+ message.location?.jpegThumbnail &&
464
+ !Buffer.isBuffer(message.location.jpegThumbnail)
465
+ ) {
466
+ message.location.jpegThumbnail = await processLocationThumbnail(
467
+ message.location.jpegThumbnail
468
+ );
469
+ }
470
+
471
+ let locationMessage;
472
+
473
+ if (message.live) {
474
+ locationMessage = {
475
+ liveLocationMessage: WAProto.Message.LiveLocationMessage.fromObject(
476
+ message.location
477
+ ),
478
+ };
479
+ } else {
480
+ locationMessage = {
481
+ locationMessage: WAProto.Message.LocationMessage.fromObject(
482
+ message.location
483
+ ),
484
+ };
485
+ }
486
+
487
+ m = locationMessage;
488
+
489
+ }else if (hasNonNullishProperty(message, "buttonsMessage")) {
490
+ const btnMsg = { ...message.buttonsMessage };
491
+ if (btnMsg.locationMessage?.jpegThumbnail && !Buffer.isBuffer(btnMsg.locationMessage.jpegThumbnail)) {
492
+ btnMsg.locationMessage.jpegThumbnail = await processLocationThumbnail(btnMsg.locationMessage.jpegThumbnail);
493
+ }
494
+ m = { buttonsMessage: btnMsg };
495
+ } else if (hasNonNullishProperty(message, "react")) {
496
+ if (!message.react.senderTimestampMs) {
497
+ message.react.senderTimestampMs = Date.now();
498
+ }
499
+ m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(message.react);
500
+ } else if (hasNonNullishProperty(message, "delete")) {
501
+ m.protocolMessage = {
502
+ key: message.delete,
503
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE,
504
+ };
505
+ } else if (hasNonNullishProperty(message, "sharePhoneNumber")) {
506
+ m.protocolMessage = {
507
+ type: WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER,
508
+ };
509
+ } else if (hasNonNullishProperty(message, "requestPhoneNumber")) {
510
+ m.requestPhoneNumberMessage = {};
511
+ } else if (hasNonNullishProperty(message, "forward")) {
512
+ m = generateForwardMessageContent(message.forward, message.force);
513
+ } else if (hasNonNullishProperty(message, "disappearingMessagesInChat")) {
514
+ const exp =
515
+ typeof message.disappearingMessagesInChat === "boolean"
516
+ ? message.disappearingMessagesInChat
517
+ ? WA_DEFAULT_EPHEMERAL
518
+ : 0
519
+ : message.disappearingMessagesInChat;
520
+ m = prepareDisappearingMessageSettingContent(exp);
521
+ } else if (hasNonNullishProperty(message, "groupInvite")) {
522
+ m.groupInviteMessage = {};
523
+ m.groupInviteMessage.inviteCode = message.groupInvite.code;
524
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.expiration;
525
+ m.groupInviteMessage.caption = message.groupInvite.caption;
526
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
527
+ m.groupInviteMessage.groupName = message.groupInvite.name;
528
+ m.groupInviteMessage.contextInfo = message.contextInfo;
529
+ if (options.getProfilePicUrl) {
530
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid);
531
+ const { thumbnail: thumbnail } = await generateThumbnail(pfpUrl, "image");
532
+ m.groupInviteMessage.jpegThumbnail = thumbnail;
533
+ }
534
+ } else if (hasNonNullishProperty(message, "adminInvite")) {
535
+ m.newsletterAdminInviteMessage = {};
536
+ m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid;
537
+ m.newsletterAdminInviteMessage.newsletterName = message.adminInvite.name;
538
+ m.newsletterAdminInviteMessage.caption = message.adminInvite.caption;
539
+ m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration;
540
+ m.newsletterAdminInviteMessage.contextInfo = message.contextInfo;
541
+ if (options.getProfilePicUrl) {
542
+ const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid);
543
+ const { thumbnail: thumbnail } = await generateThumbnail(pfpUrl, "image");
544
+ m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail;
545
+ }
546
+ } else if (hasNonNullishProperty(message, "keep")) {
547
+ m.keepInChatMessage = {};
548
+ m.keepInChatMessage.key = message.keep.key;
549
+ m.keepInChatMessage.keepType = message.keep?.type || 1;
550
+ m.keepInChatMessage.timestampMs = message.keep?.time || Date.now();
551
+ } else if (hasNonNullishProperty(message, "call")) {
552
+ m.scheduledCallCreationMessage = {};
553
+ m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now();
554
+ m.scheduledCallCreationMessage.callType = message.call?.type || 1;
555
+ m.scheduledCallCreationMessage.title = message.call?.name || "Call Creation";
556
+ } else if (hasNonNullishProperty(message, "paymentInvite")) {
557
+ m.messageContextInfo = {};
558
+ m.paymentInviteMessage = {};
559
+ m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0;
560
+ m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2;
561
+ } else if (hasNonNullishProperty(message, "orderStatus")) {
562
+ const os = message.orderStatus;
563
+ if (!os.image) throw new Error("image is required for orderStatus");
564
+
565
+ let imageInput = os.image;
566
+ if (typeof imageInput === 'string' && !imageInput.startsWith('http://') && !imageInput.startsWith('https://')) {
567
+ imageInput = await promises.readFile(imageInput);
568
+ }
569
+
570
+ const media = await prepareWAMessageMedia(
571
+ { image: imageInput },
572
+ { upload: options.upload, ...options }
573
+ );
574
+
575
+ m = {
576
+ viewOnceMessage: {
577
+ message: {
578
+ messageContextInfo: {
579
+ deviceListMetadata: {},
580
+ deviceListMetadataVersion: 2
581
+ },
582
+ interactiveMessage: proto.Message.InteractiveMessage.create({
583
+ header: {
584
+ title: os.title || "Order Status",
585
+ hasMediaAttachment: true,
586
+ ...media
587
+ },
588
+ body: {
589
+ text: os.text || "Silakan cek status pesanan Anda."
590
+ },
591
+ footer: {
592
+ text: os.footer || "Powered by AsepXyz"
593
+ },
594
+ nativeFlowMessage: {
595
+ buttons: [
596
+ {
597
+ name: "order_status",
598
+ buttonParamsJson: JSON.stringify({
599
+ reference_id: os.referenceId || "LV-001",
600
+ order: {
601
+ status: os.status || "PROCESSING",
602
+ subtotal: {
603
+ value: os.subtotalValue || 0,
604
+ offset: os.subtotalOffset || 100
605
+ },
606
+ tax: {
607
+ value: os.taxValue || 0,
608
+ offset: os.taxOffset || 100
609
+ },
610
+ currency: os.currency || "IDR"
611
+ }
612
+ })
613
+ }
614
+ ]
615
+ }
616
+ })
617
+ }
618
+ }
619
+ };
620
+ }
621
+ else if (hasNonNullishProperty(message, "ptv")) {
622
+ const { videoMessage: videoMessage } = await prepareWAMessageMedia(
623
+ { video: message.video },
624
+ options
625
+ );
626
+ m.ptvMessage = videoMessage;
627
+ } else if (hasNonNullishProperty(message, "order")) {
628
+ m.orderMessage = WAProto.Message.OrderMessage.fromObject(message.order);
629
+ } else if (hasNonNullishProperty(message, "product")) {
630
+ const { imageMessage: imageMessage } = await prepareWAMessageMedia(
631
+ { image: message.product.productImage },
632
+ options
633
+ );
634
+ m.productMessage = WAProto.Message.ProductMessage.fromObject({
635
+ ...message,
636
+ product: { ...message.product, productImage: imageMessage },
637
+ });
638
+ } else if (hasNonNullishProperty(message, "album")) {
639
+ const imageMessages = message.album.filter((item) => "image" in item);
640
+ const videoMessages = message.album.filter((item) => "video" in item);
641
+ m.albumMessage = WAProto.Message.AlbumMessage.fromObject({
642
+ expectedImageCount: imageMessages.length,
643
+ expectedVideoCount: videoMessages.length,
644
+ });
645
+ } else if (hasNonNullishProperty(message, "event")) {
646
+ m.eventMessage = WAProto.Message.EventMessage.fromObject(message.event);
647
+ if (!message.event.startTime) {
648
+ m.eventMessage.startTime = unixTimestampSeconds() + 86400;
649
+ }
650
+ if (options.getCallLink && message.event.call) {
651
+ const link = await options.getCallLink(message.event.call, m.eventMessage.startTime);
652
+ m.eventMessage.joinLink = link;
653
+ }
654
+ } else if (hasNonNullishProperty(message, "pollResult")) {
655
+ if (!Array.isArray(message.pollResult.values)) {
656
+ throw new Boom("Invalid pollResult values", { statusCode: 400 });
657
+ }
658
+ const pollResultSnapshotMessage = {
659
+ name: message.pollResult.name,
660
+ pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
661
+ optionName: optionName,
662
+ optionVoteCount: optionVoteCount,
663
+ })),
664
+ };
665
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
666
+ } else if (hasNonNullishProperty(message, "poll")) {
667
+ if (!Array.isArray(message.poll.values)) {
668
+ throw new Boom("Invalid poll values", { statusCode: 400 });
669
+ }
670
+ if (
671
+ message.poll.selectableCount < 0 ||
672
+ message.poll.selectableCount > message.poll.values.length
673
+ ) {
674
+ throw new Boom(
675
+ `poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`,
676
+ { statusCode: 400 }
677
+ );
678
+ }
679
+ const pollCreationMessage = {
680
+ name: message.poll.name,
681
+ selectableOptionsCount: message.poll?.selectableCount || 0,
682
+ options: message.poll.values.map((optionName) => ({
683
+ optionName: optionName,
684
+ })),
685
+ };
686
+ if (message.poll?.toAnnouncementGroup) {
687
+ m.pollCreationMessageV2 = pollCreationMessage;
688
+ } else {
689
+ if (message.poll.selectableCount > 0) {
690
+ m.pollCreationMessageV3 = pollCreationMessage;
691
+ } else {
692
+ m.pollCreationMessage = pollCreationMessage;
693
+ }
694
+ }
695
+ } else if (hasNonNullishProperty(message, "payment")) {
696
+ const requestPaymentMessage = {
697
+ amount: {
698
+ currencyCode: message.payment?.currency || "IDR",
699
+ offset: message.payment?.offset || 0,
700
+ value: message.payment?.amount || 999999999,
701
+ },
702
+ expiryTimestamp: message.payment?.expiry || 0,
703
+ amount1000: message.payment?.amount || 999999999 * 1e3,
704
+ currencyCodeIso4217: message.payment?.currency || "IDR",
705
+ requestFrom: message.payment?.from || "0@s.whatsapp.net",
706
+ noteMessage: {
707
+ extendedTextMessage: { text: message.payment?.note || "Notes" },
708
+ },
709
+ background: {
710
+ placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
711
+ textArgb: message.payment?.image?.textArgb || 4294967295,
712
+ subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
713
+ type: 1,
714
+ },
715
+ };
716
+ m.requestPaymentMessage = requestPaymentMessage;
717
+ } else if (hasNonNullishProperty(message, "stickerPack")) {
718
+ const {
719
+ stickers: stickers,
720
+ cover: cover,
721
+ name: name,
722
+ publisher: publisher,
723
+ packId: packId,
724
+ description: description,
725
+ } = message.stickerPack;
726
+ const { zip: zip } = require("fflate");
727
+ const stickerData = {};
728
+ const stickerPromises = stickers.map(async (s, i) => {
729
+ const { stream: stream } = await getStream(s.sticker);
730
+ const buffer = await toBuffer(stream);
731
+ const hash = sha256(buffer).toString("base64url");
732
+ const fileName = `${i.toString().padStart(2, "0")}_${hash}.webp`;
733
+ stickerData[fileName] = [new Uint8Array(buffer), { level: 0 }];
734
+ return {
735
+ fileName: fileName,
736
+ mimetype: "image/webp",
737
+ isAnimated: s.isAnimated || false,
738
+ isLottie: s.isLottie || false,
739
+ emojis: s.emojis || [],
740
+ accessibilityLabel: s.accessibilityLabel || "",
741
+ };
742
+ });
743
+ const stickerMetadata = await Promise.all(stickerPromises);
744
+ const zipBuffer = await new Promise((resolve, reject) => {
745
+ zip(stickerData, (err, data) => {
746
+ if (err) {
747
+ reject(err);
748
+ } else {
749
+ resolve(Buffer.from(data));
750
+ }
751
+ });
752
+ });
753
+ const coverBuffer = await toBuffer((await getStream(cover)).stream);
754
+ const [stickerPackUpload, coverUpload] = await Promise.all([
755
+ encryptedStream(zipBuffer, "sticker-pack", {
756
+ logger: options.logger,
757
+ opts: options.options,
758
+ }),
759
+ prepareWAMessageMedia(
760
+ { image: coverBuffer },
761
+ { ...options, mediaTypeOverride: "image" }
762
+ ),
763
+ ]);
764
+ const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
765
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString("base64"),
766
+ mediaType: "sticker-pack",
767
+ timeoutMs: options.mediaUploadTimeoutMs,
768
+ });
769
+ const coverImage = coverUpload.imageMessage;
770
+ const imageDataHash = sha256(coverBuffer).toString("base64");
771
+ const stickerPackId = packId || generateMessageID();
772
+ m.stickerPackMessage = {
773
+ name: name,
774
+ publisher: publisher,
775
+ stickerPackId: stickerPackId,
776
+ packDescription: description,
777
+ stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
778
+ stickerPackSize: stickerPackUpload.fileLength,
779
+ stickers: stickerMetadata,
780
+ fileSha256: stickerPackUpload.fileSha256,
781
+ fileEncSha256: stickerPackUpload.fileEncSha256,
782
+ mediaKey: stickerPackUpload.mediaKey,
783
+ directPath: stickerPackUploadResult.directPath,
784
+ fileLength: stickerPackUpload.fileLength,
785
+ mediaKeyTimestamp: unixTimestampSeconds(),
786
+ trayIconFileName: `${stickerPackId}.png`,
787
+ imageDataHash: imageDataHash,
788
+ thumbnailDirectPath: coverImage.directPath,
789
+ thumbnailFileSha256: coverImage.fileSha256,
790
+ thumbnailFileEncSha256: coverImage.fileEncSha256,
791
+ thumbnailHeight: coverImage.height,
792
+ thumbnailWidth: coverImage.width,
793
+ };
794
+ }
795
+
796
+ else if (hasNonNullishProperty(message, "richMessage")) {
797
+ const { randomUUID } = require('crypto');
798
+ const rich = message.richMessage;
799
+ const submessages = [];
800
+ const sections = [];
801
+ const richResponseSources = [];
802
+
803
+ const extractIE = (text) => {
804
+ let ie = [], result = '', last = 0, citation_index = 1, hyperlink_index = 0, latex_index = 0, stack = [];
805
+ for (let i = 0; i < text.length; i++) {
806
+ if (text[i] == '[' && text[i - 1] != '\\') {
807
+ stack.push(i);
808
+ } else if (text[i] == ']' && (text[i + 1] == '(' || text[i + 1] == '<')) {
809
+ let start = stack.pop();
810
+ if (start == null) continue;
811
+ let open = text[i + 1], close = open == '(' ? ')' : '>', type = open == '(' ? 'link' : 'latex', end = i + 2, depth = 1;
812
+ while (end < text.length && depth) {
813
+ if (text[end] == open && text[end - 1] != '\\') depth++;
814
+ else if (text[end] == close && text[end - 1] != '\\') depth--;
815
+ end++;
816
+ }
817
+ if (depth) continue;
818
+ let raw = text.slice(start + 1, i).trim(), url = text.slice(i + 2, end - 1).trim(), key, tag, data;
819
+ if (type == 'latex') {
820
+ let [txt = '', width = null, height = null, font_height = null, padding = null] = raw.split('|');
821
+ key = `LATEX_${latex_index++}`;
822
+ tag = `{{${key}}}${txt || 'image'}{{/${key}}}`;
823
+ data = { type: 'latex', ie: { key, text: txt, url, width, height, font_height, padding } };
824
+ } else if (raw) {
825
+ key = `HLINK_${hyperlink_index++}`;
826
+ tag = `{{${key}}}${url}{{/${key}}}`;
827
+ data = { type: 'hyperlink', ie: { key, text: raw, url } };
828
+ } else {
829
+ key = `CITE_${citation_index - 1}`;
830
+ tag = `{{${key}}}${url}{{/${key}}}`;
831
+ data = { type: 'citation', ie: { reference_id: citation_index++, key, text: '', url } };
832
+ }
833
+ result += text.slice(last, start) + tag;
834
+ last = end;
835
+ ie.push(data);
836
+ i = end - 1;
837
+ }
838
+ }
839
+ result += text.slice(last);
840
+ return { text: result, ie };
841
+ };
842
+
843
+ const tokenizer = (code, lang = 'javascript') => {
844
+ const keywordsMap = {
845
+ javascript: new Set(['break','case','catch','continue','debugger','delete','do','else','finally','for','function','if','in','instanceof','new','return','switch','this','throw','try','typeof','var','void','while','with','true','false','null','undefined','class','const','let','super','extends','export','import','yield','static','constructor','async','await','get','set'])
846
+ };
847
+ const TYPE_MAP = { 0:'DEFAULT', 1:'KEYWORD', 2:'METHOD', 3:'STR', 4:'NUMBER', 5:'COMMENT' };
848
+ const keywords = keywordsMap[lang] || new Set();
849
+ const tokens = [];
850
+ let i = 0;
851
+ const push = (content, type) => {
852
+ if (!content) return;
853
+ const last = tokens[tokens.length - 1];
854
+ if (last && last.highlightType === type) last.codeContent += content;
855
+ else tokens.push({ codeContent: content, highlightType: type });
856
+ };
857
+ while (i < code.length) {
858
+ const c = code[i];
859
+ if (/\s/.test(c)) { let s = i; while (i < code.length && /\s/.test(code[i])) i++; push(code.slice(s, i), 0); continue; }
860
+ if (c === '/' && code[i + 1] === '/') { let s = i; i += 2; while (i < code.length && code[i] !== '\n') i++; push(code.slice(s, i), 5); continue; }
861
+ if (c === '"' || c === "'" || c === '`') { let s = i; const q = c; i++; while (i < code.length) { if (code[i] === '\\' && i + 1 < code.length) i += 2; else if (code[i] === q) { i++; break; } else i++; } push(code.slice(s, i), 3); continue; }
862
+ if (/[0-9]/.test(c)) { let s = i; while (i < code.length && /[0-9.]/.test(code[i])) i++; push(code.slice(s, i), 4); continue; }
863
+ if (/[a-zA-Z_$]/.test(c)) { let s = i; while (i < code.length && /[a-zA-Z0-9_$]/.test(code[i])) i++; const word = code.slice(s, i); let type = 0; if (keywords.has(word)) type = 1; else { let j = i; while (j < code.length && /\s/.test(code[j])) j++; if (code[j] === '(') type = 2; } push(word, type); continue; }
864
+ push(c, 0); i++;
865
+ }
866
+ return { codeBlock: tokens, unified_codeBlock: tokens.map(t => ({ content: t.codeContent, type: TYPE_MAP[t.highlightType] })) };
867
+ };
868
+
869
+ const toTableMetadata = (arr) => {
870
+ const [header, ...rows] = arr;
871
+ const maxLen = Math.max(header.length, ...rows.map(r => r.length));
872
+ const normalize = (r) => [...r, ...Array(maxLen - r.length).fill('')];
873
+ const unified_rows = [{ is_header: true, cells: normalize(header) }, ...rows.map(r => ({ is_header: false, cells: normalize(r) }))];
874
+ const rowsMeta = unified_rows.map(r => ({ items: r.cells, ...(r.is_header ? { isHeading: true } : {}) }));
875
+ return { title: '', rows: rowsMeta, unified_rows };
876
+ };
877
+
878
+ if (rich.text) {
879
+ const parsed = typeof rich.text === 'string' ? extractIE(rich.text) : rich.text;
880
+ const text = parsed.text || parsed;
881
+ const inline_entities = parsed.ie ? parsed.ie.map(({ type, ie }) => {
882
+ if (type === 'hyperlink') return { key: ie.key, metadata: { display_name: ie.text, is_trusted: true, url: ie.url, __typename: 'GenAIInlineLinkItem' } };
883
+ if (type === 'citation') return { key: ie.key, metadata: { reference_id: ie.reference_id, reference_url: ie.url, reference_title: ie.url, reference_display_name: ie.url, sources: [], __typename: 'GenAISearchCitationItem' } };
884
+ if (type === 'latex') return { key: ie.key, metadata: { latex_expression: ie.text || '', latex_image: { url: ie.url, width: Number(ie.width) || 100, height: Number(ie.height) || 100 }, font_height: Number(ie.font_height) || 83.33, padding: Number(ie.padding) || 15, __typename: 'GenAILatexItem' } };
885
+ return null;
886
+ }).filter(Boolean) : [];
887
+ submessages.push({ messageType: 2, messageText: text });
888
+ sections.push({
889
+ view_model: {
890
+ primitive: { text, inline_entities, __typename: 'GenAIMarkdownTextUXPrimitive' },
891
+ __typename: 'GenAISingleLayoutViewModel'
892
+ }
893
+ });
894
+ }
895
+
896
+ if (rich.code) {
897
+ const { language, code } = rich.code;
898
+ const tok = tokenizer(code, language);
899
+ submessages.push({ messageType: 5, codeMetadata: { codeLanguage: language, codeBlocks: tok.codeBlock } });
900
+ sections.push({
901
+ view_model: {
902
+ primitive: { language, code_blocks: tok.unified_codeBlock, __typename: 'GenAICodeUXPrimitive' },
903
+ __typename: 'GenAISingleLayoutViewModel'
904
+ }
905
+ });
906
+ }
907
+
908
+ if (rich.table) {
909
+ const meta = toTableMetadata(rich.table);
910
+ submessages.push({ messageType: 4, tableMetadata: { title: meta.title, rows: meta.rows } });
911
+ sections.push({
912
+ view_model: {
913
+ primitive: { rows: meta.unified_rows, __typename: 'GenATableUXPrimitive' },
914
+ __typename: 'GenAISingleLayoutViewModel'
915
+ }
916
+ });
917
+ }
918
+
919
+ if (rich.images) {
920
+ const urls = Array.isArray(rich.images) ? rich.images : [rich.images];
921
+ submessages.push({
922
+ messageType: 1,
923
+ gridImageMetadata: {
924
+ gridImageUrl: { imagePreviewUrl: urls[0] },
925
+ imageUrls: urls.map(url => ({ imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }))
926
+ }
927
+ });
928
+ urls.forEach(url => {
929
+ sections.push({
930
+ view_model: {
931
+ primitive: { media: { url, mime_type: 'image/jpeg' }, imagine_type: 3, status: { status: 'READY' }, __typename: 'GenAIImaginePrimitive' },
932
+ __typename: 'GenAISingleLayoutViewModel'
933
+ }
934
+ });
935
+ });
936
+ }
937
+
938
+ if (rich.video) {
939
+ submessages.push({ messageType: 2, messageText: '[ CANNOT_LOAD_VIDEO - AsepXyz ]' });
940
+ sections.push({
941
+ view_model: {
942
+ primitive: {
943
+ media: { url: rich.video, mime_type: 'video/mp4', duration: 10 },
944
+ imagine_type: 'ANIMATE',
945
+ status: { status: 'READY' },
946
+ __typename: 'GenAIImaginePrimitive'
947
+ },
948
+ __typename: 'GenAISingleLayoutViewModel'
949
+ }
950
+ });
951
+ }
952
+
953
+ if (rich.productSingle) {
954
+ submessages.push({ messageType: 2, messageText: '[ CANNOT_LOAD_PRODUCT - AsepXyz ]' });
955
+ sections.push({
956
+ view_model: {
957
+ primitive: { ...rich.productSingle, __typename: 'GenAIProductItemCardPrimitive' },
958
+ __typename: 'GenAISingleLayoutViewModel'
959
+ }
960
+ });
961
+ }
962
+
963
+ if (rich.productMultiple) {
964
+ submessages.push({ messageType: 2, messageText: '[ CANNOT_LOAD_PRODUCT - AsepXyz ]' });
965
+ sections.push({
966
+ view_model: {
967
+ primitives: rich.productMultiple.map(p => ({ ...p, __typename: 'GenAIProductItemCardPrimitive' })),
968
+ __typename: 'GenAIHScrollLayoutViewModel'
969
+ }
970
+ });
971
+ }
972
+
973
+ if (rich.product && !rich.productSingle && !rich.productMultiple) {
974
+ submessages.push({ messageType: 2, messageText: '[ CANNOT_LOAD_PRODUCT - AsepXyz ]' });
975
+ if (Array.isArray(rich.product)) {
976
+ sections.push({
977
+ view_model: {
978
+ primitives: rich.product.map(p => ({ ...p, __typename: 'GenAIProductItemCardPrimitive' })),
979
+ __typename: 'GenAIHScrollLayoutViewModel'
980
+ }
981
+ });
982
+ } else {
983
+ sections.push({
984
+ view_model: {
985
+ primitive: { ...rich.product, __typename: 'GenAIProductItemCardPrimitive' },
986
+ __typename: 'GenAISingleLayoutViewModel'
987
+ }
988
+ });
989
+ }
990
+ }
991
+
992
+ if (rich.post) {
993
+ submessages.push({ messageType: 2, messageText: '[ CANNOT_LOAD_POST - AsepXyz ]' });
994
+ if (Array.isArray(rich.post)) {
995
+ sections.push({
996
+ view_model: {
997
+ primitives: rich.post.map(p => ({ ...p, __typename: 'GenAIPostPrimitive' })),
998
+ __typename: 'GenAIHScrollLayoutViewModel'
999
+ }
1000
+ });
1001
+ } else {
1002
+ sections.push({
1003
+ view_model: {
1004
+ primitive: { ...rich.post, __typename: 'GenAIPostPrimitive' },
1005
+ __typename: 'GenAISingleLayoutViewModel'
1006
+ }
1007
+ });
1008
+ }
1009
+ }
1010
+
1011
+ if (rich.reels) {
1012
+ const items = Array.isArray(rich.reels) ? rich.reels : [rich.reels];
1013
+ submessages.push({
1014
+ messageType: 9,
1015
+ contentItemsMetadata: {
1016
+ contentType: 1,
1017
+ itemsMetadata: items.map(i => ({
1018
+ reelItem: { title: i.title, profileIconUrl: i.profileIconUrl, thumbnailUrl: i.thumbnailUrl, videoUrl: i.videoUrl }
1019
+ }))
1020
+ }
1021
+ });
1022
+ sections.push({
1023
+ view_model: {
1024
+ primitives: items.map(i => ({
1025
+ reels_url: i.videoUrl,
1026
+ thumbnail_url: i.thumbnailUrl,
1027
+ creator: i.title,
1028
+ avatar_url: i.profileIconUrl,
1029
+ reels_title: i.reels_title || '',
1030
+ likes_count: i.likes_count || 0,
1031
+ shares_count: i.shares_count || 0,
1032
+ view_count: i.view_count || 0,
1033
+ reel_source: i.reel_source || 'IG',
1034
+ is_verified: i.is_verified || false,
1035
+ __typename: 'GenAIReelPrimitive'
1036
+ })),
1037
+ __typename: 'GenAIHScrollLayoutViewModel'
1038
+ }
1039
+ });
1040
+ items.forEach((i, idx) => richResponseSources.push({
1041
+ provider: 'AsepXyz',
1042
+ thumbnailCDNURL: i.thumbnailUrl,
1043
+ sourceProviderURL: i.videoUrl,
1044
+ sourceQuery: '',
1045
+ faviconCDNURL: i.profileIconUrl,
1046
+ citationNumber: idx + 1,
1047
+ sourceTitle: i.title
1048
+ }));
1049
+ }
1050
+
1051
+ if (rich.sources) {
1052
+ const sourceArr = Array.isArray(rich.sources) ? rich.sources : [rich.sources];
1053
+ sections.push({
1054
+ view_model: {
1055
+ primitive: {
1056
+ sources: sourceArr.map(s => typeof s === 'object' ? s : {
1057
+ source_type: 'THIRD_PARTY',
1058
+ source_display_name: s[2] || '',
1059
+ source_subtitle: 'AI',
1060
+ source_url: s[1] || '',
1061
+ favicon: { url: s[0] || '', mime_type: 'image/jpeg', width: 16, height: 16 }
1062
+ }),
1063
+ __typename: 'GenAISearchResultPrimitive'
1064
+ },
1065
+ __typename: 'GenAISingleLayoutViewModel'
1066
+ }
1067
+ });
1068
+ }
1069
+
1070
+ if (rich.tip) {
1071
+ submessages.push({ messageType: 2, messageText: rich.tip });
1072
+ sections.push({
1073
+ view_model: {
1074
+ primitive: { text: rich.tip, __typename: 'GenAIMetadataTextPrimitive' },
1075
+ __typename: 'GenAISingleLayoutViewModel'
1076
+ }
1077
+ });
1078
+ }
1079
+
1080
+ if (rich.suggestions) {
1081
+ sections.push({
1082
+ view_model: {
1083
+ primitives: rich.suggestions.map(s => ({
1084
+ prompt_text: s,
1085
+ prompt_type: 'SUGGESTED_PROMPT',
1086
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive'
1087
+ })),
1088
+ __typename: 'GenAIActionRowLayoutViewModel'
1089
+ }
1090
+ });
1091
+ }
1092
+
1093
+ if (rich.footer) {
1094
+ sections.push({
1095
+ view_model: {
1096
+ primitive: { text: rich.footer, __typename: 'GenAIMetadataTextPrimitive' },
1097
+ __typename: 'GenAISingleLayoutViewModel'
1098
+ }
1099
+ });
1100
+ }
1101
+
1102
+ const unifiedData = { response_id: randomUUID(), sections };
1103
+
1104
+ m = {
1105
+ messageContextInfo: {
1106
+ deviceListMetadata: {},
1107
+ deviceListMetadataVersion: 2,
1108
+ botMetadata: {
1109
+ messageDisclaimerText: rich.title || '',
1110
+ richResponseSourcesMetadata: { sources: richResponseSources }
1111
+ }
1112
+ },
1113
+ botForwardedMessage: {
1114
+ message: {
1115
+ richResponseMessage: {
1116
+ messageType: 1,
1117
+ submessages,
1118
+ unifiedResponse: {
1119
+ data: Buffer.from(JSON.stringify(unifiedData)).toString('base64')
1120
+ },
1121
+ contextInfo: {
1122
+ forwardingScore: 1,
1123
+ isForwarded: true,
1124
+ forwardedAiBotMessageInfo: { botJid: '0@bot' },
1125
+ forwardOrigin: 4
1126
+ }
1127
+ }
1128
+ }
1129
+ }
1130
+ };
1131
+ }
1132
+
1133
+
1134
+
1135
+ else {
1136
+ m = await prepareWAMessageMedia(message, options);
1137
+ }
1138
+
1139
+ if (hasNonNullishProperty(message, "buttonReply")) {
1140
+ switch (message.type) {
1141
+ case "list":
1142
+ m.listResponseMessage = {
1143
+ title: message.buttonReply.title,
1144
+ description: message.buttonReply.description,
1145
+ singleSelectReply: { selectedRowId: message.buttonReply.rowId },
1146
+ lisType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
1147
+ };
1148
+ break;
1149
+ case "template":
1150
+ m.templateButtonReplyMessage = {
1151
+ selectedDisplayText: message.buttonReply.displayText,
1152
+ selectedId: message.buttonReply.id,
1153
+ selectedIndex: message.buttonReply.index,
1154
+ };
1155
+ break;
1156
+ case "plain":
1157
+ m.buttonsResponseMessage = {
1158
+ selectedButtonId: message.buttonReply.id,
1159
+ selectedDisplayText: message.buttonReply.displayText,
1160
+ type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT,
1161
+ };
1162
+ break;
1163
+ case "interactive":
1164
+ m.interactiveResponseMessage = {
1165
+ body: {
1166
+ text: message.buttonReply.displayText,
1167
+ format: proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1,
1168
+ },
1169
+ nativeFlowResponseMessage: {
1170
+ name: message.buttonReply.nativeFlows.name,
1171
+ paramsJson: message.buttonReply.nativeFlows.paramsJson,
1172
+ version: message.buttonReply.nativeFlows.version,
1173
+ },
1174
+ };
1175
+ break;
1176
+ }
1177
+ } else if (hasNonNullishProperty(message, "sections")) {
1178
+ m.listMessage = {
1179
+ title: message.title,
1180
+ buttonText: message.buttonText,
1181
+ footerText: message.footer,
1182
+ description: message.text,
1183
+ sections: message.sections,
1184
+ listType: proto.Message.ListMessage.ListType.SINGLE_SELECT,
1185
+ };
1186
+ } else if (hasNonNullishProperty(message, "productList")) {
1187
+ const thumbnail = message.thumbnail
1188
+ ? await generateThumbnail(message.thumbnail, "image")
1189
+ : null;
1190
+ m.listMessage = {
1191
+ title: message.title,
1192
+ buttonText: message.buttonText,
1193
+ footerText: message.footer,
1194
+ description: message.text,
1195
+ productListInfo: {
1196
+ productSections: message.productList,
1197
+ headerImage: {
1198
+ productId: message.productList[0].products[0].productId,
1199
+ jpegThumbnail: thumbnail?.thumbnail || null,
1200
+ },
1201
+ businessOwnerJid: message.businessOwnerJid,
1202
+ },
1203
+ listType: proto.Message.ListMessage.ListType.PRODUCT_LIST,
1204
+ };
1205
+ }
1206
+ else if (hasNonNullishProperty(message, "buttons")) {
1207
+ const buttonsMessage = {
1208
+ buttons: message.buttons.map((b) => ({
1209
+ ...b,
1210
+ type: proto.Message.ButtonsMessage.Button.Type.RESPONSE,
1211
+ })),
1212
+ };
1213
+ if (hasNonNullishProperty(message, "text")) {
1214
+ buttonsMessage.contentText = message.text;
1215
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.EMPTY;
1216
+ } else {
1217
+ if (hasNonNullishProperty(message, "caption")) {
1218
+ buttonsMessage.contentText = message.caption;
1219
+ }
1220
+ const type = Object.keys(m)[0].replace("Message", "").toUpperCase();
1221
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType[type];
1222
+ Object.assign(buttonsMessage, m);
1223
+ }
1224
+ if (hasNonNullishProperty(message, "title")) {
1225
+ buttonsMessage.text = message.title;
1226
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.TEXT;
1227
+ }
1228
+ if (hasNonNullishProperty(message, "footer")) {
1229
+ buttonsMessage.footerText = message.footer;
1230
+ }
1231
+ m = { buttonsMessage: buttonsMessage };
1232
+
1233
+
1234
+
1235
+ } else if (hasNonNullishProperty(message, "templateButtons")) {
1236
+ const hydratedTemplate = { hydratedButtons: message.templateButtons };
1237
+ if (hasNonNullishProperty(message, "text")) {
1238
+ hydratedTemplate.hydratedContentText = message.text;
1239
+ } else {
1240
+ if (hasNonNullishProperty(message, "caption")) {
1241
+ hydratedTemplate.hydratedContentText = message.caption;
1242
+ }
1243
+ Object.assign(msg, m);
1244
+ }
1245
+ if (hasNonNullishProperty(message, "footer")) {
1246
+ hydratedTemplate.hydratedFooterText = message.footer;
1247
+ }
1248
+ m = { templateMessage: { hydratedTemplate: hydratedTemplate } };
1249
+ } else if (hasNonNullishProperty(message, "interactiveButtons")) {
1250
+ const interactiveMessage = {
1251
+ nativeFlowMessage: { buttons: message.interactiveButtons },
1252
+ };
1253
+ if (hasNonNullishProperty(message, "text")) {
1254
+ interactiveMessage.body = { text: message.text };
1255
+ }
1256
+ if (hasNonNullishProperty(message, "title")) {
1257
+ interactiveMessage.header = {
1258
+ title: message.title,
1259
+ subtitle: null,
1260
+ hasMediaAttachment: false,
1261
+ };
1262
+ if (hasNonNullishProperty(message, "subtitle")) {
1263
+ interactiveMessage.header.subtitle = message.subtitle;
1264
+ }
1265
+ } else {
1266
+ if (hasNonNullishProperty(message, "caption")) {
1267
+ interactiveMessage.body = { text: message.caption };
1268
+ interactiveMessage.header = {
1269
+ title: null,
1270
+ subtitle: null,
1271
+ hasMediaAttachment: false,
1272
+ ...Object.assign(interactiveMessage, m),
1273
+ };
1274
+ if (hasNonNullishProperty(message, "title")) {
1275
+ interactiveMessage.header.title = message.title;
1276
+ }
1277
+ if (hasNonNullishProperty(message, "subtitle")) {
1278
+ interactiveMessage.header.subtitle = message.subtitle;
1279
+ }
1280
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1281
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1282
+ message.hasMediaAttachment
1283
+ );
1284
+ }
1285
+ }
1286
+ }
1287
+ if (hasNonNullishProperty(message, "footer")) {
1288
+ interactiveMessage.footer = { text: message.footer };
1289
+ }
1290
+ m = { interactiveMessage: interactiveMessage };
1291
+ } else if (hasNonNullishProperty(message, "shop")) {
1292
+ const interactiveMessage = {
1293
+ shopStorefrontMessage: {
1294
+ surface: message.shop.surface,
1295
+ id: message.shop.id,
1296
+ },
1297
+ };
1298
+ if (hasNonNullishProperty(message, "text")) {
1299
+ interactiveMessage.body = { text: message.text };
1300
+ }
1301
+ if (hasNonNullishProperty(message, "title")) {
1302
+ interactiveMessage.header = {
1303
+ title: message.title,
1304
+ subtitle: null,
1305
+ hasMediaAttachment: false,
1306
+ };
1307
+ if (hasNonNullishProperty(message, "subtitle")) {
1308
+ interactiveMessage.header.subtitle = message.subtitle;
1309
+ }
1310
+ } else {
1311
+ if (hasNonNullishProperty(message, "caption")) {
1312
+ interactiveMessage.body = { text: message.caption };
1313
+ interactiveMessage.header = {
1314
+ title: null,
1315
+ subtitle: null,
1316
+ hasMediaAttachment: false,
1317
+ ...Object.assign(interactiveMessage, m),
1318
+ };
1319
+ if (hasNonNullishProperty(message, "title")) {
1320
+ interactiveMessage.header.title = message.title;
1321
+ }
1322
+ if (hasNonNullishProperty(message, "subtitle")) {
1323
+ interactiveMessage.header.subtitle = message.subtitle;
1324
+ }
1325
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1326
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1327
+ message.hasMediaAttachment
1328
+ );
1329
+ }
1330
+ }
1331
+ }
1332
+ if (hasNonNullishProperty(message, "footer")) {
1333
+ interactiveMessage.footer = { text: message.footer };
1334
+ }
1335
+ m = { interactiveMessage: interactiveMessage };
1336
+ } else if (hasNonNullishProperty(message, "collection")) {
1337
+ const interactiveMessage = {
1338
+ collectionMessage: {
1339
+ bizJid: message.collection.bizJid,
1340
+ id: message.collection.id,
1341
+ messageVersion: message?.collection?.version,
1342
+ },
1343
+ };
1344
+ if (hasNonNullishProperty(message, "text")) {
1345
+ interactiveMessage.body = { text: message.text };
1346
+ }
1347
+ if (hasNonNullishProperty(message, "title")) {
1348
+ interactiveMessage.header = {
1349
+ title: message.title,
1350
+ subtitle: null,
1351
+ hasMediaAttachment: false,
1352
+ };
1353
+ if (hasNonNullishProperty(message, "subtitle")) {
1354
+ interactiveMessage.header.subtitle = message.subtitle;
1355
+ }
1356
+ } else {
1357
+ if (hasNonNullishProperty(message, "caption")) {
1358
+ interactiveMessage.body = { text: message.caption };
1359
+ interactiveMessage.header = {
1360
+ title: null,
1361
+ subtitle: null,
1362
+ hasMediaAttachment: false,
1363
+ ...Object.assign(interactiveMessage, m),
1364
+ };
1365
+ if (hasNonNullishProperty(message, "title")) {
1366
+ interactiveMessage.header.title = message.title;
1367
+ }
1368
+ if (hasNonNullishProperty(message, "subtitle")) {
1369
+ interactiveMessage.header.subtitle = message.subtitle;
1370
+ }
1371
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1372
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1373
+ message.hasMediaAttachment
1374
+ );
1375
+ }
1376
+ }
1377
+ }
1378
+ if (hasNonNullishProperty(message, "footer")) {
1379
+ interactiveMessage.footer = { text: message.footer };
1380
+ }
1381
+ m = { interactiveMessage: interactiveMessage };
1382
+ } else if (hasNonNullishProperty(message, "cards")) {
1383
+ const slides = await Promise.all(
1384
+ message.cards.map(async (slide) => {
1385
+ const {
1386
+ image: image,
1387
+ video: video,
1388
+ product: product,
1389
+ title: title,
1390
+ body: body,
1391
+ footer: footer,
1392
+ buttons: buttons,
1393
+ } = slide;
1394
+ let header;
1395
+ if (product) {
1396
+ const { imageMessage: imageMessage } = await prepareWAMessageMedia(
1397
+ { image: product.productImage, ...options },
1398
+ options
1399
+ );
1400
+ header = {
1401
+ productMessage: {
1402
+ product: { ...product, productImage: imageMessage },
1403
+ ...slide,
1404
+ },
1405
+ };
1406
+ } else if (image) {
1407
+ header = await prepareWAMessageMedia({ image: image, ...options }, options);
1408
+ } else if (video) {
1409
+ header = await prepareWAMessageMedia({ video: video, ...options }, options);
1410
+ }
1411
+ const msg = {
1412
+ header: { title: title, hasMediaAttachment: true, ...header },
1413
+ body: { text: body },
1414
+ footer: { text: footer },
1415
+ nativeFlowMessage: { buttons: buttons },
1416
+ };
1417
+ return msg;
1418
+ })
1419
+ );
1420
+ const interactiveMessage = { carouselMessage: { cards: slides } };
1421
+ if (hasNonNullishProperty(message, "text")) {
1422
+ interactiveMessage.body = { text: message.text };
1423
+ }
1424
+ if (hasNonNullishProperty(message, "title")) {
1425
+ interactiveMessage.header = {
1426
+ title: message.title,
1427
+ subtitle: null,
1428
+ hasMediaAttachment: false,
1429
+ };
1430
+ if (hasNonNullishProperty(message, "subtitle")) {
1431
+ interactiveMessage.header.subtitle = message.subtitle;
1432
+ }
1433
+ } else {
1434
+ if (hasNonNullishProperty(message, "caption")) {
1435
+ interactiveMessage.body = { text: message.caption };
1436
+ interactiveMessage.header = {
1437
+ title: null,
1438
+ subtitle: null,
1439
+ hasMediaAttachment: false,
1440
+ ...Object.assign(interactiveMessage, m),
1441
+ };
1442
+ if (hasNonNullishProperty(message, "title")) {
1443
+ interactiveMessage.header.title = message.title;
1444
+ }
1445
+ if (hasNonNullishProperty(message, "subtitle")) {
1446
+ interactiveMessage.header.subtitle = message.subtitle;
1447
+ }
1448
+ if (hasNonNullishProperty(message, "hasMediaAttachment")) {
1449
+ interactiveMessage.header.hasMediaAttachment = Boolean(
1450
+ message.hasMediaAttachment
1451
+ );
1452
+ }
1453
+ }
1454
+ }
1455
+ if (hasNonNullishProperty(message, "footer")) {
1456
+ interactiveMessage.footer = { text: message.footer };
1457
+ }
1458
+ m = { interactiveMessage: interactiveMessage };
1459
+ }
1460
+ if (hasOptionalProperty(message, "ephemeral")) {
1461
+ m = { ephemeralMessage: { message: m } };
1462
+ }
1463
+ if (hasOptionalProperty(message, "mentions") && message.mentions?.length) {
1464
+ const messageType = Object.keys(m)[0];
1465
+ const key = m[messageType];
1466
+ if ("contextInfo" in key && !!key.contextInfo) {
1467
+ key.contextInfo.mentionedJid = message.mentions;
1468
+ } else if (key) {
1469
+ key.contextInfo = { mentionedJid: message.mentions };
1470
+ }
1471
+ }
1472
+ if (hasOptionalProperty(message, "contextInfo") && !!message.contextInfo) {
1473
+ const messageType = Object.keys(m)[0];
1474
+ const key = m[messageType];
1475
+ if ("contextInfo" in key && !!key.contextInfo) {
1476
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1477
+ } else if (key) {
1478
+ key.contextInfo = message.contextInfo;
1479
+ }
1480
+ }
1481
+ if (hasOptionalProperty(message, "edit")) {
1482
+ m = {
1483
+ protocolMessage: {
1484
+ key: message.edit,
1485
+ editedMessage: m,
1486
+ timestampMs: Date.now(),
1487
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
1488
+ },
1489
+ };
1490
+ }
1491
+ return WAProto.Message.fromObject(m);
1492
+ };
1493
+ const generateWAMessageFromContent = (jid, message, options) => {
1494
+ if (!options.timestamp) {
1495
+ options.timestamp = new Date();
1496
+ }
1497
+ const innerMessage = normalizeMessageContent(message);
1498
+ const key = getContentType(innerMessage);
1499
+ const timestamp = unixTimestampSeconds(options.timestamp);
1500
+ const threadId = [];
1501
+ const { quoted: quoted, userJid: userJid } = options;
1502
+ if (quoted && !isJidNewsletter(jid)) {
1503
+ const participant = quoted.key.fromMe
1504
+ ? userJid
1505
+ : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
1506
+ let quotedMsg = normalizeMessageContent(quoted.message);
1507
+ const msgType = getContentType(quotedMsg);
1508
+ quotedMsg = proto.Message.fromObject({ [msgType]: quotedMsg[msgType] });
1509
+ const quotedContent = quotedMsg[msgType];
1510
+ if (typeof quotedContent === "object" && quotedContent && "contextInfo" in quotedContent) {
1511
+ delete quotedContent.contextInfo;
1512
+ }
1513
+ let requestPayment;
1514
+ if (key === "requestPaymentMessage") {
1515
+ if (
1516
+ innerMessage?.requestPaymentMessage &&
1517
+ innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage
1518
+ ) {
1519
+ requestPayment =
1520
+ innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage;
1521
+ } else if (
1522
+ innerMessage?.requestPaymentMessage &&
1523
+ innerMessage?.requestPaymentMessage?.noteMessage?.stickerMessage
1524
+ ) {
1525
+ requestPayment = innerMessage.requestPaymentMessage?.noteMessage?.stickerMessage;
1526
+ }
1527
+ }
1528
+ const contextInfo =
1529
+ (key === "requestPaymentMessage"
1530
+ ? requestPayment?.contextInfo
1531
+ : innerMessage[key].contextInfo) || {};
1532
+ contextInfo.participant = jidNormalizedUser(participant);
1533
+ contextInfo.stanzaId = quoted.key.id;
1534
+ contextInfo.quotedMessage = quotedMsg;
1535
+ if (jid !== quoted.key.remoteJid) {
1536
+ contextInfo.remoteJid = quoted.key.remoteJid;
1537
+ }
1538
+ if (contextInfo.quotedMessage) {
1539
+ contextInfo.quotedType = proto.ContextInfo.QuotedType.EXPLICIT;
1540
+ }
1541
+ if (contextInfo.quotedMessage && isJidGroup(jid)) {
1542
+ threadId.push({
1543
+ threadType: proto.ThreadID.ThreadType.VIEW_REPLIES,
1544
+ threadKey: {
1545
+ remoteJid: quoted?.key?.remoteJid,
1546
+ fromMe: quoted?.key?.fromMe,
1547
+ id: generateMessageID(),
1548
+ ...(quoted?.key?.fromMe ? {} : { participant: quoted?.key?.participant }),
1549
+ },
1550
+ });
1551
+ }
1552
+ if (key === "requestPaymentMessage" && requestPayment) {
1553
+ requestPayment.contextInfo = contextInfo;
1554
+ } else {
1555
+ innerMessage[key].contextInfo = contextInfo;
1556
+ }
1557
+ }
1558
+ if (key !== "protocolMessage" && key !== "ephemeralMessage" && !isJidNewsletter(jid)) {
1559
+ message.messageContextInfo = {
1560
+ threadId: threadId.length > 0 ? threadId : [],
1561
+ messageSecret: randomBytes(32),
1562
+ ...message.messageContextInfo,
1563
+ };
1564
+ innerMessage[key].contextInfo = {
1565
+ ...(innerMessage[key].contextInfo || {}),
1566
+ expiration: options.ephemeralExpiration ? options.ephemeralExpiration : 0,
1567
+ };
1568
+ }
1569
+ message = WAProto.Message.fromObject(message);
1570
+ const messageJSON = {
1571
+ key: {
1572
+ remoteJid: jid,
1573
+ fromMe: true,
1574
+ id: options?.messageId || generateMessageID(),
1575
+ },
1576
+ message: message,
1577
+ messageTimestamp: timestamp,
1578
+ messageStubParameters: [],
1579
+ participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined,
1580
+ status: WAMessageStatus.PENDING,
1581
+ };
1582
+ return WAProto.WebMessageInfo.fromObject(messageJSON);
1583
+ };
1584
+ const generateWAMessage = async (jid, content, options) => {
1585
+ options.logger = options?.logger?.child({ msgId: options.messageId });
1586
+ return generateWAMessageFromContent(
1587
+ jid,
1588
+ await generateWAMessageContent(content, {
1589
+ newsletter: isJidNewsletter(jid),
1590
+ ...options,
1591
+ }),
1592
+ options
1593
+ );
1594
+ };
1595
+ const getContentType = (content) => {
1596
+ if (content) {
1597
+ const keys = Object.keys(content);
1598
+ const key = keys.find(
1599
+ (k) =>
1600
+ (k === "conversation" ||
1601
+ k.endsWith("Message") ||
1602
+ k.endsWith("V2") ||
1603
+ k.endsWith("V3") ||
1604
+ k.endsWith("V4")) &&
1605
+ k !== "senderKeyDistributionMessage" &&
1606
+ k !== "messageContextInfo"
1607
+ );
1608
+ return key;
1609
+ }
1610
+ };
1611
+ const normalizeMessageContent = (content) => {
1612
+ if (!content) {
1613
+ return undefined;
1614
+ }
1615
+ for (let i = 0; i < 5; i++) {
1616
+ const inner = getFutureProofMessage(content);
1617
+ if (!inner) {
1618
+ break;
1619
+ }
1620
+ content = inner.message;
1621
+ }
1622
+ return content;
1623
+ function getFutureProofMessage(message) {
1624
+ return (
1625
+ message?.editedMessage ||
1626
+ message?.statusAddYours ||
1627
+ message?.botTaskMessage ||
1628
+ message?.eventCoverImage ||
1629
+ message?.questionMessage ||
1630
+ message?.viewOnceMessage ||
1631
+ message?.botInvokeMessage ||
1632
+ message?.ephemeralMessage ||
1633
+ message?.limitSharingMessage ||
1634
+ message?.viewOnceMessageV2 ||
1635
+ message?.lottieStickerMessage ||
1636
+ message?.questionReplyMessage ||
1637
+ message?.botForwardedMessage ||
1638
+ message?.statusMentionMessage ||
1639
+ message?.pollCreationMessageV4 ||
1640
+ message?.associatedChildMessage ||
1641
+ message?.groupMentionedMessage ||
1642
+ message?.groupStatusMentionMessage ||
1643
+ message?.viewOnceMessageV2Extension ||
1644
+ message?.documentWithCaptionMessage ||
1645
+ message?.pollCreationOptionImageMessage
1646
+ );
1647
+ }
1648
+ };
1649
+ const extractMessageContent = (content) => {
1650
+ const extractFromButtonsMessage = (msg) => {
1651
+ const header = typeof msg.header === "object" && msg.header !== null;
1652
+ if (header ? msg.header?.imageMessage : msg.imageMessage) {
1653
+ return {
1654
+ imageMessage: header ? msg.header.imageMessage : msg.imageMessage,
1655
+ };
1656
+ } else if (header ? msg.header?.documentMessage : msg.documentMessage) {
1657
+ return {
1658
+ documentMessage: header ? msg.header.documentMessage : msg.documentMessage,
1659
+ };
1660
+ } else if (header ? msg.header?.videoMessage : msg.videoMessage) {
1661
+ return {
1662
+ videoMessage: header ? msg.header.videoMessage : msg.videoMessage,
1663
+ };
1664
+ } else if (header ? msg.header?.locationMessage : msg.locationMessage) {
1665
+ return {
1666
+ locationMessage: header ? msg.header.locationMessage : msg.locationMessage,
1667
+ };
1668
+ } else if (header ? msg.header?.productMessage : msg.productMessage) {
1669
+ return {
1670
+ productMessage: header ? msg.header.productMessage : msg.productMessage,
1671
+ };
1672
+ } else {
1673
+ return {
1674
+ conversation:
1675
+ "contentText" in msg
1676
+ ? msg.contentText
1677
+ : "hydratedContentText" in msg
1678
+ ? msg.hydratedContentText
1679
+ : "body" in msg
1680
+ ? msg.body.text
1681
+ : "",
1682
+ };
1683
+ }
1684
+ };
1685
+ content = normalizeMessageContent(content);
1686
+ if (content?.buttonsMessage) {
1687
+ return extractFromButtonsMessage(content.buttonsMessage);
1688
+ }
1689
+ if (content?.interactiveMessage) {
1690
+ return extractFromButtonsMessage(content.interactiveMessage);
1691
+ }
1692
+ if (content?.templateMessage?.interactiveMessageTemplate) {
1693
+ return extractFromButtonsMessage(content?.templateMessage?.interactiveMessageTemplate);
1694
+ }
1695
+ if (content?.templateMessage?.hydratedFourRowTemplate) {
1696
+ return extractFromButtonsMessage(content?.templateMessage?.hydratedFourRowTemplate);
1697
+ }
1698
+ if (content?.templateMessage?.hydratedTemplate) {
1699
+ return extractFromButtonsMessage(content?.templateMessage?.hydratedTemplate);
1700
+ }
1701
+ if (content?.templateMessage?.fourRowTemplate) {
1702
+ return extractFromButtonsMessage(content?.templateMessage?.fourRowTemplate);
1703
+ }
1704
+ return content;
1705
+ };
1706
+ const getDevice = (id) =>
1707
+ /^3A.{18}$/.test(id)
1708
+ ? "ios"
1709
+ : /^3E.{20}$/.test(id)
1710
+ ? "web"
1711
+ : /^(.{21}|.{32})$/.test(id)
1712
+ ? "android"
1713
+ : /^(3F|.{18}$)/.test(id)
1714
+ ? "desktop"
1715
+ : "baileys";
1716
+ const updateMessageWithReceipt = (msg, receipt) => {
1717
+ msg.userReceipt = msg.userReceipt || [];
1718
+ const recp = msg.userReceipt.find((m) => m.userJid === receipt.userJid);
1719
+ if (recp) {
1720
+ Object.assign(recp, receipt);
1721
+ } else {
1722
+ msg.userReceipt.push(receipt);
1723
+ }
1724
+ };
1725
+ const updateMessageWithReaction = (msg, reaction) => {
1726
+ const authorID = getKeyAuthor(reaction.key);
1727
+ const reactions = (msg.reactions || []).filter((r) => getKeyAuthor(r.key) !== authorID);
1728
+ reaction.text = reaction.text || "";
1729
+ reactions.push(reaction);
1730
+ msg.reactions = reactions;
1731
+ };
1732
+ const updateMessageWithPollUpdate = (msg, update) => {
1733
+ const authorID = getKeyAuthor(update.pollUpdateMessageKey);
1734
+ const votes = (msg.pollUpdates || []).filter(
1735
+ (r) => getKeyAuthor(r.pollUpdateMessageKey) !== authorID
1736
+ );
1737
+ if (update.vote?.selectedOptions?.length) {
1738
+ votes.push(update);
1739
+ }
1740
+ msg.pollUpdates = votes;
1741
+ };
1742
+ const updateMessageWithEventResponse = (msg, update) => {
1743
+ const authorID = getKeyAuthor(update.eventResponseMessageKey);
1744
+ const responses = (msg.eventResponses || []).filter(
1745
+ (r) => getKeyAuthor(r.eventResponseMessageKey) !== authorID
1746
+ );
1747
+ responses.push(update);
1748
+ msg.eventResponses = responses;
1749
+ };
1750
+ function getAggregateVotesInPollMessage({ message: message, pollUpdates: pollUpdates }, meId) {
1751
+ message = normalizeMessageContent(message);
1752
+ const opts =
1753
+ message?.pollCreationMessage?.options ||
1754
+ message?.pollCreationMessageV2?.options ||
1755
+ message?.pollCreationMessageV3?.options ||
1756
+ [];
1757
+ const voteHashMap = opts.reduce((acc, opt) => {
1758
+ const hash = sha256(Buffer.from(opt.optionName || "")).toString();
1759
+ acc[hash] = { name: opt.optionName || "", voters: [] };
1760
+ return acc;
1761
+ }, {});
1762
+ for (const update of pollUpdates || []) {
1763
+ const { vote: vote } = update;
1764
+ if (!vote) {
1765
+ continue;
1766
+ }
1767
+ for (const option of vote.selectedOptions || []) {
1768
+ const hash = option.toString();
1769
+ let data = voteHashMap[hash];
1770
+ if (!data) {
1771
+ voteHashMap[hash] = { name: "Unknown", voters: [] };
1772
+ data = voteHashMap[hash];
1773
+ }
1774
+ voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
1775
+ }
1776
+ }
1777
+ return Object.values(voteHashMap);
1778
+ }
1779
+ function getAggregateResponsesInEventMessage({ eventResponses: eventResponses }, meLid) {
1780
+ const responseTypes = ["GOING", "NOT_GOING", "MAYBE"];
1781
+ const responseMap = {};
1782
+ for (const type of responseTypes) {
1783
+ responseMap[type] = { response: type, responders: [] };
1784
+ }
1785
+ for (const update of eventResponses) {
1786
+ const { response: response } = update.response || 0;
1787
+ const responseType = proto.Message.EventResponseMessage.EventResponseType[response];
1788
+ if (responseType !== "UNKNOWN" && responseMap[responseType]) {
1789
+ responseMap[responseType].responders.push(
1790
+ getKeyAuthor(update.eventResponseMessageKey, meLid)
1791
+ );
1792
+ }
1793
+ }
1794
+ return Object.values(responseMap);
1795
+ }
1796
+ const aggregateMessageKeysNotFromMe = (keys) => {
1797
+ const keyMap = {};
1798
+ for (const { remoteJid: remoteJid, id: id, participant: participant, fromMe: fromMe } of keys) {
1799
+ if (!fromMe) {
1800
+ const uqKey = `${remoteJid}:${participant || ""}`;
1801
+ if (!keyMap[uqKey]) {
1802
+ keyMap[uqKey] = {
1803
+ jid: remoteJid,
1804
+ participant: participant,
1805
+ messageIds: [],
1806
+ };
1807
+ }
1808
+ keyMap[uqKey].messageIds.push(id);
1809
+ }
1810
+ }
1811
+ return Object.values(keyMap);
1812
+ };
1813
+ const REUPLOAD_REQUIRED_STATUS = [410, 404];
1814
+ const downloadMediaMessage = async (message, type, options, ctx) => {
1815
+ const result = await downloadMsg().catch(async (error) => {
1816
+ if (
1817
+ ctx &&
1818
+ typeof error?.status === "number" &&
1819
+ REUPLOAD_REQUIRED_STATUS.includes(error.status)
1820
+ ) {
1821
+ ctx.logger.info({ key: message.key }, "sending reupload media request...");
1822
+ message = await ctx.reuploadRequest(message);
1823
+ const result = await downloadMsg();
1824
+ return result;
1825
+ }
1826
+ throw error;
1827
+ });
1828
+ return result;
1829
+ async function downloadMsg() {
1830
+ const mContent = extractMessageContent(message.message);
1831
+ if (!mContent) {
1832
+ throw new Boom("No message present", { statusCode: 400, data: message });
1833
+ }
1834
+ const contentType = getContentType(mContent);
1835
+ let mediaType = contentType?.replace("Message", "");
1836
+ const media =
1837
+ contentType === "productMessage"
1838
+ ? mContent[contentType]?.product?.productImage
1839
+ : mContent[contentType];
1840
+ if (
1841
+ !media ||
1842
+ typeof media !== "object" ||
1843
+ (!("url" in media) && !("thumbnailDirectPath" in media))
1844
+ ) {
1845
+ throw new Boom(`"${contentType}" message is not a media message`);
1846
+ }
1847
+ let download;
1848
+ if ("thumbnailDirectPath" in media && !("url" in media)) {
1849
+ download = {
1850
+ directPath: media.thumbnailDirectPath,
1851
+ mediaKey: media.mediaKey,
1852
+ };
1853
+ mediaType = "thumbnail-link";
1854
+ } else {
1855
+ download = media;
1856
+ }
1857
+ const stream = await downloadContentFromMessage(download, mediaType, options);
1858
+ if (type === "buffer") {
1859
+ const bufferArray = [];
1860
+ for await (const chunk of stream) {
1861
+ bufferArray.push(chunk);
1862
+ }
1863
+ return Buffer.concat(bufferArray);
1864
+ }
1865
+ return stream;
1866
+ }
1867
+ };
1868
+ const assertMediaContent = (content) => {
1869
+ content = extractMessageContent(content);
1870
+ const mediaContent =
1871
+ content?.documentMessage ||
1872
+ content?.imageMessage ||
1873
+ content?.videoMessage ||
1874
+ content?.audioMessage ||
1875
+ content?.stickerMessage;
1876
+ if (!mediaContent) {
1877
+ throw new Boom("given message is not a media message", {
1878
+ statusCode: 400,
1879
+ data: content,
1880
+ });
1881
+ }
1882
+ return mediaContent;
1883
+ };
1884
+ module.exports = {
1885
+ extractUrlFromText: extractUrlFromText,
1886
+ generateLinkPreviewIfRequired: generateLinkPreviewIfRequired,
1887
+ prepareWAMessageMedia: prepareWAMessageMedia,
1888
+ prepareAlbumMessageContent: prepareAlbumMessageContent,
1889
+ prepareDisappearingMessageSettingContent: prepareDisappearingMessageSettingContent,
1890
+ generateForwardMessageContent: generateForwardMessageContent,
1891
+ generateWAMessageContent: generateWAMessageContent,
1892
+ generateWAMessageFromContent: generateWAMessageFromContent,
1893
+ generateWAMessage: generateWAMessage,
1894
+ getContentType: getContentType,
1895
+ hasNonNullishProperty: hasNonNullishProperty,
1896
+ normalizeMessageContent: normalizeMessageContent,
1897
+ extractMessageContent: extractMessageContent,
1898
+ getDevice: getDevice,
1899
+ updateMessageWithReceipt: updateMessageWithReceipt,
1900
+ updateMessageWithReaction: updateMessageWithReaction,
1901
+ updateMessageWithPollUpdate: updateMessageWithPollUpdate,
1902
+ updateMessageWithEventResponse: updateMessageWithEventResponse,
1903
+ getAggregateVotesInPollMessage: getAggregateVotesInPollMessage,
1904
+ getAggregateResponsesInEventMessage: getAggregateResponsesInEventMessage,
1905
+ aggregateMessageKeysNotFromMe: aggregateMessageKeysNotFromMe,
1906
+ downloadMediaMessage: downloadMediaMessage,
1907
+ assertMediaContent: assertMediaContent,
1908
+ };