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.
- package/LICENSE +1 -1
- package/README.md +454 -29
- package/engine-requirements.js +4 -13
- package/lib/Defaults/constants.js +74 -0
- package/lib/Defaults/media.js +48 -0
- package/lib/Socket/chats.js +15 -28
- package/lib/Socket/{dugong.d.ts → luxu.d.ts} +51 -37
- package/lib/Socket/luxu.js +591 -0
- package/lib/Socket/messages-send.js +121 -100
- package/lib/Socket/newsletter.js +132 -96
- package/lib/Socket/socket.js +10 -16
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/messages-media.js +835 -545
- package/lib/Utils/messages.js +1571 -497
- package/lib/WABinary/jid-utils.js +3 -0
- package/lib/index.js +4 -10
- package/lib/temp +1 -0
- package/package.json +12 -21
- package/lib/@Danu'Zz +0 -1
- package/lib/Socket/dugong.js +0 -484
- package/lib/WAUSync/index.d.ts +0 -3
package/lib/Utils/messages.js
CHANGED
|
@@ -1,547 +1,1480 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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':
|
|
28
|
-
'video':
|
|
29
|
-
'audio':
|
|
30
|
-
'sticker':
|
|
31
|
-
'document':
|
|
32
|
-
}
|
|
33
|
-
|
|
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) =>
|
|
40
|
-
|
|
63
|
+
const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0]
|
|
64
|
+
|
|
41
65
|
const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
|
|
42
|
-
const url =
|
|
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
|
-
|
|
49
|
-
|
|
73
|
+
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger?.warn({ trace: error.stack }, 'url generation failed')
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
78
|
-
statusCode: 400
|
|
79
|
-
});
|
|
109
|
+
throw new Boom('Invalid media type', { statusCode: 400 })
|
|
80
110
|
}
|
|
81
111
|
|
|
82
|
-
|
|
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
|
-
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
delete uploadData[mediaType]
|
|
151
|
+
|
|
152
|
+
// check if cacheable + generate cache key
|
|
87
153
|
const cacheableKey = typeof uploadData.media === 'object' &&
|
|
88
|
-
|
|
154
|
+
'url' in uploadData.media &&
|
|
89
155
|
!!uploadData.media.url &&
|
|
90
|
-
!!options.mediaCache &&
|
|
91
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
-
|
|
120
|
-
logger,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
|
132
|
-
|
|
133
|
-
const
|
|
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(
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
143
|
-
|
|
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
|
|
254
|
+
uploadData.width = originalImageDimensions.width
|
|
255
|
+
uploadData.height = originalImageDimensions.height
|
|
256
|
+
logger?.debug('set dimensions')
|
|
148
257
|
}
|
|
149
|
-
logger
|
|
258
|
+
logger?.debug('generated thumbnail')
|
|
150
259
|
}
|
|
260
|
+
|
|
151
261
|
if (requiresDurationComputation) {
|
|
152
|
-
uploadData.seconds = await
|
|
153
|
-
logger
|
|
262
|
+
uploadData.seconds = await getAudioDuration(originalFilePath)
|
|
263
|
+
logger?.debug('computed audio duration')
|
|
154
264
|
}
|
|
265
|
+
|
|
155
266
|
if (requiresWaveformProcessing) {
|
|
156
|
-
uploadData.waveform = await
|
|
157
|
-
logger
|
|
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
|
|
272
|
+
uploadData.backgroundArgb = await assertColor(options.backgroundColor)
|
|
273
|
+
logger?.debug('computed backgroundColor audio status')
|
|
162
274
|
}
|
|
163
275
|
}
|
|
164
276
|
catch (error) {
|
|
165
|
-
logger
|
|
277
|
+
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
|
166
278
|
}
|
|
167
|
-
})()
|
|
168
|
-
])
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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 =
|
|
295
|
+
const obj = WAProto.Message.fromObject({
|
|
181
296
|
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
|
182
|
-
url:
|
|
297
|
+
url: mediaUrl,
|
|
183
298
|
directPath,
|
|
184
|
-
mediaKey
|
|
185
|
-
fileEncSha256
|
|
299
|
+
mediaKey,
|
|
300
|
+
fileEncSha256,
|
|
186
301
|
fileSha256,
|
|
187
302
|
fileLength,
|
|
188
|
-
mediaKeyTimestamp:
|
|
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
|
|
201
|
-
options.mediaCache.set(cacheableKey,
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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:
|
|
214
|
-
ephemeralExpiration
|
|
403
|
+
type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
404
|
+
ephemeralExpiration: expiration ? expiration : 0
|
|
215
405
|
}
|
|
216
406
|
}
|
|
217
407
|
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
229
|
-
|
|
419
|
+
let content = message.message
|
|
420
|
+
|
|
230
421
|
if (!content) {
|
|
231
|
-
throw new
|
|
422
|
+
throw new Boom('no content in message', { statusCode: 400 })
|
|
232
423
|
}
|
|
424
|
+
|
|
233
425
|
// hacky copy
|
|
234
|
-
content =
|
|
235
|
-
content =
|
|
236
|
-
|
|
237
|
-
let
|
|
238
|
-
score
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
let urlInfo = message.linkPreview
|
|
260
|
-
|
|
261
|
-
|
|
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[
|
|
265
|
-
extContent.matchedText = urlInfo[
|
|
266
|
-
extContent.jpegThumbnail = urlInfo.jpegThumbnail
|
|
267
|
-
extContent.description = urlInfo.description
|
|
268
|
-
extContent.title = urlInfo.title
|
|
269
|
-
extContent.previewType = 0
|
|
270
|
-
|
|
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(
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
else if (
|
|
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
|
|
528
|
+
throw new Boom("require atleast 1 contact", { statusCode: 400 })
|
|
293
529
|
}
|
|
530
|
+
|
|
294
531
|
if (contactLen === 1) {
|
|
295
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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:
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
else if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
886
|
+
|
|
887
|
+
if (hasNonNullishProperty(message, "buttonReply")) {
|
|
326
888
|
switch (message.type) {
|
|
327
|
-
case
|
|
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
|
|
906
|
+
}
|
|
907
|
+
break
|
|
908
|
+
case "plain":
|
|
335
909
|
m.buttonsResponseMessage = {
|
|
336
910
|
selectedButtonId: message.buttonReply.id,
|
|
337
911
|
selectedDisplayText: message.buttonReply.displayText,
|
|
338
|
-
type:
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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 => ({
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
985
|
+
|
|
986
|
+
if (hasNonNullishProperty(message, "title")) {
|
|
987
|
+
buttonsMessage.text = message.title
|
|
988
|
+
buttonsMessage.headerType =
|
|
989
|
+
proto.Message.ButtonsMessage.HeaderType.TEXT
|
|
404
990
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
1008
|
+
|
|
1009
|
+
Object.assign(msg, m)
|
|
419
1010
|
}
|
|
420
|
-
|
|
421
|
-
|
|
1011
|
+
|
|
1012
|
+
if (hasNonNullishProperty(message, "footer")) {
|
|
1013
|
+
hydratedTemplate.hydratedFooterText = message.footer
|
|
422
1014
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
m[messageType]
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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
|
-
|
|
506
|
-
|
|
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
|
|
510
|
-
|
|
511
|
-
};
|
|
1441
|
+
expiration: options.ephemeralExpiration ? options.ephemeralExpiration : 0
|
|
1442
|
+
}
|
|
512
1443
|
}
|
|
513
|
-
|
|
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:
|
|
1451
|
+
id: options?.messageId || generateMessageID()
|
|
519
1452
|
},
|
|
520
1453
|
message: message,
|
|
521
1454
|
messageTimestamp: timestamp,
|
|
522
1455
|
messageStubParameters: [],
|
|
523
|
-
participant:
|
|
524
|
-
status:
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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.
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1494
|
+
|
|
1495
|
+
content = inner.message
|
|
562
1496
|
}
|
|
563
|
-
|
|
1497
|
+
|
|
1498
|
+
return content
|
|
1499
|
+
|
|
564
1500
|
function getFutureProofMessage(message) {
|
|
565
|
-
return (
|
|
566
|
-
|
|
567
|
-
|| (message
|
|
568
|
-
|| (message
|
|
569
|
-
|| (message
|
|
570
|
-
|| (message
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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
|
-
|
|
588
|
-
|
|
1547
|
+
|
|
1548
|
+
else if (header ? msg.header?.locationMessage : msg.locationMessage) {
|
|
1549
|
+
return { locationMessage: header ? msg.header.locationMessage : msg.locationMessage }
|
|
589
1550
|
}
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
-
|
|
609
|
-
|
|
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
|
-
|
|
612
|
-
|
|
1582
|
+
|
|
1583
|
+
if (content?.templateMessage?.hydratedTemplate) {
|
|
1584
|
+
return extractFromButtonsMessage(content?.templateMessage?.hydratedTemplate)
|
|
613
1585
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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' :
|
|
621
|
-
|
|
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
|
-
|
|
1615
|
+
}
|
|
1616
|
+
|
|
634
1617
|
/** Update the message with a new reaction */
|
|
635
1618
|
const updateMessageWithReaction = (msg, reaction) => {
|
|
636
|
-
const authorID =
|
|
1619
|
+
const authorID = getKeyAuthor(reaction.key)
|
|
637
1620
|
const reactions = (msg.reactions || [])
|
|
638
|
-
.filter(r =>
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
msg.reactions = reactions
|
|
643
|
-
}
|
|
644
|
-
|
|
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
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
665
|
-
|
|
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 =
|
|
1663
|
+
const hash = sha256(Buffer.from(opt.optionName || '')).toString()
|
|
668
1664
|
acc[hash] = {
|
|
669
1665
|
name: opt.optionName || '',
|
|
670
1666
|
voters: []
|
|
671
|
-
}
|
|
672
|
-
|
|
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
|
-
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
data = voteHashMap[hash]
|
|
688
1690
|
}
|
|
689
|
-
|
|
1691
|
+
|
|
1692
|
+
voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId))
|
|
690
1693
|
}
|
|
691
1694
|
}
|
|
692
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1742
|
+
|
|
1743
|
+
keyMap[uqKey].messageIds.push(id)
|
|
709
1744
|
}
|
|
710
1745
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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 =
|
|
1775
|
+
const mContent = extractMessageContent(message.message)
|
|
1776
|
+
|
|
739
1777
|
if (!mContent) {
|
|
740
|
-
throw new
|
|
1778
|
+
throw new Boom('No message present', { statusCode: 400, data: message })
|
|
741
1779
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
|
1788
|
+
throw new Boom(`"${contentType}" message is not a media message`)
|
|
747
1789
|
}
|
|
748
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
|
1828
|
+
throw new Boom('given message is not a media message', { statusCode: 400, data: content });
|
|
781
1829
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
+
}
|