@oxidezap/baileyrs 0.0.7

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +490 -0
  3. package/WAProto/index.d.ts +13875 -0
  4. package/WAProto/index.js +175 -0
  5. package/lib/Defaults/index.d.ts +9 -0
  6. package/lib/Defaults/index.d.ts.map +1 -0
  7. package/lib/Defaults/index.js +32 -0
  8. package/lib/Defaults/index.js.map +1 -0
  9. package/lib/Socket/blocking.d.ts +6 -0
  10. package/lib/Socket/blocking.d.ts.map +1 -0
  11. package/lib/Socket/blocking.js +9 -0
  12. package/lib/Socket/blocking.js.map +1 -0
  13. package/lib/Socket/chat-actions.d.ts +32 -0
  14. package/lib/Socket/chat-actions.d.ts.map +1 -0
  15. package/lib/Socket/chat-actions.js +78 -0
  16. package/lib/Socket/chat-actions.js.map +1 -0
  17. package/lib/Socket/contacts.d.ts +10 -0
  18. package/lib/Socket/contacts.d.ts.map +1 -0
  19. package/lib/Socket/contacts.js +21 -0
  20. package/lib/Socket/contacts.js.map +1 -0
  21. package/lib/Socket/events.d.ts +10 -0
  22. package/lib/Socket/events.d.ts.map +1 -0
  23. package/lib/Socket/events.js +293 -0
  24. package/lib/Socket/events.js.map +1 -0
  25. package/lib/Socket/groups.d.ts +28 -0
  26. package/lib/Socket/groups.d.ts.map +1 -0
  27. package/lib/Socket/groups.js +90 -0
  28. package/lib/Socket/groups.js.map +1 -0
  29. package/lib/Socket/index.d.ts +202 -0
  30. package/lib/Socket/index.d.ts.map +1 -0
  31. package/lib/Socket/index.js +378 -0
  32. package/lib/Socket/index.js.map +1 -0
  33. package/lib/Socket/messages.d.ts +42 -0
  34. package/lib/Socket/messages.d.ts.map +1 -0
  35. package/lib/Socket/messages.js +172 -0
  36. package/lib/Socket/messages.js.map +1 -0
  37. package/lib/Socket/newsletter.d.ts +9 -0
  38. package/lib/Socket/newsletter.d.ts.map +1 -0
  39. package/lib/Socket/newsletter.js +18 -0
  40. package/lib/Socket/newsletter.js.map +1 -0
  41. package/lib/Socket/presence.d.ts +7 -0
  42. package/lib/Socket/presence.d.ts.map +1 -0
  43. package/lib/Socket/presence.js +12 -0
  44. package/lib/Socket/presence.js.map +1 -0
  45. package/lib/Socket/profile.d.ts +14 -0
  46. package/lib/Socket/profile.d.ts.map +1 -0
  47. package/lib/Socket/profile.js +36 -0
  48. package/lib/Socket/profile.js.map +1 -0
  49. package/lib/Socket/transport.d.ts +12 -0
  50. package/lib/Socket/transport.d.ts.map +1 -0
  51. package/lib/Socket/transport.js +90 -0
  52. package/lib/Socket/transport.js.map +1 -0
  53. package/lib/Socket/types.d.ts +30 -0
  54. package/lib/Socket/types.d.ts.map +1 -0
  55. package/lib/Socket/types.js +3 -0
  56. package/lib/Socket/types.js.map +1 -0
  57. package/lib/Types/Auth.d.ts +144 -0
  58. package/lib/Types/Auth.d.ts.map +1 -0
  59. package/lib/Types/Auth.js +2 -0
  60. package/lib/Types/Auth.js.map +1 -0
  61. package/lib/Types/BinaryNode.d.ts +15 -0
  62. package/lib/Types/BinaryNode.d.ts.map +1 -0
  63. package/lib/Types/BinaryNode.js +2 -0
  64. package/lib/Types/BinaryNode.js.map +1 -0
  65. package/lib/Types/Bussines.d.ts +25 -0
  66. package/lib/Types/Bussines.d.ts.map +1 -0
  67. package/lib/Types/Bussines.js +2 -0
  68. package/lib/Types/Bussines.js.map +1 -0
  69. package/lib/Types/Call.d.ts +15 -0
  70. package/lib/Types/Call.d.ts.map +1 -0
  71. package/lib/Types/Call.js +2 -0
  72. package/lib/Types/Call.js.map +1 -0
  73. package/lib/Types/Chat.d.ts +123 -0
  74. package/lib/Types/Chat.d.ts.map +1 -0
  75. package/lib/Types/Chat.js +8 -0
  76. package/lib/Types/Chat.js.map +1 -0
  77. package/lib/Types/Contact.d.ts +24 -0
  78. package/lib/Types/Contact.d.ts.map +1 -0
  79. package/lib/Types/Contact.js +2 -0
  80. package/lib/Types/Contact.js.map +1 -0
  81. package/lib/Types/Events.d.ts +238 -0
  82. package/lib/Types/Events.d.ts.map +1 -0
  83. package/lib/Types/Events.js +2 -0
  84. package/lib/Types/Events.js.map +1 -0
  85. package/lib/Types/GroupMetadata.d.ts +67 -0
  86. package/lib/Types/GroupMetadata.d.ts.map +1 -0
  87. package/lib/Types/GroupMetadata.js +2 -0
  88. package/lib/Types/GroupMetadata.js.map +1 -0
  89. package/lib/Types/Label.d.ts +47 -0
  90. package/lib/Types/Label.d.ts.map +1 -0
  91. package/lib/Types/Label.js +25 -0
  92. package/lib/Types/Label.js.map +1 -0
  93. package/lib/Types/LabelAssociation.d.ts +30 -0
  94. package/lib/Types/LabelAssociation.d.ts.map +1 -0
  95. package/lib/Types/LabelAssociation.js +7 -0
  96. package/lib/Types/LabelAssociation.js.map +1 -0
  97. package/lib/Types/Message.d.ts +298 -0
  98. package/lib/Types/Message.d.ts.map +1 -0
  99. package/lib/Types/Message.js +12 -0
  100. package/lib/Types/Message.js.map +1 -0
  101. package/lib/Types/Newsletter.d.ts +98 -0
  102. package/lib/Types/Newsletter.d.ts.map +1 -0
  103. package/lib/Types/Newsletter.js +31 -0
  104. package/lib/Types/Newsletter.js.map +1 -0
  105. package/lib/Types/Socket.d.ts +54 -0
  106. package/lib/Types/Socket.d.ts.map +1 -0
  107. package/lib/Types/Socket.js +2 -0
  108. package/lib/Types/Socket.js.map +1 -0
  109. package/lib/Types/State.d.ts +29 -0
  110. package/lib/Types/State.d.ts.map +1 -0
  111. package/lib/Types/State.js +2 -0
  112. package/lib/Types/State.js.map +1 -0
  113. package/lib/Types/index.d.ts +60 -0
  114. package/lib/Types/index.d.ts.map +1 -0
  115. package/lib/Types/index.js +25 -0
  116. package/lib/Types/index.js.map +1 -0
  117. package/lib/Utils/browser-utils.d.ts +3 -0
  118. package/lib/Utils/browser-utils.d.ts.map +1 -0
  119. package/lib/Utils/browser-utils.js +22 -0
  120. package/lib/Utils/browser-utils.js.map +1 -0
  121. package/lib/Utils/event-buffer.d.ts +7 -0
  122. package/lib/Utils/event-buffer.d.ts.map +1 -0
  123. package/lib/Utils/event-buffer.js +42 -0
  124. package/lib/Utils/event-buffer.js.map +1 -0
  125. package/lib/Utils/generics.d.ts +13 -0
  126. package/lib/Utils/generics.d.ts.map +1 -0
  127. package/lib/Utils/generics.js +40 -0
  128. package/lib/Utils/generics.js.map +1 -0
  129. package/lib/Utils/index.d.ts +10 -0
  130. package/lib/Utils/index.d.ts.map +1 -0
  131. package/lib/Utils/index.js +10 -0
  132. package/lib/Utils/index.js.map +1 -0
  133. package/lib/Utils/logger.d.ts +13 -0
  134. package/lib/Utils/logger.d.ts.map +1 -0
  135. package/lib/Utils/logger.js +3 -0
  136. package/lib/Utils/logger.js.map +1 -0
  137. package/lib/Utils/messages-media.d.ts +51 -0
  138. package/lib/Utils/messages-media.d.ts.map +1 -0
  139. package/lib/Utils/messages-media.js +223 -0
  140. package/lib/Utils/messages-media.js.map +1 -0
  141. package/lib/Utils/messages.d.ts +88 -0
  142. package/lib/Utils/messages.d.ts.map +1 -0
  143. package/lib/Utils/messages.js +730 -0
  144. package/lib/Utils/messages.js.map +1 -0
  145. package/lib/Utils/use-bridge-store.d.ts +13 -0
  146. package/lib/Utils/use-bridge-store.d.ts.map +1 -0
  147. package/lib/Utils/use-bridge-store.js +124 -0
  148. package/lib/Utils/use-bridge-store.js.map +1 -0
  149. package/lib/Utils/use-memory-store.d.ts +9 -0
  150. package/lib/Utils/use-memory-store.d.ts.map +1 -0
  151. package/lib/Utils/use-memory-store.js +23 -0
  152. package/lib/Utils/use-memory-store.js.map +1 -0
  153. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  154. package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
  155. package/lib/Utils/use-multi-file-auth-state.js +26 -0
  156. package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
  157. package/lib/Utils/wrap-legacy-store.d.ts +36 -0
  158. package/lib/Utils/wrap-legacy-store.d.ts.map +1 -0
  159. package/lib/Utils/wrap-legacy-store.js +708 -0
  160. package/lib/Utils/wrap-legacy-store.js.map +1 -0
  161. package/lib/WABinary/index.d.ts +2 -0
  162. package/lib/WABinary/index.d.ts.map +1 -0
  163. package/lib/WABinary/index.js +2 -0
  164. package/lib/WABinary/index.js.map +1 -0
  165. package/lib/WABinary/jid-utils.d.ts +20 -0
  166. package/lib/WABinary/jid-utils.d.ts.map +1 -0
  167. package/lib/WABinary/jid-utils.js +51 -0
  168. package/lib/WABinary/jid-utils.js.map +1 -0
  169. package/lib/index.d.ts +10 -0
  170. package/lib/index.d.ts.map +1 -0
  171. package/lib/index.js +9 -0
  172. package/lib/index.js.map +1 -0
  173. package/package.json +98 -0
@@ -0,0 +1,730 @@
1
+ import { Boom } from '@hapi/boom';
2
+ import { Buffer } from 'node:buffer';
3
+ import { Readable } from 'node:stream';
4
+ import { proto } from '../../WAProto/index.js';
5
+ import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
+ import { WAMessageStatus, WAProto } from '../Types/index.js';
7
+ import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
8
+ import { unixTimestampSeconds } from './generics.js';
9
+ import { generateThumbnail, getAudioDuration, getAudioWaveform, getStream, toBuffer } from './messages-media.js';
10
+ const MIMETYPE_MAP = {
11
+ image: 'image/jpeg',
12
+ video: 'video/mp4',
13
+ document: 'application/pdf',
14
+ audio: 'audio/ogg; codecs=opus',
15
+ sticker: 'image/webp',
16
+ 'product-catalog-image': 'image/jpeg'
17
+ };
18
+ const MessageTypeProto = {
19
+ image: WAProto.Message.ImageMessage,
20
+ video: WAProto.Message.VideoMessage,
21
+ audio: WAProto.Message.AudioMessage,
22
+ sticker: WAProto.Message.StickerMessage,
23
+ document: WAProto.Message.DocumentMessage
24
+ };
25
+ /**
26
+ * Uses a regex to test whether the string contains a URL, and returns the URL if it does.
27
+ * @param text eg. hello https://google.com
28
+ * @returns the URL, eg. https://google.com
29
+ */
30
+ export const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
31
+ export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
32
+ if (!getUrlInfo)
33
+ return;
34
+ const url = extractUrlFromText(text);
35
+ if (url) {
36
+ try {
37
+ const urlInfo = await getUrlInfo(url);
38
+ return urlInfo;
39
+ }
40
+ catch (error) {
41
+ // ignore if fails
42
+ logger?.warn({ trace: error.stack }, 'url generation failed');
43
+ }
44
+ }
45
+ };
46
+ const assertColor = (color) => {
47
+ if (typeof color === 'number') {
48
+ return color > 0 ? color : 0xffffffff + Number(color) + 1;
49
+ }
50
+ let hex = color.trim().replace('#', '');
51
+ if (hex.length <= 6) {
52
+ hex = 'FF' + hex.padStart(6, '0');
53
+ }
54
+ return parseInt(hex, 16);
55
+ };
56
+ export const prepareWAMessageMedia = async (message, options) => {
57
+ const logger = options.logger;
58
+ let mediaType;
59
+ for (const key of MEDIA_KEYS) {
60
+ if (key in message) {
61
+ mediaType = key;
62
+ }
63
+ }
64
+ if (!mediaType) {
65
+ throw new Boom('Invalid media type', { statusCode: 400 });
66
+ }
67
+ const messageRecord = message;
68
+ const uploadData = {
69
+ ...message,
70
+ media: messageRecord[mediaType]
71
+ };
72
+ delete uploadData[mediaType];
73
+ // check if cacheable + generate cache key
74
+ const cacheableKey = typeof uploadData.media === 'object' &&
75
+ 'url' in uploadData.media &&
76
+ !!uploadData.media.url &&
77
+ !!options.mediaCache &&
78
+ mediaType + ':' + uploadData.media.url.toString();
79
+ if (mediaType === 'document' && !uploadData.fileName) {
80
+ uploadData.fileName = 'file';
81
+ }
82
+ if (!uploadData.mimetype) {
83
+ uploadData.mimetype = MIMETYPE_MAP[mediaType];
84
+ }
85
+ if (cacheableKey) {
86
+ const mediaBuff = await options.mediaCache.get(cacheableKey);
87
+ if (mediaBuff) {
88
+ logger?.debug({ cacheableKey }, 'got media cache hit');
89
+ const obj = proto.Message.decode(mediaBuff);
90
+ const key = `${mediaType}Message`;
91
+ Object.assign(obj[key], { ...uploadData, media: undefined });
92
+ return obj;
93
+ }
94
+ }
95
+ // Read media into buffer — needed for both upload and metadata extraction
96
+ const { stream } = await getStream(uploadData.media, options.options);
97
+ const buffer = await toBuffer(stream);
98
+ const effectiveMediaType = options.mediaTypeOverride || mediaType;
99
+ // Run upload + metadata extraction in parallel
100
+ let uploadResult;
101
+ if (options.processMedia) {
102
+ // User-provided processing pipeline (streaming encrypt, custom thumbnails, etc.)
103
+ const { upload, metadata } = await options.processMedia(buffer, effectiveMediaType, options.waClient);
104
+ uploadResult = upload;
105
+ if (metadata) {
106
+ if (metadata.jpegThumbnail !== undefined)
107
+ uploadData.jpegThumbnail = metadata.jpegThumbnail;
108
+ if (metadata.width !== undefined)
109
+ uploadData.width = metadata.width;
110
+ if (metadata.height !== undefined)
111
+ uploadData.height = metadata.height;
112
+ if (metadata.seconds !== undefined)
113
+ uploadData.seconds = metadata.seconds;
114
+ if (metadata.waveform !== undefined)
115
+ uploadData.waveform = metadata.waveform;
116
+ }
117
+ }
118
+ else {
119
+ // Default: in-memory encrypt + upload via Rust, built-in metadata extraction
120
+ const [result] = await Promise.all([
121
+ options.waClient.uploadMedia(buffer, effectiveMediaType),
122
+ extractBuiltInMetadata(buffer, mediaType, uploadData, options)
123
+ ]);
124
+ uploadResult = result;
125
+ }
126
+ const obj = WAProto.Message.fromObject({
127
+ [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
128
+ url: uploadResult.url,
129
+ directPath: uploadResult.directPath,
130
+ mediaKey: uploadResult.mediaKey,
131
+ fileEncSha256: uploadResult.fileEncSha256,
132
+ fileSha256: uploadResult.fileSha256,
133
+ fileLength: uploadResult.fileLength,
134
+ mediaKeyTimestamp: unixTimestampSeconds(),
135
+ ...uploadData,
136
+ media: undefined
137
+ })
138
+ });
139
+ if (uploadData.ptv) {
140
+ obj.ptvMessage = obj.videoMessage;
141
+ delete obj.videoMessage;
142
+ }
143
+ if (obj.stickerMessage) {
144
+ obj.stickerMessage.stickerSentTs = Date.now();
145
+ }
146
+ if (cacheableKey) {
147
+ logger?.debug({ cacheableKey }, 'set cache');
148
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
149
+ }
150
+ return obj;
151
+ };
152
+ /** Extract metadata (thumbnail, duration, waveform) from buffer using built-in JS libraries */
153
+ async function extractBuiltInMetadata(buffer, mediaType, uploadData, options) {
154
+ try {
155
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined';
156
+ const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
157
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
158
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
159
+ if (requiresThumbnailComputation) {
160
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(buffer, mediaType, options);
161
+ uploadData.jpegThumbnail = thumbnail;
162
+ if (!uploadData.width && originalImageDimensions) {
163
+ uploadData.width = originalImageDimensions.width;
164
+ uploadData.height = originalImageDimensions.height;
165
+ }
166
+ }
167
+ if (requiresDurationComputation) {
168
+ uploadData.seconds = await getAudioDuration(buffer);
169
+ }
170
+ if (requiresWaveformProcessing) {
171
+ uploadData.waveform = await getAudioWaveform(buffer, options.logger);
172
+ }
173
+ if (requiresAudioBackground) {
174
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
175
+ }
176
+ }
177
+ catch (error) {
178
+ options.logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
179
+ }
180
+ }
181
+ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
182
+ ephemeralExpiration = ephemeralExpiration || 0;
183
+ const content = {
184
+ ephemeralMessage: {
185
+ message: {
186
+ protocolMessage: {
187
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
188
+ ephemeralExpiration
189
+ }
190
+ }
191
+ }
192
+ };
193
+ return WAProto.Message.fromObject(content);
194
+ };
195
+ /**
196
+ * Generate forwarded message content like WA does
197
+ * @param message the message to forward
198
+ * @param options.forceForward will show the message as forwarded even if it is from you
199
+ */
200
+ export const generateForwardMessageContent = (message, forceForward) => {
201
+ let content = message.message;
202
+ if (!content) {
203
+ throw new Boom('no content in message', { statusCode: 400 });
204
+ }
205
+ content = normalizeMessageContent(content);
206
+ // Shallow clone — only the inner message object gets modified (contextInfo)
207
+ content = { ...content };
208
+ let key = Object.keys(content)[0];
209
+ let score = content?.[key]?.contextInfo?.forwardingScore || 0;
210
+ score += message.key.fromMe && !forceForward ? 0 : 1;
211
+ if (key === 'conversation') {
212
+ content.extendedTextMessage = { text: content[key] };
213
+ delete content.conversation;
214
+ key = 'extendedTextMessage';
215
+ }
216
+ const key_ = content?.[key];
217
+ if (score > 0) {
218
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
219
+ }
220
+ else {
221
+ key_.contextInfo = {};
222
+ }
223
+ return content;
224
+ };
225
+ export const hasNonNullishProperty = (message, key) => {
226
+ return (typeof message === 'object' &&
227
+ message !== null &&
228
+ key in message &&
229
+ message[key] !== null &&
230
+ message[key] !== undefined);
231
+ };
232
+ function hasOptionalProperty(obj, key) {
233
+ return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
234
+ }
235
+ export const generateWAMessageContent = async (message, options) => {
236
+ var _a, _b;
237
+ let m = {};
238
+ if (hasNonNullishProperty(message, 'text')) {
239
+ const extContent = { text: message.text };
240
+ let urlInfo = message.linkPreview;
241
+ if (typeof urlInfo === 'undefined') {
242
+ urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
243
+ }
244
+ if (urlInfo) {
245
+ extContent.matchedText = urlInfo['matched-text'];
246
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail;
247
+ extContent.description = urlInfo.description;
248
+ extContent.title = urlInfo.title;
249
+ extContent.previewType = 0;
250
+ const img = urlInfo.highQualityThumbnail;
251
+ if (img) {
252
+ extContent.thumbnailDirectPath = img.directPath;
253
+ extContent.mediaKey = img.mediaKey;
254
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
255
+ extContent.thumbnailWidth = img.width;
256
+ extContent.thumbnailHeight = img.height;
257
+ extContent.thumbnailSha256 = img.fileSha256;
258
+ extContent.thumbnailEncSha256 = img.fileEncSha256;
259
+ }
260
+ }
261
+ if (options.backgroundColor) {
262
+ extContent.backgroundArgb = await assertColor(options.backgroundColor);
263
+ }
264
+ if (options.font) {
265
+ extContent.font = options.font;
266
+ }
267
+ m.extendedTextMessage = extContent;
268
+ }
269
+ else if (hasNonNullishProperty(message, 'contacts')) {
270
+ const contactLen = message.contacts.contacts.length;
271
+ if (!contactLen) {
272
+ throw new Boom('require atleast 1 contact', { statusCode: 400 });
273
+ }
274
+ if (contactLen === 1) {
275
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
276
+ }
277
+ else {
278
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
279
+ }
280
+ }
281
+ else if (hasNonNullishProperty(message, 'location')) {
282
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
283
+ }
284
+ else if (hasNonNullishProperty(message, 'react')) {
285
+ if (!message.react.senderTimestampMs) {
286
+ message.react.senderTimestampMs = Date.now();
287
+ }
288
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
289
+ }
290
+ else if (hasNonNullishProperty(message, 'delete')) {
291
+ m.protocolMessage = {
292
+ key: message.delete,
293
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
294
+ };
295
+ }
296
+ else if (hasNonNullishProperty(message, 'forward')) {
297
+ m = generateForwardMessageContent(message.forward, message.force);
298
+ }
299
+ else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
300
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
301
+ ? message.disappearingMessagesInChat
302
+ ? WA_DEFAULT_EPHEMERAL
303
+ : 0
304
+ : message.disappearingMessagesInChat;
305
+ m = prepareDisappearingMessageSettingContent(exp);
306
+ }
307
+ else if (hasNonNullishProperty(message, 'groupInvite')) {
308
+ m.groupInviteMessage = {};
309
+ m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
310
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
311
+ m.groupInviteMessage.caption = message.groupInvite.text;
312
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
313
+ m.groupInviteMessage.groupName = message.groupInvite.subject;
314
+ //TODO: use built-in interface and get disappearing mode info etc.
315
+ //TODO: cache / use store!?
316
+ if (options.getProfilePicUrl) {
317
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
318
+ if (pfpUrl) {
319
+ const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
320
+ if (resp.ok) {
321
+ const buf = Buffer.from(await resp.arrayBuffer());
322
+ m.groupInviteMessage.jpegThumbnail = buf;
323
+ }
324
+ }
325
+ }
326
+ }
327
+ else if (hasNonNullishProperty(message, 'pin')) {
328
+ m.pinInChatMessage = {};
329
+ m.messageContextInfo = {};
330
+ m.pinInChatMessage.key = message.pin;
331
+ m.pinInChatMessage.type = message.type;
332
+ m.pinInChatMessage.senderTimestampMs = Date.now();
333
+ m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
334
+ }
335
+ else if (hasNonNullishProperty(message, 'buttonReply')) {
336
+ switch (message.type) {
337
+ case 'template':
338
+ m.templateButtonReplyMessage = {
339
+ selectedDisplayText: message.buttonReply.displayText,
340
+ selectedId: message.buttonReply.id,
341
+ selectedIndex: message.buttonReply.index
342
+ };
343
+ break;
344
+ case 'plain':
345
+ m.buttonsResponseMessage = {
346
+ selectedButtonId: message.buttonReply.id,
347
+ selectedDisplayText: message.buttonReply.displayText,
348
+ type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
349
+ };
350
+ break;
351
+ }
352
+ }
353
+ else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
354
+ const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
355
+ m.ptvMessage = videoMessage;
356
+ }
357
+ else if (hasNonNullishProperty(message, 'product')) {
358
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options);
359
+ m.productMessage = WAProto.Message.ProductMessage.create({
360
+ ...message,
361
+ product: {
362
+ ...message.product,
363
+ productImage: imageMessage
364
+ }
365
+ });
366
+ }
367
+ else if (hasNonNullishProperty(message, 'listReply')) {
368
+ m.listResponseMessage = { ...message.listReply };
369
+ }
370
+ else if (hasNonNullishProperty(message, 'event')) {
371
+ m.eventMessage = {};
372
+ const startTime = Math.floor(message.event.startDate.getTime() / 1000);
373
+ if (message.event.call && options.getCallLink) {
374
+ const token = await options.getCallLink(message.event.call, { startTime });
375
+ m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
376
+ }
377
+ m.eventMessage.name = message.event.name;
378
+ m.eventMessage.description = message.event.description;
379
+ m.eventMessage.startTime = startTime;
380
+ m.eventMessage.endTime = message.event.endDate ? Math.floor(message.event.endDate.getTime() / 1000) : undefined;
381
+ m.eventMessage.isCanceled = message.event.isCancelled ?? false;
382
+ m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
383
+ m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
384
+ m.eventMessage.location = message.event.location;
385
+ }
386
+ else if (hasNonNullishProperty(message, 'poll')) {
387
+ (_a = message.poll).selectableCount || (_a.selectableCount = 0);
388
+ (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
389
+ if (!Array.isArray(message.poll.values)) {
390
+ throw new Boom('Invalid poll values', { statusCode: 400 });
391
+ }
392
+ if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
393
+ throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
394
+ statusCode: 400
395
+ });
396
+ }
397
+ const pollCreationMessage = {
398
+ name: message.poll.name,
399
+ selectableOptionsCount: message.poll.selectableCount,
400
+ options: message.poll.values.map(optionName => ({ optionName }))
401
+ };
402
+ if (message.poll.toAnnouncementGroup) {
403
+ // poll v2 is for community announcement groups (single select and multiple)
404
+ m.pollCreationMessageV2 = pollCreationMessage;
405
+ }
406
+ else {
407
+ if (message.poll.selectableCount === 1) {
408
+ //poll v3 is for single select polls
409
+ m.pollCreationMessageV3 = pollCreationMessage;
410
+ }
411
+ else {
412
+ // poll for multiple choice polls
413
+ m.pollCreationMessage = pollCreationMessage;
414
+ }
415
+ }
416
+ }
417
+ else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
418
+ m.protocolMessage = {
419
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
420
+ };
421
+ }
422
+ else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
423
+ m.requestPhoneNumberMessage = {};
424
+ }
425
+ else if (hasNonNullishProperty(message, 'limitSharing')) {
426
+ m.protocolMessage = {
427
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
428
+ limitSharing: {
429
+ sharingLimited: message.limitSharing === true,
430
+ trigger: 1,
431
+ limitSharingSettingTimestamp: Date.now(),
432
+ initiatedByMe: true
433
+ }
434
+ };
435
+ }
436
+ else {
437
+ m = await prepareWAMessageMedia(message, options);
438
+ }
439
+ if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
440
+ m = { viewOnceMessage: { message: m } };
441
+ }
442
+ if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
443
+ (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
444
+ const messageType = Object.keys(m)[0];
445
+ const key = m[messageType];
446
+ if (key && 'contextInfo' in key) {
447
+ key.contextInfo = key.contextInfo || {};
448
+ if (message.mentions?.length) {
449
+ key.contextInfo.mentionedJid = message.mentions;
450
+ }
451
+ if (message.mentionAll) {
452
+ key.contextInfo.nonJidMentions = 1;
453
+ }
454
+ }
455
+ }
456
+ if (hasOptionalProperty(message, 'edit')) {
457
+ m = {
458
+ protocolMessage: {
459
+ key: message.edit,
460
+ editedMessage: m,
461
+ timestampMs: Date.now(),
462
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
463
+ }
464
+ };
465
+ }
466
+ if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
467
+ const messageType = Object.keys(m)[0];
468
+ const key = m[messageType];
469
+ if (key) {
470
+ key.contextInfo = key.contextInfo ? Object.assign(key.contextInfo, message.contextInfo) : message.contextInfo;
471
+ }
472
+ }
473
+ return WAProto.Message.create(m);
474
+ };
475
+ export const generateWAMessageFromContent = (jid, message, options) => {
476
+ // set timestamp to now
477
+ // if not specified
478
+ if (!options.timestamp) {
479
+ options.timestamp = new Date();
480
+ }
481
+ const innerMessage = normalizeMessageContent(message);
482
+ const key = getContentType(innerMessage);
483
+ const timestamp = unixTimestampSeconds(options.timestamp);
484
+ const { quoted, userJid } = options;
485
+ if (quoted && !isJidNewsletter(jid)) {
486
+ const participant = quoted.key.fromMe
487
+ ? userJid // TODO: Add support for LIDs
488
+ : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
489
+ let quotedMsg = normalizeMessageContent(quoted.message);
490
+ const msgType = getContentType(quotedMsg);
491
+ // strip any redundant properties
492
+ quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
493
+ const quotedContent = quotedMsg[msgType];
494
+ if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
495
+ delete quotedContent.contextInfo;
496
+ }
497
+ const contextInfo = ('contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo) || {};
498
+ contextInfo.participant = jidNormalizedUser(participant);
499
+ contextInfo.stanzaId = quoted.key.id;
500
+ contextInfo.quotedMessage = quotedMsg;
501
+ // if a participant is quoted, then it must be a group
502
+ // hence, remoteJid of group must also be entered
503
+ if (jid !== quoted.key.remoteJid) {
504
+ contextInfo.remoteJid = quoted.key.remoteJid;
505
+ }
506
+ if (contextInfo && innerMessage[key]) {
507
+ /* @ts-ignore */
508
+ innerMessage[key].contextInfo = contextInfo;
509
+ }
510
+ }
511
+ if (
512
+ // if we want to send a disappearing message
513
+ !!options?.ephemeralExpiration &&
514
+ // and it's not a protocol message -- delete, toggle disappear message
515
+ key !== 'protocolMessage' &&
516
+ // already not converted to disappearing message
517
+ key !== 'ephemeralMessage' &&
518
+ // newsletters don't support ephemeral messages
519
+ !isJidNewsletter(jid)) {
520
+ /* @ts-ignore */
521
+ innerMessage[key].contextInfo = {
522
+ ...innerMessage[key].contextInfo,
523
+ expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
524
+ //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
525
+ };
526
+ }
527
+ message = WAProto.Message.create(message);
528
+ const messageJSON = {
529
+ key: {
530
+ remoteJid: jid,
531
+ fromMe: true,
532
+ id: options?.messageId || '' // Rust generates the real message ID
533
+ },
534
+ message: message,
535
+ messageTimestamp: timestamp,
536
+ messageStubParameters: [],
537
+ participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
538
+ status: WAMessageStatus.PENDING
539
+ };
540
+ return WAProto.WebMessageInfo.fromObject(messageJSON);
541
+ };
542
+ export const generateWAMessage = async (jid, content, options) => {
543
+ options.logger = options?.logger?.child({ msgId: options.messageId });
544
+ options.jid = jid;
545
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
546
+ };
547
+ /** Get the key to access the true type of content */
548
+ export const getContentType = (content) => {
549
+ if (content) {
550
+ const keys = Object.keys(content);
551
+ const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
552
+ return key;
553
+ }
554
+ };
555
+ /**
556
+ * Normalizes ephemeral, view once messages to regular message content
557
+ * Eg. image messages in ephemeral messages, in view once messages etc.
558
+ * @param content
559
+ * @returns
560
+ */
561
+ export const normalizeMessageContent = (content) => {
562
+ if (!content) {
563
+ return undefined;
564
+ }
565
+ // set max iterations to prevent an infinite loop
566
+ for (let i = 0; i < 5; i++) {
567
+ const inner = getFutureProofMessage(content);
568
+ if (!inner) {
569
+ break;
570
+ }
571
+ content = inner.message;
572
+ }
573
+ return content;
574
+ function getFutureProofMessage(message) {
575
+ return (message?.ephemeralMessage ||
576
+ message?.viewOnceMessage ||
577
+ message?.documentWithCaptionMessage ||
578
+ message?.viewOnceMessageV2 ||
579
+ message?.viewOnceMessageV2Extension ||
580
+ message?.editedMessage ||
581
+ message?.associatedChildMessage ||
582
+ message?.groupStatusMessage ||
583
+ message?.groupStatusMessageV2);
584
+ }
585
+ };
586
+ /**
587
+ * Extract the true message content from a message
588
+ * Eg. extracts the inner message from a disappearing message/view once message
589
+ */
590
+ export const extractMessageContent = (content) => {
591
+ const extractFromTemplateMessage = (msg) => {
592
+ if (msg.imageMessage) {
593
+ return { imageMessage: msg.imageMessage };
594
+ }
595
+ else if (msg.documentMessage) {
596
+ return { documentMessage: msg.documentMessage };
597
+ }
598
+ else if (msg.videoMessage) {
599
+ return { videoMessage: msg.videoMessage };
600
+ }
601
+ else if (msg.locationMessage) {
602
+ return { locationMessage: msg.locationMessage };
603
+ }
604
+ else {
605
+ return {
606
+ conversation: 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
607
+ };
608
+ }
609
+ };
610
+ content = normalizeMessageContent(content);
611
+ if (content?.buttonsMessage) {
612
+ return extractFromTemplateMessage(content.buttonsMessage);
613
+ }
614
+ if (content?.templateMessage?.hydratedFourRowTemplate) {
615
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate);
616
+ }
617
+ if (content?.templateMessage?.hydratedTemplate) {
618
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate);
619
+ }
620
+ if (content?.templateMessage?.fourRowTemplate) {
621
+ return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate);
622
+ }
623
+ return content;
624
+ };
625
+ /**
626
+ * Downloads the given message. Throws an error if it's not a media message.
627
+ *
628
+ * Uses the Rust bridge for download — provides CDN failover, automatic auth
629
+ * refresh on 401/404, HMAC-SHA256 integrity verification, and AES-256-CBC
630
+ * decryption. Requires `ctx.waClient` (the bridge client).
631
+ */
632
+ export const downloadMediaMessage = async (message, type, options, ctx) => {
633
+ return (await downloadMsg());
634
+ async function downloadMsg() {
635
+ const mContent = extractMessageContent(message.message);
636
+ if (!mContent) {
637
+ throw new Boom('No message present', { statusCode: 400, data: message });
638
+ }
639
+ const contentType = getContentType(mContent);
640
+ let mediaType = String(contentType)?.replace('Message', '');
641
+ const media = mContent[contentType];
642
+ if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
643
+ throw new Boom(`"${String(contentType)}" message is not a media message`);
644
+ }
645
+ if ('thumbnailDirectPath' in media && !('url' in media)) {
646
+ mediaType = 'thumbnail-link';
647
+ }
648
+ if (!('directPath' in media) || !media.directPath || !('mediaKey' in media) || !media.mediaKey) {
649
+ throw new Boom('Media message missing directPath or mediaKey', { statusCode: 400 });
650
+ }
651
+ const mediaObj = media;
652
+ if (!mediaObj.fileSha256 || !mediaObj.fileEncSha256) {
653
+ throw new Boom('Media message missing fileSha256 or fileEncSha256', { statusCode: 400 });
654
+ }
655
+ const args = [
656
+ mediaObj.directPath,
657
+ mediaObj.mediaKey,
658
+ mediaObj.fileSha256,
659
+ mediaObj.fileEncSha256,
660
+ Number(mediaObj.fileLength || 0),
661
+ mediaType
662
+ ];
663
+ if (type === 'buffer') {
664
+ const data = await ctx.waClient.downloadMedia(...args);
665
+ return Buffer.from(data);
666
+ }
667
+ // Stream mode: Web ReadableStream from Rust → Node.js Readable
668
+ const webStream = ctx.waClient.downloadMediaStream(...args);
669
+ return Readable.fromWeb(webStream);
670
+ }
671
+ };
672
+ /**
673
+ * Aggregate votes from a poll message.
674
+ *
675
+ * Takes a poll creation message and its accumulated poll updates (from
676
+ * `messages.update` events) and returns the vote tally per option.
677
+ *
678
+ * `pollUpdates` should contain pre-decrypted votes where `vote.selectedOptions`
679
+ * are the SHA-256 hashes of the voted option names.
680
+ *
681
+ * @example
682
+ * ```ts
683
+ * sock.ev.on('messages.update', event => {
684
+ * for (const { key, update } of event) {
685
+ * if (update.pollUpdates) {
686
+ * const pollMsg = await getMessageFromStore(key)
687
+ * const votes = getAggregateVotesInPollMessage({
688
+ * message: pollMsg.message,
689
+ * pollUpdates: pollMsg.pollUpdates
690
+ * })
691
+ * console.log(votes) // [{ name: 'Yes', voters: ['jid1'] }, ...]
692
+ * }
693
+ * }
694
+ * })
695
+ * ```
696
+ */
697
+ export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
698
+ const opts = message?.pollCreationMessage?.options ||
699
+ message?.pollCreationMessageV2?.options ||
700
+ message?.pollCreationMessageV3?.options ||
701
+ [];
702
+ // Build hash→name lookup: SHA-256(optionName) hex → optionName
703
+ // Use synchronous hash via crypto.subtle is async, so we pre-build
704
+ // using the raw bytes from selectedOptions and match by position.
705
+ const voteHashMap = {};
706
+ for (const opt of opts) {
707
+ const name = opt.optionName || '';
708
+ voteHashMap[name] = { name, voters: [] };
709
+ }
710
+ for (const update of pollUpdates || []) {
711
+ const { vote } = update;
712
+ if (!vote?.selectedOptions?.length)
713
+ continue;
714
+ const voter = update.pollUpdateMessageKey ? getKeyAuthor(update.pollUpdateMessageKey, meId) : 'unknown';
715
+ for (const optionHash of vote.selectedOptions) {
716
+ const hashHex = Buffer.from(optionHash).toString('hex');
717
+ // Try to find the matching option name
718
+ if (!voteHashMap[hashHex]) {
719
+ voteHashMap[hashHex] = { name: hashHex, voters: [] };
720
+ }
721
+ voteHashMap[hashHex].voters.push(voter);
722
+ }
723
+ }
724
+ return Object.values(voteHashMap);
725
+ }
726
+ /** Get the author of a message key */
727
+ function getKeyAuthor(key, meId) {
728
+ return (key.fromMe ? meId : key.participant || key.remoteJid) || 'unknown';
729
+ }
730
+ //# sourceMappingURL=messages.js.map