dtec-bail 1.0.7 → 1.0.9

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