gifted-baileys 1.5.5 → 1.5.7

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