@sixcore/baileys 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/WAProto/index.js +14270 -302
  2. package/jessica.js +91 -0
  3. package/lib/Defaults/baileys-version.json +1 -1
  4. package/lib/Defaults/index.js +117 -79
  5. package/lib/Defaults/phonenumber-mcc.json +223 -0
  6. package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
  7. package/lib/Signal/Group/ciphertext-message.js +15 -0
  8. package/lib/Signal/Group/group-session-builder.d.ts +14 -0
  9. package/lib/Signal/Group/group-session-builder.js +64 -0
  10. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  11. package/lib/Signal/Group/group_cipher.js +96 -0
  12. package/lib/Signal/Group/index.d.ts +11 -0
  13. package/lib/Signal/Group/index.js +57 -0
  14. package/lib/Signal/Group/keyhelper.d.ts +10 -0
  15. package/lib/Signal/Group/keyhelper.js +55 -0
  16. package/lib/Signal/Group/queue-job.d.ts +1 -0
  17. package/lib/Signal/Group/queue-job.js +57 -0
  18. package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
  19. package/lib/Signal/Group/sender-chain-key.js +34 -0
  20. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
  21. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  22. package/lib/Signal/Group/sender-key-message.d.ts +18 -0
  23. package/lib/Signal/Group/sender-key-message.js +69 -0
  24. package/lib/Signal/Group/sender-key-name.d.ts +17 -0
  25. package/lib/Signal/Group/sender-key-name.js +51 -0
  26. package/lib/Signal/Group/sender-key-record.d.ts +30 -0
  27. package/lib/Signal/Group/sender-key-record.js +53 -0
  28. package/lib/Signal/Group/sender-key-state.d.ts +38 -0
  29. package/lib/Signal/Group/sender-key-state.js +99 -0
  30. package/lib/Signal/Group/sender-message-key.d.ts +11 -0
  31. package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +6 -16
  32. package/lib/Signal/libsignal.js +51 -29
  33. package/lib/Socket/business.d.ts +43 -42
  34. package/lib/Socket/chats.d.ts +222 -36
  35. package/lib/Socket/chats.js +173 -153
  36. package/lib/Socket/dugong.d.ts +254 -0
  37. package/lib/Socket/dugong.js +484 -0
  38. package/lib/Socket/groups.d.ts +7 -7
  39. package/lib/Socket/groups.js +37 -35
  40. package/lib/Socket/index.d.ts +52 -51
  41. package/lib/Socket/index.js +1 -0
  42. package/lib/Socket/messages-recv.d.ts +37 -34
  43. package/lib/Socket/messages-recv.js +175 -37
  44. package/lib/Socket/messages-send.d.ts +12 -18
  45. package/lib/Socket/messages-send.js +396 -574
  46. package/lib/Socket/newsletter.d.ts +28 -26
  47. package/lib/Socket/newsletter.js +123 -25
  48. package/lib/Socket/registration.d.ts +52 -49
  49. package/lib/Socket/registration.js +7 -7
  50. package/lib/Socket/socket.d.ts +0 -1
  51. package/lib/Socket/socket.js +49 -27
  52. package/lib/Socket/usync.d.ts +10 -11
  53. package/lib/Store/make-cache-manager-store.d.ts +1 -2
  54. package/lib/Store/make-in-memory-store.d.ts +2 -2
  55. package/lib/Store/make-in-memory-store.js +1 -5
  56. package/lib/Store/make-ordered-dictionary.js +2 -2
  57. package/lib/Types/Auth.d.ts +1 -0
  58. package/lib/Types/Call.d.ts +1 -1
  59. package/lib/Types/Chat.d.ts +7 -12
  60. package/lib/Types/Events.d.ts +2 -17
  61. package/lib/Types/GroupMetadata.d.ts +2 -3
  62. package/lib/Types/Label.d.ts +0 -11
  63. package/lib/Types/Label.js +1 -1
  64. package/lib/Types/LabelAssociation.js +1 -1
  65. package/lib/Types/Message.d.ts +10 -170
  66. package/lib/Types/Newsletter.d.ts +97 -86
  67. package/lib/Types/Newsletter.js +38 -32
  68. package/lib/Types/Socket.d.ts +2 -7
  69. package/lib/Types/index.d.ts +0 -9
  70. package/lib/Types/index.js +1 -1
  71. package/lib/Utils/auth-utils.js +14 -35
  72. package/lib/Utils/business.d.ts +1 -1
  73. package/lib/Utils/business.js +2 -2
  74. package/lib/Utils/chat-utils.d.ts +12 -11
  75. package/lib/Utils/chat-utils.js +36 -52
  76. package/lib/Utils/crypto.d.ts +16 -15
  77. package/lib/Utils/crypto.js +26 -74
  78. package/lib/Utils/decode-wa-message.d.ts +0 -17
  79. package/lib/Utils/decode-wa-message.js +17 -53
  80. package/lib/Utils/event-buffer.js +7 -10
  81. package/lib/Utils/generics.d.ts +17 -13
  82. package/lib/Utils/generics.js +79 -58
  83. package/lib/Utils/history.d.ts +2 -6
  84. package/lib/Utils/history.js +6 -4
  85. package/lib/Utils/logger.d.ts +3 -1
  86. package/lib/Utils/lt-hash.js +12 -12
  87. package/lib/Utils/make-mutex.d.ts +2 -2
  88. package/lib/Utils/messages-media.d.ts +28 -25
  89. package/lib/Utils/messages-media.js +733 -557
  90. package/lib/Utils/messages.js +68 -473
  91. package/lib/Utils/noise-handler.d.ts +5 -4
  92. package/lib/Utils/noise-handler.js +14 -19
  93. package/lib/Utils/process-message.d.ts +5 -5
  94. package/lib/Utils/process-message.js +23 -75
  95. package/lib/Utils/signal.d.ts +1 -2
  96. package/lib/Utils/signal.js +26 -32
  97. package/lib/Utils/use-multi-file-auth-state.d.ts +1 -0
  98. package/lib/Utils/use-multi-file-auth-state.js +66 -242
  99. package/lib/Utils/validate-connection.d.ts +1 -1
  100. package/lib/Utils/validate-connection.js +88 -64
  101. package/lib/WABinary/constants.d.ts +27 -24
  102. package/lib/WABinary/decode.d.ts +2 -1
  103. package/lib/WABinary/decode.js +11 -23
  104. package/lib/WABinary/encode.d.ts +2 -1
  105. package/lib/WABinary/encode.js +147 -134
  106. package/lib/WABinary/generic-utils.d.ts +5 -2
  107. package/lib/WABinary/generic-utils.js +125 -37
  108. package/lib/WABinary/jid-utils.d.ts +1 -1
  109. package/lib/WAM/BinaryInfo.d.ts +11 -2
  110. package/lib/WAM/encode.d.ts +2 -1
  111. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +3 -3
  112. package/lib/WAUSync/USyncUser.d.ts +2 -0
  113. package/lib/index.d.ts +12 -0
  114. package/lib/index.js +64 -1
  115. package/package.json +113 -51
  116. package/WAProto/GenerateStatics.sh +0 -4
  117. package/WAProto/WAProto.proto +0 -4357
  118. package/WAProto/index.d.ts +0 -50383
  119. package/WASignalGroup/GroupProtocol.js +0 -1697
  120. package/WASignalGroup/ciphertext_message.js +0 -16
  121. package/WASignalGroup/generate-proto.sh +0 -1
  122. package/WASignalGroup/group.proto +0 -42
  123. package/WASignalGroup/group_cipher.js +0 -120
  124. package/WASignalGroup/group_session_builder.js +0 -46
  125. package/WASignalGroup/index.js +0 -5
  126. package/WASignalGroup/keyhelper.js +0 -21
  127. package/WASignalGroup/protobufs.js +0 -3
  128. package/WASignalGroup/queue_job.js +0 -69
  129. package/WASignalGroup/sender_chain_key.js +0 -50
  130. package/WASignalGroup/sender_key_distribution_message.js +0 -78
  131. package/WASignalGroup/sender_key_message.js +0 -92
  132. package/WASignalGroup/sender_key_name.js +0 -70
  133. package/WASignalGroup/sender_key_record.js +0 -56
  134. package/WASignalGroup/sender_key_state.js +0 -129
  135. package/lib/Utils/use-single-file-auth-state.d.ts +0 -12
  136. package/lib/Utils/use-single-file-auth-state.js +0 -75
  137. package/src/Defaults/baileys-version.json +0 -3
  138. package/src/Defaults/index.ts +0 -133
  139. package/src/Signal/Group/ciphertext-message.ts +0 -9
  140. package/src/Signal/Group/group-session-builder.ts +0 -56
  141. package/src/Signal/Group/group_cipher.ts +0 -117
  142. package/src/Signal/Group/index.ts +0 -11
  143. package/src/Signal/Group/keyhelper.ts +0 -28
  144. package/src/Signal/Group/sender-chain-key.ts +0 -34
  145. package/src/Signal/Group/sender-key-distribution-message.ts +0 -95
  146. package/src/Signal/Group/sender-key-message.ts +0 -96
  147. package/src/Signal/Group/sender-key-name.ts +0 -66
  148. package/src/Signal/Group/sender-key-record.ts +0 -69
  149. package/src/Signal/Group/sender-key-state.ts +0 -134
  150. package/src/Signal/Group/sender-message-key.ts +0 -36
  151. package/src/Signal/libsignal.ts +0 -447
  152. package/src/Signal/lid-mapping.ts +0 -209
  153. package/src/Socket/Client/index.ts +0 -2
  154. package/src/Socket/Client/types.ts +0 -22
  155. package/src/Socket/Client/websocket.ts +0 -56
  156. package/src/Socket/business.ts +0 -421
  157. package/src/Socket/chats.ts +0 -1223
  158. package/src/Socket/communities.ts +0 -477
  159. package/src/Socket/groups.ts +0 -361
  160. package/src/Socket/index.ts +0 -22
  161. package/src/Socket/messages-recv.ts +0 -1563
  162. package/src/Socket/messages-send.ts +0 -1210
  163. package/src/Socket/mex.ts +0 -58
  164. package/src/Socket/newsletter.ts +0 -229
  165. package/src/Socket/socket.ts +0 -1072
  166. package/src/Types/Auth.ts +0 -115
  167. package/src/Types/Bussines.ts +0 -20
  168. package/src/Types/Call.ts +0 -14
  169. package/src/Types/Chat.ts +0 -138
  170. package/src/Types/Contact.ts +0 -24
  171. package/src/Types/Events.ts +0 -132
  172. package/src/Types/GroupMetadata.ts +0 -70
  173. package/src/Types/Label.ts +0 -48
  174. package/src/Types/LabelAssociation.ts +0 -35
  175. package/src/Types/Message.ts +0 -424
  176. package/src/Types/Newsletter.ts +0 -98
  177. package/src/Types/Product.ts +0 -85
  178. package/src/Types/Signal.ts +0 -76
  179. package/src/Types/Socket.ts +0 -150
  180. package/src/Types/State.ts +0 -43
  181. package/src/Types/USync.ts +0 -27
  182. package/src/Types/globals.d.ts +0 -8
  183. package/src/Types/index.ts +0 -67
  184. package/src/Utils/auth-utils.ts +0 -331
  185. package/src/Utils/browser-utils.ts +0 -31
  186. package/src/Utils/business.ts +0 -286
  187. package/src/Utils/chat-utils.ts +0 -933
  188. package/src/Utils/crypto.ts +0 -184
  189. package/src/Utils/decode-wa-message.ts +0 -355
  190. package/src/Utils/event-buffer.ts +0 -662
  191. package/src/Utils/generics.ts +0 -470
  192. package/src/Utils/history.ts +0 -114
  193. package/src/Utils/index.ts +0 -18
  194. package/src/Utils/link-preview.ts +0 -111
  195. package/src/Utils/logger.ts +0 -13
  196. package/src/Utils/lt-hash.ts +0 -65
  197. package/src/Utils/make-mutex.ts +0 -45
  198. package/src/Utils/message-retry-manager.ts +0 -229
  199. package/src/Utils/messages-media.ts +0 -820
  200. package/src/Utils/messages.ts +0 -1137
  201. package/src/Utils/noise-handler.ts +0 -192
  202. package/src/Utils/pre-key-manager.ts +0 -126
  203. package/src/Utils/process-message.ts +0 -622
  204. package/src/Utils/signal.ts +0 -214
  205. package/src/Utils/use-multi-file-auth-state.ts +0 -136
  206. package/src/Utils/validate-connection.ts +0 -253
  207. package/src/WABinary/constants.ts +0 -1305
  208. package/src/WABinary/decode.ts +0 -281
  209. package/src/WABinary/encode.ts +0 -253
  210. package/src/WABinary/generic-utils.ts +0 -127
  211. package/src/WABinary/index.ts +0 -5
  212. package/src/WABinary/jid-utils.ts +0 -128
  213. package/src/WABinary/types.ts +0 -17
  214. package/src/WAM/BinaryInfo.ts +0 -12
  215. package/src/WAM/constants.ts +0 -22889
  216. package/src/WAM/encode.ts +0 -169
  217. package/src/WAM/index.ts +0 -3
  218. package/src/WAUSync/Protocols/USyncContactProtocol.ts +0 -32
  219. package/src/WAUSync/Protocols/USyncDeviceProtocol.ts +0 -78
  220. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts +0 -35
  221. package/src/WAUSync/Protocols/USyncStatusProtocol.ts +0 -44
  222. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts +0 -76
  223. package/src/WAUSync/Protocols/UsyncLIDProtocol.ts +0 -33
  224. package/src/WAUSync/Protocols/index.ts +0 -4
  225. package/src/WAUSync/USyncQuery.ts +0 -133
  226. package/src/WAUSync/USyncUser.ts +0 -32
  227. package/src/WAUSync/index.ts +0 -3
  228. package/src/index.ts +0 -13
@@ -1,820 +0,0 @@
1
- import { Boom } from '@hapi/boom'
2
- import { exec } from 'child_process'
3
- import * as Crypto from 'crypto'
4
- import { once } from 'events'
5
- import { createReadStream, createWriteStream, promises as fs, WriteStream } from 'fs'
6
- import type { IAudioMetadata } from 'music-metadata'
7
- import { tmpdir } from 'os'
8
- import { join } from 'path'
9
- import { Readable, Transform } from 'stream'
10
- import { URL } from 'url'
11
- import { proto } from '../../WAProto/index.js'
12
- import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP, type MediaType } from '../Defaults'
13
- import type {
14
- BaileysEventMap,
15
- DownloadableMessage,
16
- MediaConnInfo,
17
- MediaDecryptionKeyInfo,
18
- MessageType,
19
- SocketConfig,
20
- WAGenericMediaMessage,
21
- WAMediaUpload,
22
- WAMediaUploadFunction,
23
- WAMessageContent,
24
- WAMessageKey
25
- } from '../Types'
26
- import { type BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary'
27
- import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto'
28
- import { generateMessageIDV2 } from './generics'
29
- import type { ILogger } from './logger'
30
-
31
- const getTmpFilesDirectory = () => tmpdir()
32
-
33
- const getImageProcessingLibrary = async () => {
34
- //@ts-ignore
35
- const [jimp, sharp] = await Promise.all([import('jimp').catch(() => {}), import('sharp').catch(() => {})])
36
-
37
- if (sharp) {
38
- return { sharp }
39
- }
40
-
41
- if (jimp) {
42
- return { jimp }
43
- }
44
-
45
- throw new Boom('No image processing library available')
46
- }
47
-
48
- export const hkdfInfoKey = (type: MediaType) => {
49
- const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type]
50
- return `WhatsApp ${hkdfInfo} Keys`
51
- }
52
-
53
- export const getRawMediaUploadData = async (media: WAMediaUpload, mediaType: MediaType, logger?: ILogger) => {
54
- const { stream } = await getStream(media)
55
- logger?.debug('got stream for raw upload')
56
-
57
- const hasher = Crypto.createHash('sha256')
58
- const filePath = join(tmpdir(), mediaType + generateMessageIDV2())
59
- const fileWriteStream = createWriteStream(filePath)
60
-
61
- let fileLength = 0
62
- try {
63
- for await (const data of stream) {
64
- fileLength += data.length
65
- hasher.update(data)
66
- if (!fileWriteStream.write(data)) {
67
- await once(fileWriteStream, 'drain')
68
- }
69
- }
70
-
71
- fileWriteStream.end()
72
- await once(fileWriteStream, 'finish')
73
- stream.destroy()
74
- const fileSha256 = hasher.digest()
75
- logger?.debug('hashed data for raw upload')
76
- return {
77
- filePath: filePath,
78
- fileSha256,
79
- fileLength
80
- }
81
- } catch (error) {
82
- fileWriteStream.destroy()
83
- stream.destroy()
84
- try {
85
- await fs.unlink(filePath)
86
- } catch {
87
- //
88
- }
89
-
90
- throw error
91
- }
92
- }
93
-
94
- /** generates all the keys required to encrypt/decrypt & sign a media message */
95
- export async function getMediaKeys(
96
- buffer: Uint8Array | string | null | undefined,
97
- mediaType: MediaType
98
- ): Promise<MediaDecryptionKeyInfo> {
99
- if (!buffer) {
100
- throw new Boom('Cannot derive from empty media key')
101
- }
102
-
103
- if (typeof buffer === 'string') {
104
- buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64')
105
- }
106
-
107
- // expand using HKDF to 112 bytes, also pass in the relevant app info
108
- const expandedMediaKey = await hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
109
- return {
110
- iv: expandedMediaKey.slice(0, 16),
111
- cipherKey: expandedMediaKey.slice(16, 48),
112
- macKey: expandedMediaKey.slice(48, 80)
113
- }
114
- }
115
-
116
- /** Extracts video thumb using FFMPEG */
117
- const extractVideoThumb = async (
118
- path: string,
119
- destPath: string,
120
- time: string,
121
- size: { width: number; height: number }
122
- ) =>
123
- new Promise<void>((resolve, reject) => {
124
- const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`
125
- exec(cmd, err => {
126
- if (err) {
127
- reject(err)
128
- } else {
129
- resolve()
130
- }
131
- })
132
- })
133
-
134
- export const extractImageThumb = async (bufferOrFilePath: Readable | Buffer | string, width = 32) => {
135
- // TODO: Move entirely to sharp, removing jimp as it supports readable streams
136
- // This will have positive speed and performance impacts as well as minimizing RAM usage.
137
- if (bufferOrFilePath instanceof Readable) {
138
- bufferOrFilePath = await toBuffer(bufferOrFilePath)
139
- }
140
-
141
- const lib = await getImageProcessingLibrary()
142
- if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
143
- const img = lib.sharp.default(bufferOrFilePath)
144
- const dimensions = await img.metadata()
145
-
146
- const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer()
147
- return {
148
- buffer,
149
- original: {
150
- width: dimensions.width,
151
- height: dimensions.height
152
- }
153
- }
154
- } else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
155
- const jimp = await (lib.jimp.Jimp as any).read(bufferOrFilePath)
156
- const dimensions = {
157
- width: jimp.width,
158
- height: jimp.height
159
- }
160
- const buffer = await jimp
161
- .resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
162
- .getBuffer('image/jpeg', { quality: 50 })
163
- return {
164
- buffer,
165
- original: dimensions
166
- }
167
- } else {
168
- throw new Boom('No image processing library available')
169
- }
170
- }
171
-
172
- export const encodeBase64EncodedStringForUpload = (b64: string) =>
173
- encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''))
174
-
175
- export const generateProfilePicture = async (
176
- mediaUpload: WAMediaUpload,
177
- dimensions?: { width: number; height: number }
178
- ) => {
179
- let buffer: Buffer
180
-
181
- const { width: w = 640, height: h = 640 } = dimensions || {}
182
-
183
- if (Buffer.isBuffer(mediaUpload)) {
184
- buffer = mediaUpload
185
- } else {
186
- // Use getStream to handle all WAMediaUpload types (Buffer, Stream, URL)
187
- const { stream } = await getStream(mediaUpload)
188
- // Convert the resulting stream to a buffer
189
- buffer = await toBuffer(stream)
190
- }
191
-
192
- const lib = await getImageProcessingLibrary()
193
- let img: Promise<Buffer>
194
- if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
195
- img = lib.sharp
196
- .default(buffer)
197
- .resize(w, h)
198
- .jpeg({
199
- quality: 50
200
- })
201
- .toBuffer()
202
- } else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'function') {
203
- const jimp = await (lib.jimp.Jimp as any).read(buffer)
204
- const min = Math.min(jimp.width, jimp.height)
205
- const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min })
206
-
207
- img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 })
208
- } else {
209
- throw new Boom('No image processing library available')
210
- }
211
-
212
- return {
213
- img: await img
214
- }
215
- }
216
-
217
- /** gets the SHA256 of the given media message */
218
- export const mediaMessageSHA256B64 = (message: WAMessageContent) => {
219
- const media = Object.values(message)[0] as WAGenericMediaMessage
220
- return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64')
221
- }
222
-
223
- export async function getAudioDuration(buffer: Buffer | string | Readable) {
224
- const musicMetadata = await import('music-metadata')
225
- let metadata: IAudioMetadata
226
- const options = {
227
- duration: true
228
- }
229
- if (Buffer.isBuffer(buffer)) {
230
- metadata = await musicMetadata.parseBuffer(buffer, undefined, options)
231
- } else if (typeof buffer === 'string') {
232
- metadata = await musicMetadata.parseFile(buffer, options)
233
- } else {
234
- metadata = await musicMetadata.parseStream(buffer, undefined, options)
235
- }
236
-
237
- return metadata.format.duration
238
- }
239
-
240
- /**
241
- referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
242
- */
243
- export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: ILogger) {
244
- try {
245
- // @ts-ignore
246
- const { default: decoder } = await import('audio-decode')
247
- let audioData: Buffer
248
- if (Buffer.isBuffer(buffer)) {
249
- audioData = buffer
250
- } else if (typeof buffer === 'string') {
251
- const rStream = createReadStream(buffer)
252
- audioData = await toBuffer(rStream)
253
- } else {
254
- audioData = await toBuffer(buffer)
255
- }
256
-
257
- const audioBuffer = await decoder(audioData)
258
-
259
- const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
260
- const samples = 64 // Number of samples we want to have in our final data set
261
- const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision
262
- const filteredData: number[] = []
263
- for (let i = 0; i < samples; i++) {
264
- const blockStart = blockSize * i // the location of the first sample in the block
265
- let sum = 0
266
- for (let j = 0; j < blockSize; j++) {
267
- sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block
268
- }
269
-
270
- filteredData.push(sum / blockSize) // divide the sum by the block size to get the average
271
- }
272
-
273
- // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
274
- const multiplier = Math.pow(Math.max(...filteredData), -1)
275
- const normalizedData = filteredData.map(n => n * multiplier)
276
-
277
- // Generate waveform like WhatsApp
278
- const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)))
279
-
280
- return waveform
281
- } catch (e) {
282
- logger?.debug('Failed to generate waveform: ' + e)
283
- }
284
- }
285
-
286
- export const toReadable = (buffer: Buffer) => {
287
- const readable = new Readable({ read: () => {} })
288
- readable.push(buffer)
289
- readable.push(null)
290
- return readable
291
- }
292
-
293
- export const toBuffer = async (stream: Readable) => {
294
- const chunks: Buffer[] = []
295
- for await (const chunk of stream) {
296
- chunks.push(chunk)
297
- }
298
-
299
- stream.destroy()
300
- return Buffer.concat(chunks)
301
- }
302
-
303
- export const getStream = async (item: WAMediaUpload, opts?: RequestInit & { maxContentLength?: number }) => {
304
- if (Buffer.isBuffer(item)) {
305
- return { stream: toReadable(item), type: 'buffer' } as const
306
- }
307
-
308
- if ('stream' in item) {
309
- return { stream: item.stream, type: 'readable' } as const
310
- }
311
-
312
- const urlStr = item.url.toString()
313
-
314
- if (urlStr.startsWith('data:')) {
315
- const buffer = Buffer.from(urlStr.split(',')[1]!, 'base64')
316
- return { stream: toReadable(buffer), type: 'buffer' } as const
317
- }
318
-
319
- if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
320
- return { stream: await getHttpStream(item.url, opts), type: 'remote' } as const
321
- }
322
-
323
- return { stream: createReadStream(item.url), type: 'file' } as const
324
- }
325
-
326
- /** generates a thumbnail for a given media, if required */
327
- export async function generateThumbnail(
328
- file: string,
329
- mediaType: 'video' | 'image',
330
- options: {
331
- logger?: ILogger
332
- }
333
- ) {
334
- let thumbnail: string | undefined
335
- let originalImageDimensions: { width: number; height: number } | undefined
336
- if (mediaType === 'image') {
337
- const { buffer, original } = await extractImageThumb(file)
338
- thumbnail = buffer.toString('base64')
339
- if (original.width && original.height) {
340
- originalImageDimensions = {
341
- width: original.width,
342
- height: original.height
343
- }
344
- }
345
- } else if (mediaType === 'video') {
346
- const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + '.jpg')
347
- try {
348
- await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 })
349
- const buff = await fs.readFile(imgFilename)
350
- thumbnail = buff.toString('base64')
351
-
352
- await fs.unlink(imgFilename)
353
- } catch (err) {
354
- options.logger?.debug('could not generate video thumb: ' + err)
355
- }
356
- }
357
-
358
- return {
359
- thumbnail,
360
- originalImageDimensions
361
- }
362
- }
363
-
364
- export const getHttpStream = async (url: string | URL, options: RequestInit & { isStream?: true } = {}) => {
365
- const response = await fetch(url.toString(), {
366
- dispatcher: options.dispatcher,
367
- method: 'GET',
368
- headers: options.headers as HeadersInit
369
- })
370
- if (!response.ok) {
371
- throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } })
372
- }
373
-
374
- // @ts-ignore Node18+ Readable.fromWeb exists
375
- return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body as any)
376
- }
377
-
378
- type EncryptedStreamOptions = {
379
- saveOriginalFileIfRequired?: boolean
380
- logger?: ILogger
381
- opts?: RequestInit
382
- }
383
-
384
- export const encryptedStream = async (
385
- media: WAMediaUpload,
386
- mediaType: MediaType,
387
- { logger, saveOriginalFileIfRequired, opts }: EncryptedStreamOptions = {}
388
- ) => {
389
- const { stream, type } = await getStream(media, opts)
390
-
391
- logger?.debug('fetched media stream')
392
-
393
- const mediaKey = Crypto.randomBytes(32)
394
- const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
395
-
396
- const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-enc')
397
- const encFileWriteStream = createWriteStream(encFilePath)
398
-
399
- let originalFileStream: WriteStream | undefined
400
- let originalFilePath: string | undefined
401
-
402
- if (saveOriginalFileIfRequired) {
403
- originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-original')
404
- originalFileStream = createWriteStream(originalFilePath)
405
- }
406
-
407
- let fileLength = 0
408
- const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
409
- const hmac = Crypto.createHmac('sha256', macKey!).update(iv)
410
- const sha256Plain = Crypto.createHash('sha256')
411
- const sha256Enc = Crypto.createHash('sha256')
412
-
413
- const onChunk = (buff: Buffer) => {
414
- sha256Enc.update(buff)
415
- hmac.update(buff)
416
- encFileWriteStream.write(buff)
417
- }
418
-
419
- try {
420
- for await (const data of stream) {
421
- fileLength += data.length
422
-
423
- if (
424
- type === 'remote' &&
425
- (opts as any)?.maxContentLength &&
426
- fileLength + data.length > (opts as any).maxContentLength
427
- ) {
428
- throw new Boom(`content length exceeded when encrypting "${type}"`, {
429
- data: { media, type }
430
- })
431
- }
432
-
433
- if (originalFileStream) {
434
- if (!originalFileStream.write(data)) {
435
- await once(originalFileStream, 'drain')
436
- }
437
- }
438
-
439
- sha256Plain.update(data)
440
- onChunk(aes.update(data))
441
- }
442
-
443
- onChunk(aes.final())
444
-
445
- const mac = hmac.digest().slice(0, 10)
446
- sha256Enc.update(mac)
447
-
448
- const fileSha256 = sha256Plain.digest()
449
- const fileEncSha256 = sha256Enc.digest()
450
-
451
- encFileWriteStream.write(mac)
452
-
453
- encFileWriteStream.end()
454
- originalFileStream?.end?.()
455
- stream.destroy()
456
-
457
- logger?.debug('encrypted data successfully')
458
-
459
- return {
460
- mediaKey,
461
- originalFilePath,
462
- encFilePath,
463
- mac,
464
- fileEncSha256,
465
- fileSha256,
466
- fileLength
467
- }
468
- } catch (error) {
469
- // destroy all streams with error
470
- encFileWriteStream.destroy()
471
- originalFileStream?.destroy?.()
472
- aes.destroy()
473
- hmac.destroy()
474
- sha256Plain.destroy()
475
- sha256Enc.destroy()
476
- stream.destroy()
477
-
478
- try {
479
- await fs.unlink(encFilePath)
480
- if (originalFilePath) {
481
- await fs.unlink(originalFilePath)
482
- }
483
- } catch (err) {
484
- logger?.error({ err }, 'failed deleting tmp files')
485
- }
486
-
487
- throw error
488
- }
489
- }
490
-
491
- const DEF_HOST = 'mmg.whatsapp.net'
492
- const AES_CHUNK_SIZE = 16
493
-
494
- const toSmallestChunkSize = (num: number) => {
495
- return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE
496
- }
497
-
498
- export type MediaDownloadOptions = {
499
- startByte?: number
500
- endByte?: number
501
- options?: RequestInit
502
- }
503
-
504
- export const getUrlFromDirectPath = (directPath: string) => `https://${DEF_HOST}${directPath}`
505
-
506
- export const downloadContentFromMessage = async (
507
- { mediaKey, directPath, url }: DownloadableMessage,
508
- type: MediaType,
509
- opts: MediaDownloadOptions = {}
510
- ) => {
511
- const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/')
512
- const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath!)
513
- if (!downloadUrl) {
514
- throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 })
515
- }
516
-
517
- const keys = await getMediaKeys(mediaKey, type)
518
-
519
- return downloadEncryptedContent(downloadUrl, keys, opts)
520
- }
521
-
522
- /**
523
- * Decrypts and downloads an AES256-CBC encrypted file given the keys.
524
- * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
525
- * */
526
- export const downloadEncryptedContent = async (
527
- downloadUrl: string,
528
- { cipherKey, iv }: MediaDecryptionKeyInfo,
529
- { startByte, endByte, options }: MediaDownloadOptions = {}
530
- ) => {
531
- let bytesFetched = 0
532
- let startChunk = 0
533
- let firstBlockIsIV = false
534
- // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
535
- if (startByte) {
536
- const chunk = toSmallestChunkSize(startByte || 0)
537
- if (chunk) {
538
- startChunk = chunk - AES_CHUNK_SIZE
539
- bytesFetched = chunk
540
-
541
- firstBlockIsIV = true
542
- }
543
- }
544
-
545
- const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
546
-
547
- const headersInit = options?.headers ? options.headers : undefined
548
- const headers: Record<string, string> = {
549
- ...(headersInit
550
- ? Array.isArray(headersInit)
551
- ? Object.fromEntries(headersInit)
552
- : (headersInit as Record<string, string>)
553
- : {}),
554
- Origin: DEFAULT_ORIGIN
555
- }
556
- if (startChunk || endChunk) {
557
- headers.Range = `bytes=${startChunk}-`
558
- if (endChunk) {
559
- headers.Range += endChunk
560
- }
561
- }
562
-
563
- // download the message
564
- const fetched = await getHttpStream(downloadUrl, {
565
- ...(options || {}),
566
- headers
567
- })
568
-
569
- let remainingBytes = Buffer.from([])
570
-
571
- let aes: Crypto.Decipher
572
-
573
- const pushBytes = (bytes: Buffer, push: (bytes: Buffer) => void) => {
574
- if (startByte || endByte) {
575
- const start = bytesFetched >= startByte! ? undefined : Math.max(startByte! - bytesFetched, 0)
576
- const end = bytesFetched + bytes.length < endByte! ? undefined : Math.max(endByte! - bytesFetched, 0)
577
-
578
- push(bytes.slice(start, end))
579
-
580
- bytesFetched += bytes.length
581
- } else {
582
- push(bytes)
583
- }
584
- }
585
-
586
- const output = new Transform({
587
- transform(chunk, _, callback) {
588
- let data = Buffer.concat([remainingBytes, chunk])
589
-
590
- const decryptLength = toSmallestChunkSize(data.length)
591
- remainingBytes = data.slice(decryptLength)
592
- data = data.slice(0, decryptLength)
593
-
594
- if (!aes) {
595
- let ivValue = iv
596
- if (firstBlockIsIV) {
597
- ivValue = data.slice(0, AES_CHUNK_SIZE)
598
- data = data.slice(AES_CHUNK_SIZE)
599
- }
600
-
601
- aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue)
602
- // if an end byte that is not EOF is specified
603
- // stop auto padding (PKCS7) -- otherwise throws an error for decryption
604
- if (endByte) {
605
- aes.setAutoPadding(false)
606
- }
607
- }
608
-
609
- try {
610
- pushBytes(aes.update(data), b => this.push(b))
611
- callback()
612
- } catch (error: any) {
613
- callback(error)
614
- }
615
- },
616
- final(callback) {
617
- try {
618
- pushBytes(aes.final(), b => this.push(b))
619
- callback()
620
- } catch (error: any) {
621
- callback(error)
622
- }
623
- }
624
- })
625
- return fetched.pipe(output, { end: true })
626
- }
627
-
628
- export function extensionForMediaMessage(message: WAMessageContent) {
629
- const getExtension = (mimetype: string) => mimetype.split(';')[0]?.split('/')[1]
630
- const type = Object.keys(message)[0] as Exclude<MessageType, 'toJSON'>
631
- let extension: string
632
- if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') {
633
- extension = '.jpeg'
634
- } else {
635
- const messageContent = message[type] as WAGenericMediaMessage
636
- extension = getExtension(messageContent.mimetype!)!
637
- }
638
-
639
- return extension
640
- }
641
-
642
- export const getWAUploadToServer = (
643
- { customUploadHosts, fetchAgent, logger, options }: SocketConfig,
644
- refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
645
- ): WAMediaUploadFunction => {
646
- return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
647
- // send a query JSON to obtain the url & auth token to upload our media
648
- let uploadInfo = await refreshMediaConn(false)
649
-
650
- let urls: { mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number } | undefined
651
- const hosts = [...customUploadHosts, ...uploadInfo.hosts]
652
-
653
- fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
654
-
655
- for (const { hostname } of hosts) {
656
- logger.debug(`uploading to "${hostname}"`)
657
-
658
- const auth = encodeURIComponent(uploadInfo.auth) // the auth token
659
- const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
660
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
661
- let result: any
662
- try {
663
- const stream = createReadStream(filePath)
664
- const response = await fetch(url, {
665
- dispatcher: fetchAgent,
666
- method: 'POST',
667
- body: stream as any,
668
- headers: {
669
- ...(() => {
670
- const hdrs = options?.headers
671
- if (!hdrs) return {}
672
- return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : (hdrs as Record<string, string>)
673
- })(),
674
- 'Content-Type': 'application/octet-stream',
675
- Origin: DEFAULT_ORIGIN
676
- },
677
- duplex: 'half',
678
- // Note: custom agents/proxy require undici Agent; omitted here.
679
- signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
680
- })
681
- let parsed: any = undefined
682
- try {
683
- parsed = await response.json()
684
- } catch {
685
- parsed = undefined
686
- }
687
-
688
- result = parsed
689
-
690
- if (result?.url || result?.directPath) {
691
- urls = {
692
- mediaUrl: result.url,
693
- directPath: result.direct_path,
694
- meta_hmac: result.meta_hmac,
695
- fbid: result.fbid,
696
- ts: result.ts
697
- }
698
- break
699
- } else {
700
- uploadInfo = await refreshMediaConn(true)
701
- throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
702
- }
703
- } catch (error: any) {
704
- const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
705
- logger.warn(
706
- { trace: error?.stack, uploadResult: result },
707
- `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
708
- )
709
- }
710
- }
711
-
712
- if (!urls) {
713
- throw new Boom('Media upload failed on all hosts', { statusCode: 500 })
714
- }
715
-
716
- return urls
717
- }
718
- }
719
-
720
- const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => {
721
- return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
722
- }
723
-
724
- /**
725
- * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
726
- */
727
- export const encryptMediaRetryRequest = async (key: WAMessageKey, mediaKey: Buffer | Uint8Array, meId: string) => {
728
- const recp: proto.IServerErrorReceipt = { stanzaId: key.id }
729
- const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
730
-
731
- const iv = Crypto.randomBytes(12)
732
- const retryKey = await getMediaRetryKey(mediaKey)
733
- const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id!))
734
-
735
- const req: BinaryNode = {
736
- tag: 'receipt',
737
- attrs: {
738
- id: key.id!,
739
- to: jidNormalizedUser(meId),
740
- type: 'server-error'
741
- },
742
- content: [
743
- // this encrypt node is actually pretty useless
744
- // the media is returned even without this node
745
- // keeping it here to maintain parity with WA Web
746
- {
747
- tag: 'encrypt',
748
- attrs: {},
749
- content: [
750
- { tag: 'enc_p', attrs: {}, content: ciphertext },
751
- { tag: 'enc_iv', attrs: {}, content: iv }
752
- ]
753
- },
754
- {
755
- tag: 'rmr',
756
- attrs: {
757
- jid: key.remoteJid!,
758
- from_me: (!!key.fromMe).toString(),
759
- // @ts-ignore
760
- participant: key.participant || undefined
761
- }
762
- }
763
- ]
764
- }
765
-
766
- return req
767
- }
768
-
769
- export const decodeMediaRetryNode = (node: BinaryNode) => {
770
- const rmrNode = getBinaryNodeChild(node, 'rmr')!
771
-
772
- const event: BaileysEventMap['messages.media-update'][number] = {
773
- key: {
774
- id: node.attrs.id,
775
- remoteJid: rmrNode.attrs.jid,
776
- fromMe: rmrNode.attrs.from_me === 'true',
777
- participant: rmrNode.attrs.participant
778
- }
779
- }
780
-
781
- const errorNode = getBinaryNodeChild(node, 'error')
782
- if (errorNode) {
783
- const errorCode = +errorNode.attrs.code!
784
- event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
785
- data: errorNode.attrs,
786
- statusCode: getStatusCodeForMediaRetry(errorCode)
787
- })
788
- } else {
789
- const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
790
- const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
791
- const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
792
- if (ciphertext && iv) {
793
- event.media = { ciphertext, iv }
794
- } else {
795
- event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
796
- }
797
- }
798
-
799
- return event
800
- }
801
-
802
- export const decryptMediaRetryData = async (
803
- { ciphertext, iv }: { ciphertext: Uint8Array; iv: Uint8Array },
804
- mediaKey: Uint8Array,
805
- msgId: string
806
- ) => {
807
- const retryKey = await getMediaRetryKey(mediaKey)
808
- const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
809
- return proto.MediaRetryNotification.decode(plaintext)
810
- }
811
-
812
- export const getStatusCodeForMediaRetry = (code: number) =>
813
- MEDIA_RETRY_STATUS_MAP[code as proto.MediaRetryNotification.ResultType]
814
-
815
- const MEDIA_RETRY_STATUS_MAP = {
816
- [proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
817
- [proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
818
- [proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
819
- [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
820
- } as const