xmd-baileys 1.0.0
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 +21 -0
- package/README.md +10 -0
- package/WAProto/index.js +169661 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/baileys-version.json +3 -0
- package/lib/Defaults/constants.js +74 -0
- package/lib/Defaults/index.d.ts +53 -0
- package/lib/Defaults/index.js +147 -0
- package/lib/Defaults/media.js +48 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +15 -0
- package/lib/Signal/Group/group-session-builder.d.ts +14 -0
- package/lib/Signal/Group/group-session-builder.js +64 -0
- package/lib/Signal/Group/group_cipher.d.ts +17 -0
- package/lib/Signal/Group/group_cipher.js +96 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +57 -0
- package/lib/Signal/Group/keyhelper.d.ts +10 -0
- package/lib/Signal/Group/keyhelper.js +55 -0
- package/lib/Signal/Group/queue-job.d.ts +1 -0
- package/lib/Signal/Group/queue-job.js +57 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +34 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +69 -0
- package/lib/Signal/Group/sender-key-name.d.ts +17 -0
- package/lib/Signal/Group/sender-key-name.js +51 -0
- package/lib/Signal/Group/sender-key-record.d.ts +30 -0
- package/lib/Signal/Group/sender-key-record.js +53 -0
- package/lib/Signal/Group/sender-key-state.d.ts +38 -0
- package/lib/Signal/Group/sender-key-state.js +99 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/lib/Signal/Group/sender-message-key.js +29 -0
- package/lib/Signal/Group/tmp +1 -0
- package/lib/Signal/libsignal.d.ts +3 -0
- package/lib/Signal/libsignal.js +174 -0
- package/lib/Socket/Client/abstract-socket-client.d.ts +17 -0
- package/lib/Socket/Client/abstract-socket-client.js +13 -0
- package/lib/Socket/Client/index.d.ts +3 -0
- package/lib/Socket/Client/index.js +19 -0
- package/lib/Socket/Client/mobile-socket-client.d.ts +13 -0
- package/lib/Socket/Client/mobile-socket-client.js +65 -0
- package/lib/Socket/Client/tmp +1 -0
- package/lib/Socket/Client/web-socket-client.d.ts +12 -0
- package/lib/Socket/Client/web-socket-client.js +62 -0
- package/lib/Socket/business.d.ts +171 -0
- package/lib/Socket/business.js +260 -0
- package/lib/Socket/chats.d.ts +267 -0
- package/lib/Socket/chats.js +970 -0
- package/lib/Socket/groups.d.ts +115 -0
- package/lib/Socket/groups.js +317 -0
- package/lib/Socket/index.d.ts +173 -0
- package/lib/Socket/index.js +11 -0
- package/lib/Socket/luxu.d.ts +268 -0
- package/lib/Socket/luxu.js +591 -0
- package/lib/Socket/messages-recv.d.ts +161 -0
- package/lib/Socket/messages-recv.js +1110 -0
- package/lib/Socket/messages-send.d.ts +149 -0
- package/lib/Socket/messages-send.js +912 -0
- package/lib/Socket/newsletter.d.ts +134 -0
- package/lib/Socket/newsletter.js +315 -0
- package/lib/Socket/registration.d.ts +267 -0
- package/lib/Socket/registration.js +166 -0
- package/lib/Socket/socket.d.ts +43 -0
- package/lib/Socket/socket.js +665 -0
- package/lib/Socket/usync.d.ts +36 -0
- package/lib/Socket/usync.js +70 -0
- package/lib/Store/index.d.ts +3 -0
- package/lib/Store/index.js +10 -0
- package/lib/Store/make-cache-manager-store.d.ts +13 -0
- package/lib/Store/make-cache-manager-store.js +83 -0
- package/lib/Store/make-in-memory-store.d.ts +118 -0
- package/lib/Store/make-in-memory-store.js +427 -0
- package/lib/Store/make-ordered-dictionary.d.ts +13 -0
- package/lib/Store/make-ordered-dictionary.js +81 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Store/object-repository.js +27 -0
- package/lib/Store/tmp +1 -0
- package/lib/Types/Auth.d.ts +110 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Call.d.ts +13 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.d.ts +102 -0
- package/lib/Types/Chat.js +4 -0
- package/lib/Types/Contact.d.ts +19 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.d.ts +157 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.d.ts +55 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.d.ts +35 -0
- package/lib/Types/Label.js +27 -0
- package/lib/Types/LabelAssociation.d.ts +29 -0
- package/lib/Types/LabelAssociation.js +9 -0
- package/lib/Types/Message.d.ts +273 -0
- package/lib/Types/Message.js +9 -0
- package/lib/Types/Newsletter.d.ts +103 -0
- package/lib/Types/Newsletter.js +38 -0
- package/lib/Types/Product.d.ts +78 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/Signal.d.ts +57 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.d.ts +111 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.d.ts +27 -0
- package/lib/Types/State.js +2 -0
- package/lib/Types/USync.d.ts +25 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.d.ts +57 -0
- package/lib/Types/index.js +42 -0
- package/lib/Types/tmp +1 -0
- package/lib/Utils/auth-utils.d.ts +18 -0
- package/lib/Utils/auth-utils.js +206 -0
- package/lib/Utils/baileys-event-stream.d.ts +16 -0
- package/lib/Utils/baileys-event-stream.js +63 -0
- package/lib/Utils/business.d.ts +22 -0
- package/lib/Utils/business.js +234 -0
- package/lib/Utils/chat-utils.d.ts +71 -0
- package/lib/Utils/chat-utils.js +729 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +151 -0
- package/lib/Utils/decode-wa-message.d.ts +19 -0
- package/lib/Utils/decode-wa-message.js +198 -0
- package/lib/Utils/event-buffer.d.ts +35 -0
- package/lib/Utils/event-buffer.js +514 -0
- package/lib/Utils/generics.d.ts +92 -0
- package/lib/Utils/generics.js +423 -0
- package/lib/Utils/history.d.ts +15 -0
- package/lib/Utils/history.js +96 -0
- package/lib/Utils/index.d.ts +17 -0
- package/lib/Utils/index.js +33 -0
- package/lib/Utils/link-preview.d.ts +21 -0
- package/lib/Utils/link-preview.js +93 -0
- package/lib/Utils/logger.d.ts +4 -0
- package/lib/Utils/logger.js +7 -0
- package/lib/Utils/lt-hash.d.ts +12 -0
- package/lib/Utils/lt-hash.js +51 -0
- package/lib/Utils/make-mutex.d.ts +7 -0
- package/lib/Utils/make-mutex.js +43 -0
- package/lib/Utils/messages-media.d.ts +116 -0
- package/lib/Utils/messages-media.js +1109 -0
- package/lib/Utils/messages.d.ts +77 -0
- package/lib/Utils/messages.js +1858 -0
- package/lib/Utils/noise-handler.d.ts +21 -0
- package/lib/Utils/noise-handler.js +155 -0
- package/lib/Utils/process-message.d.ts +41 -0
- package/lib/Utils/process-message.js +321 -0
- package/lib/Utils/signal.d.ts +32 -0
- package/lib/Utils/signal.js +153 -0
- package/lib/Utils/tmp +1 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
- package/lib/Utils/use-multi-file-auth-state.js +119 -0
- package/lib/Utils/validate-connection.d.ts +11 -0
- package/lib/Utils/validate-connection.js +229 -0
- package/lib/WABinary/constants.d.ts +30 -0
- package/lib/WABinary/constants.js +40 -0
- package/lib/WABinary/decode.d.ts +7 -0
- package/lib/WABinary/decode.js +252 -0
- package/lib/WABinary/encode.d.ts +3 -0
- package/lib/WABinary/encode.js +265 -0
- package/lib/WABinary/generic-utils.d.ts +17 -0
- package/lib/WABinary/generic-utils.js +198 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +21 -0
- package/lib/WABinary/jid-utils.d.ts +31 -0
- package/lib/WABinary/jid-utils.js +65 -0
- package/lib/WABinary/tmp +1 -0
- package/lib/WABinary/types.d.ts +18 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.d.ts +17 -0
- package/lib/WAM/BinaryInfo.js +13 -0
- package/lib/WAM/constants.d.ts +38 -0
- package/lib/WAM/constants.js +15350 -0
- package/lib/WAM/encode.d.ts +3 -0
- package/lib/WAM/encode.js +155 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +19 -0
- package/lib/WAM/tmp +1 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +53 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +8 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -0
- package/lib/WAUSync/Protocols/index.d.ts +4 -0
- package/lib/WAUSync/Protocols/index.js +20 -0
- package/lib/WAUSync/Protocols/tmp +1 -0
- package/lib/WAUSync/USyncQuery.d.ts +28 -0
- package/lib/WAUSync/USyncQuery.js +89 -0
- package/lib/WAUSync/USyncUser.d.ts +12 -0
- package/lib/WAUSync/USyncUser.js +26 -0
- package/lib/WAUSync/index.js +19 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +39 -0
- package/lib/temp +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1858 @@
|
|
|
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
|
+
|
|
41
|
+
const MIMETYPE_MAP = {
|
|
42
|
+
image: 'image/jpeg',
|
|
43
|
+
video: 'video/mp4',
|
|
44
|
+
document: 'application/pdf',
|
|
45
|
+
audio: 'audio/ogg codecs=opus',
|
|
46
|
+
sticker: 'image/webp',
|
|
47
|
+
'product-catalog-image': 'image/jpeg'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const MessageTypeProto = {
|
|
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
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Uses a regex to test whether the string contains a URL, and returns the URL if it does.
|
|
60
|
+
* @param text eg. hello https://google.com
|
|
61
|
+
* @returns the URL, eg. https://google.com
|
|
62
|
+
*/
|
|
63
|
+
const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0]
|
|
64
|
+
|
|
65
|
+
const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
|
|
66
|
+
const url = extractUrlFromText(text)
|
|
67
|
+
|
|
68
|
+
if (!!getUrlInfo && url) {
|
|
69
|
+
try {
|
|
70
|
+
const urlInfo = await getUrlInfo(url)
|
|
71
|
+
return urlInfo
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger?.warn({ trace: error.stack }, 'url generation failed')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const assertColor = async (color) => {
|
|
81
|
+
let assertedColor
|
|
82
|
+
|
|
83
|
+
if (typeof color === 'number') {
|
|
84
|
+
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
else {
|
|
88
|
+
let hex = color.trim().replace('#', '')
|
|
89
|
+
if (hex.length <= 6) {
|
|
90
|
+
hex = 'FF' + hex.padStart(6, '0')
|
|
91
|
+
}
|
|
92
|
+
assertedColor = parseInt(hex, 16)
|
|
93
|
+
return assertedColor
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const prepareWAMessageMedia = async (message, options) => {
|
|
98
|
+
const logger = options.logger
|
|
99
|
+
|
|
100
|
+
let mediaType
|
|
101
|
+
|
|
102
|
+
for (const key of MEDIA_KEYS) {
|
|
103
|
+
if (key in message) {
|
|
104
|
+
mediaType = key
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!mediaType) {
|
|
109
|
+
throw new Boom('Invalid media type', { statusCode: 400 })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const uploadData = {
|
|
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: "120363402220977044@newsletter",
|
|
139
|
+
serverMessageId: 0,
|
|
140
|
+
newsletterName: "Queen Jusmy Md",
|
|
141
|
+
contentType: "UPDATE",
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}),
|
|
146
|
+
media: message[mediaType]
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
delete uploadData[mediaType]
|
|
151
|
+
|
|
152
|
+
// check if cacheable + generate cache key
|
|
153
|
+
const cacheableKey = typeof uploadData.media === 'object' &&
|
|
154
|
+
'url' in uploadData.media &&
|
|
155
|
+
!!uploadData.media.url &&
|
|
156
|
+
!!options.mediaCache &&
|
|
157
|
+
mediaType + ':' + uploadData.media.url.toString()
|
|
158
|
+
|
|
159
|
+
if (mediaType === 'document' && !uploadData.fileName) {
|
|
160
|
+
uploadData.fileName = 'file'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!uploadData.mimetype) {
|
|
164
|
+
uploadData.mimetype = MIMETYPE_MAP[mediaType]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (cacheableKey) {
|
|
168
|
+
const mediaBuff = await options.mediaCache.get(cacheableKey)
|
|
169
|
+
|
|
170
|
+
if (mediaBuff) {
|
|
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
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const isNewsletter = !!options.jid && isJidNewsletter(options.jid)
|
|
183
|
+
|
|
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
|
+
}
|
|
212
|
+
|
|
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
|
|
223
|
+
}
|
|
224
|
+
|
|
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([
|
|
237
|
+
(async () => {
|
|
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
|
|
245
|
+
})(),
|
|
246
|
+
(async () => {
|
|
247
|
+
try {
|
|
248
|
+
if (requiresThumbnailComputation) {
|
|
249
|
+
const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options)
|
|
250
|
+
|
|
251
|
+
uploadData.jpegThumbnail = thumbnail
|
|
252
|
+
|
|
253
|
+
if (!uploadData.width && originalImageDimensions) {
|
|
254
|
+
uploadData.width = originalImageDimensions.width
|
|
255
|
+
uploadData.height = originalImageDimensions.height
|
|
256
|
+
logger?.debug('set dimensions')
|
|
257
|
+
}
|
|
258
|
+
logger?.debug('generated thumbnail')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (requiresDurationComputation) {
|
|
262
|
+
uploadData.seconds = await getAudioDuration(originalFilePath)
|
|
263
|
+
logger?.debug('computed audio duration')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (requiresWaveformProcessing) {
|
|
267
|
+
uploadData.waveform = await getAudioWaveform(originalFilePath, logger)
|
|
268
|
+
logger?.debug('processed waveform')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (requiresAudioBackground) {
|
|
272
|
+
uploadData.backgroundArgb = await assertColor(options.backgroundColor)
|
|
273
|
+
logger?.debug('computed backgroundColor audio status')
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
|
278
|
+
}
|
|
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')
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
logger?.warn('failed to remove tmp file')
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const obj = WAProto.Message.fromObject({
|
|
296
|
+
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
|
297
|
+
url: mediaUrl,
|
|
298
|
+
directPath,
|
|
299
|
+
mediaKey,
|
|
300
|
+
fileEncSha256,
|
|
301
|
+
fileSha256,
|
|
302
|
+
fileLength,
|
|
303
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
304
|
+
...uploadData,
|
|
305
|
+
media: undefined
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
if (uploadData.ptv) {
|
|
310
|
+
obj.ptvMessage = obj.videoMessage
|
|
311
|
+
delete obj.videoMessage
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (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.")
|
|
329
|
+
}
|
|
330
|
+
|
|
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) => {
|
|
399
|
+
const content = {
|
|
400
|
+
ephemeralMessage: {
|
|
401
|
+
message: {
|
|
402
|
+
protocolMessage: {
|
|
403
|
+
type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
404
|
+
ephemeralExpiration: expiration ? expiration : 0
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return WAProto.Message.fromObject(content)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate forwarded message content like WA does
|
|
415
|
+
* @param message the message to forward
|
|
416
|
+
* @param options.forceForward will show the message as forwarded even if it is from you
|
|
417
|
+
*/
|
|
418
|
+
const generateForwardMessageContent = (message, forceForward) => {
|
|
419
|
+
let content = message.message
|
|
420
|
+
|
|
421
|
+
if (!content) {
|
|
422
|
+
throw new Boom('no content in message', { statusCode: 400 })
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// hacky copy
|
|
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
|
+
|
|
434
|
+
if (key === 'conversation') {
|
|
435
|
+
content.extendedTextMessage = { text: content[key] }
|
|
436
|
+
delete content.conversation
|
|
437
|
+
key = 'extendedTextMessage'
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (score > 0) {
|
|
441
|
+
content[key].contextInfo = { forwardingScore: score, isForwarded: true }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
else {
|
|
445
|
+
content[key].contextInfo = {}
|
|
446
|
+
}
|
|
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
|
+
|
|
470
|
+
const generateWAMessageContent = async (message, options) => {
|
|
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
|
+
)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (urlInfo) {
|
|
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
|
+
|
|
496
|
+
if (img) {
|
|
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
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (options.backgroundColor) {
|
|
508
|
+
extContent.backgroundArgb = await assertColor(
|
|
509
|
+
options.backgroundColor
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (options.textColor) {
|
|
514
|
+
extContent.textArgb = await assertColor(options.textColor)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (options.font) {
|
|
518
|
+
extContent.font = options.font
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
m.extendedTextMessage = extContent
|
|
522
|
+
} else if (hasNonNullishProperty(message, "contacts")) {
|
|
523
|
+
const contactLen = message.contacts.contacts.length
|
|
524
|
+
|
|
525
|
+
let contactMessage
|
|
526
|
+
|
|
527
|
+
if (!contactLen) {
|
|
528
|
+
throw new Boom("require atleast 1 contact", { statusCode: 400 })
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (contactLen === 1) {
|
|
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
|
+
}
|
|
543
|
+
}
|
|
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 })
|
|
553
|
+
}
|
|
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")) {
|
|
589
|
+
if (!message.react.senderTimestampMs) {
|
|
590
|
+
message.react.senderTimestampMs = Date.now()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(
|
|
594
|
+
message.react
|
|
595
|
+
)
|
|
596
|
+
} else if (hasNonNullishProperty(message, "delete")) {
|
|
597
|
+
m.protocolMessage = {
|
|
598
|
+
key: message.delete,
|
|
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)
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (hasNonNullishProperty(message, "buttonReply")) {
|
|
888
|
+
switch (message.type) {
|
|
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":
|
|
902
|
+
m.templateButtonReplyMessage = {
|
|
903
|
+
selectedDisplayText: message.buttonReply.displayText,
|
|
904
|
+
selectedId: message.buttonReply.id,
|
|
905
|
+
selectedIndex: message.buttonReply.index,
|
|
906
|
+
}
|
|
907
|
+
break
|
|
908
|
+
case "plain":
|
|
909
|
+
m.buttonsResponseMessage = {
|
|
910
|
+
selectedButtonId: message.buttonReply.id,
|
|
911
|
+
selectedDisplayText: message.buttonReply.displayText,
|
|
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
|
|
931
|
+
}
|
|
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,
|
|
940
|
+
}
|
|
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,
|
|
960
|
+
}
|
|
961
|
+
} else if (hasNonNullishProperty(message, "buttons")) {
|
|
962
|
+
const buttonsMessage = {
|
|
963
|
+
buttons: message.buttons.map((b) => ({
|
|
964
|
+
...b,
|
|
965
|
+
type: proto.Message.ButtonsMessage.Button.Type.RESPONSE,
|
|
966
|
+
})),
|
|
967
|
+
}
|
|
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
|
|
976
|
+
}
|
|
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)
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (hasNonNullishProperty(message, "title")) {
|
|
987
|
+
buttonsMessage.text = message.title
|
|
988
|
+
buttonsMessage.headerType =
|
|
989
|
+
proto.Message.ButtonsMessage.HeaderType.TEXT
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (hasNonNullishProperty(message, "footer")) {
|
|
993
|
+
buttonsMessage.footerText = message.footer
|
|
994
|
+
}
|
|
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
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
Object.assign(msg, m)
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (hasNonNullishProperty(message, "footer")) {
|
|
1013
|
+
hydratedTemplate.hydratedFooterText = message.footer
|
|
1014
|
+
}
|
|
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
|
|
1039
|
+
}
|
|
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 }
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (hasOptionalProperty(message, "ephemeral")) {
|
|
1320
|
+
m = { ephemeralMessage: { message: m } }
|
|
1321
|
+
}
|
|
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
|
+
}
|
|
1334
|
+
}
|
|
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
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (hasOptionalProperty(message, "edit")) {
|
|
1348
|
+
m = {
|
|
1349
|
+
protocolMessage: {
|
|
1350
|
+
key: message.edit,
|
|
1351
|
+
editedMessage: m,
|
|
1352
|
+
timestampMs: Date.now(),
|
|
1353
|
+
type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
|
|
1354
|
+
},
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
return WAProto.Message.fromObject(m)
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
const generateWAMessageFromContent = (jid, message, options) => {
|
|
1362
|
+
if (!options.timestamp) {
|
|
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
|
+
|
|
1384
|
+
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
|
|
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
|
+
|
|
1404
|
+
if (jid !== 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
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
if (key !== 'protocolMessage' &&
|
|
1432
|
+
key !== 'ephemeralMessage' &&
|
|
1433
|
+
!isJidNewsletter(jid)) {
|
|
1434
|
+
message.messageContextInfo = {
|
|
1435
|
+
threadId: threadId.length > 0 ? threadId : [],
|
|
1436
|
+
messageSecret: randomBytes(32),
|
|
1437
|
+
...message.messageContextInfo
|
|
1438
|
+
}
|
|
1439
|
+
innerMessage[key].contextInfo = {
|
|
1440
|
+
...(innerMessage[key].contextInfo || {}),
|
|
1441
|
+
expiration: options.ephemeralExpiration ? options.ephemeralExpiration : 0
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
message = WAProto.Message.fromObject(message)
|
|
1446
|
+
|
|
1447
|
+
const messageJSON = {
|
|
1448
|
+
key: {
|
|
1449
|
+
remoteJid: jid,
|
|
1450
|
+
fromMe: true,
|
|
1451
|
+
id: options?.messageId || generateMessageID()
|
|
1452
|
+
},
|
|
1453
|
+
message: message,
|
|
1454
|
+
messageTimestamp: timestamp,
|
|
1455
|
+
messageStubParameters: [],
|
|
1456
|
+
participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined,
|
|
1457
|
+
status: WAMessageStatus.PENDING
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
return WAProto.WebMessageInfo.fromObject(messageJSON)
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const generateWAMessage = async (jid, content, options) => {
|
|
1464
|
+
options.logger = options?.logger?.child({ msgId: options.messageId })
|
|
1465
|
+
|
|
1466
|
+
return generateWAMessageFromContent(jid, await generateWAMessageContent(content, { newsletter: isJidNewsletter(jid), ...options }), options)
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
const getContentType = (content) => {
|
|
1470
|
+
if (content) {
|
|
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
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Normalizes ephemeral, view once messages to regular message content
|
|
1480
|
+
* Eg. image messages in ephemeral messages, in view once messages etc.
|
|
1481
|
+
* @param content
|
|
1482
|
+
* @returns
|
|
1483
|
+
*/
|
|
1484
|
+
const normalizeMessageContent = (content) => {
|
|
1485
|
+
if (!content) {
|
|
1486
|
+
return undefined
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
for (let i = 0; i < 5; i++) {
|
|
1490
|
+
const inner = getFutureProofMessage(content)
|
|
1491
|
+
if (!inner) {
|
|
1492
|
+
break
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
content = inner.message
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
return content
|
|
1499
|
+
|
|
1500
|
+
function getFutureProofMessage(message) {
|
|
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
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Extract the true message content from a message
|
|
1530
|
+
* Eg. extracts the inner message from a disappearing message/view once message
|
|
1531
|
+
*/
|
|
1532
|
+
const extractMessageContent = (content) => {
|
|
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 }
|
|
1538
|
+
}
|
|
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 }
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
else if (header ? msg.header?.locationMessage : msg.locationMessage) {
|
|
1549
|
+
return { locationMessage: header ? msg.header.locationMessage : msg.locationMessage }
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
else if (header ? msg.header?.productMessage : msg.productMessage) {
|
|
1553
|
+
return { productMessage: header ? msg.header.productMessage : msg.productMessage }
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
else {
|
|
1557
|
+
return {
|
|
1558
|
+
conversation: 'contentText' in msg
|
|
1559
|
+
? msg.contentText
|
|
1560
|
+
: ('hydratedContentText' in msg ? msg.hydratedContentText : 'body' in msg ? msg.body.text : '')
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
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)
|
|
1573
|
+
}
|
|
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)
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (content?.templateMessage?.hydratedTemplate) {
|
|
1584
|
+
return extractFromButtonsMessage(content?.templateMessage?.hydratedTemplate)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
if (content?.templateMessage?.fourRowTemplate) {
|
|
1588
|
+
return extractFromButtonsMessage(content?.templateMessage?.fourRowTemplate)
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
return content
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Returns the device predicted by message ID
|
|
1596
|
+
*/
|
|
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
|
+
|
|
1603
|
+
/** Upserts a receipt in the message */
|
|
1604
|
+
const updateMessageWithReceipt = (msg, receipt) => {
|
|
1605
|
+
msg.userReceipt = msg.userReceipt || []
|
|
1606
|
+
const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid)
|
|
1607
|
+
|
|
1608
|
+
if (recp) {
|
|
1609
|
+
Object.assign(recp, receipt)
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
else {
|
|
1613
|
+
msg.userReceipt.push(receipt)
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/** Update the message with a new reaction */
|
|
1618
|
+
const updateMessageWithReaction = (msg, reaction) => {
|
|
1619
|
+
const authorID = getKeyAuthor(reaction.key)
|
|
1620
|
+
const reactions = (msg.reactions || [])
|
|
1621
|
+
.filter(r => getKeyAuthor(r.key) !== authorID)
|
|
1622
|
+
|
|
1623
|
+
reaction.text = reaction.text || ''
|
|
1624
|
+
reactions.push(reaction)
|
|
1625
|
+
msg.reactions = reactions
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/** Update the message with a new poll update */
|
|
1629
|
+
const updateMessageWithPollUpdate = (msg, update) => {
|
|
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
|
+
|
|
1651
|
+
/**
|
|
1652
|
+
* Aggregates all poll updates in a poll.
|
|
1653
|
+
* @param msg the poll creation message
|
|
1654
|
+
* @param meId your jid
|
|
1655
|
+
* @returns A list of options & their voters
|
|
1656
|
+
*/
|
|
1657
|
+
function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
|
|
1658
|
+
message = normalizeMessageContent(message)
|
|
1659
|
+
|
|
1660
|
+
const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || []
|
|
1661
|
+
|
|
1662
|
+
const voteHashMap = opts.reduce((acc, opt) => {
|
|
1663
|
+
const hash = sha256(Buffer.from(opt.optionName || '')).toString()
|
|
1664
|
+
acc[hash] = {
|
|
1665
|
+
name: opt.optionName || '',
|
|
1666
|
+
voters: []
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
return acc
|
|
1670
|
+
}, {})
|
|
1671
|
+
|
|
1672
|
+
for (const update of pollUpdates || []) {
|
|
1673
|
+
const { vote } = update
|
|
1674
|
+
|
|
1675
|
+
if (!vote) {
|
|
1676
|
+
continue
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
for (const option of vote.selectedOptions || []) {
|
|
1680
|
+
const hash = option.toString()
|
|
1681
|
+
let data = voteHashMap[hash]
|
|
1682
|
+
|
|
1683
|
+
if (!data) {
|
|
1684
|
+
voteHashMap[hash] = {
|
|
1685
|
+
name: 'Unknown',
|
|
1686
|
+
voters: []
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
data = voteHashMap[hash]
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId))
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
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)
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
|
|
1728
|
+
const aggregateMessageKeysNotFromMe = (keys) => {
|
|
1729
|
+
const keyMap = {}
|
|
1730
|
+
|
|
1731
|
+
for (const { remoteJid, id, participant, fromMe } of keys) {
|
|
1732
|
+
if (!fromMe) {
|
|
1733
|
+
const uqKey = `${remoteJid}:${participant || ''}`
|
|
1734
|
+
|
|
1735
|
+
if (!keyMap[uqKey]) {
|
|
1736
|
+
keyMap[uqKey] = {
|
|
1737
|
+
jid: remoteJid,
|
|
1738
|
+
participant: participant,
|
|
1739
|
+
messageIds: []
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
keyMap[uqKey].messageIds.push(id)
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
return Object.values(keyMap)
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
const REUPLOAD_REQUIRED_STATUS = [410, 404]
|
|
1751
|
+
|
|
1752
|
+
/**
|
|
1753
|
+
* Downloads the given message. Throws an error if it's not a media message
|
|
1754
|
+
*/
|
|
1755
|
+
const downloadMediaMessage = async (message, type, options, ctx) => {
|
|
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
|
+
|
|
1774
|
+
async function downloadMsg() {
|
|
1775
|
+
const mContent = extractMessageContent(message.message)
|
|
1776
|
+
|
|
1777
|
+
if (!mContent) {
|
|
1778
|
+
throw new Boom('No message present', { statusCode: 400, data: message })
|
|
1779
|
+
}
|
|
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
|
+
|
|
1787
|
+
if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
|
|
1788
|
+
throw new Boom(`"${contentType}" message is not a media message`)
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
let download
|
|
1792
|
+
|
|
1793
|
+
if ('thumbnailDirectPath' in media && !('url' in media)) {
|
|
1794
|
+
download = {
|
|
1795
|
+
directPath: media.thumbnailDirectPath,
|
|
1796
|
+
mediaKey: media.mediaKey
|
|
1797
|
+
}
|
|
1798
|
+
mediaType = 'thumbnail-link'
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
download = media
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const stream = await downloadContentFromMessage(download, mediaType, options)
|
|
1805
|
+
|
|
1806
|
+
if (type === 'buffer') {
|
|
1807
|
+
const bufferArray = []
|
|
1808
|
+
for await (const chunk of stream) {
|
|
1809
|
+
bufferArray.push(chunk);
|
|
1810
|
+
}
|
|
1811
|
+
return Buffer.concat(bufferArray);
|
|
1812
|
+
}
|
|
1813
|
+
return stream
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
/** Checks whether the given message is a media message; if it is returns the inner content */
|
|
1818
|
+
const assertMediaContent = (content) => {
|
|
1819
|
+
content = extractMessageContent(content)
|
|
1820
|
+
|
|
1821
|
+
const mediaContent = content?.documentMessage ||
|
|
1822
|
+
content?.imageMessage ||
|
|
1823
|
+
content?.videoMessage ||
|
|
1824
|
+
content?.audioMessage ||
|
|
1825
|
+
content?.stickerMessage
|
|
1826
|
+
|
|
1827
|
+
if (!mediaContent) {
|
|
1828
|
+
throw new Boom('given message is not a media message', { statusCode: 400, data: content });
|
|
1829
|
+
}
|
|
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
|
+
}
|