@sixcore/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.
Files changed (278) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/WAProto/GenerateStatics.sh +4 -0
  4. package/WAProto/WAProto.proto +4357 -0
  5. package/WAProto/index.d.ts +50383 -0
  6. package/WAProto/index.js +155693 -0
  7. package/WASignalGroup/GroupProtocol.js +1697 -0
  8. package/WASignalGroup/ciphertext_message.js +16 -0
  9. package/WASignalGroup/generate-proto.sh +1 -0
  10. package/WASignalGroup/group.proto +42 -0
  11. package/WASignalGroup/group_cipher.js +120 -0
  12. package/WASignalGroup/group_session_builder.js +46 -0
  13. package/WASignalGroup/index.js +5 -0
  14. package/WASignalGroup/keyhelper.js +21 -0
  15. package/WASignalGroup/protobufs.js +3 -0
  16. package/WASignalGroup/queue_job.js +69 -0
  17. package/WASignalGroup/sender_chain_key.js +50 -0
  18. package/WASignalGroup/sender_key_distribution_message.js +78 -0
  19. package/WASignalGroup/sender_key_message.js +92 -0
  20. package/WASignalGroup/sender_key_name.js +70 -0
  21. package/WASignalGroup/sender_key_record.js +56 -0
  22. package/WASignalGroup/sender_key_state.js +129 -0
  23. package/WASignalGroup/sender_message_key.js +39 -0
  24. package/lib/Defaults/baileys-version.json +3 -0
  25. package/lib/Defaults/index.d.ts +53 -0
  26. package/lib/Defaults/index.js +108 -0
  27. package/lib/Signal/libsignal.d.ts +3 -0
  28. package/lib/Signal/libsignal.js +152 -0
  29. package/lib/Socket/Client/abstract-socket-client.d.ts +17 -0
  30. package/lib/Socket/Client/abstract-socket-client.js +13 -0
  31. package/lib/Socket/Client/index.d.ts +3 -0
  32. package/lib/Socket/Client/index.js +19 -0
  33. package/lib/Socket/Client/mobile-socket-client.d.ts +13 -0
  34. package/lib/Socket/Client/mobile-socket-client.js +65 -0
  35. package/lib/Socket/Client/web-socket-client.d.ts +12 -0
  36. package/lib/Socket/Client/web-socket-client.js +62 -0
  37. package/lib/Socket/business.d.ts +170 -0
  38. package/lib/Socket/business.js +260 -0
  39. package/lib/Socket/chats.d.ts +81 -0
  40. package/lib/Socket/chats.js +950 -0
  41. package/lib/Socket/groups.d.ts +115 -0
  42. package/lib/Socket/groups.js +315 -0
  43. package/lib/Socket/index.d.ts +172 -0
  44. package/lib/Socket/index.js +10 -0
  45. package/lib/Socket/messages-recv.d.ts +158 -0
  46. package/lib/Socket/messages-recv.js +972 -0
  47. package/lib/Socket/messages-send.d.ts +155 -0
  48. package/lib/Socket/messages-send.js +1087 -0
  49. package/lib/Socket/newsletter.d.ts +132 -0
  50. package/lib/Socket/newsletter.js +236 -0
  51. package/lib/Socket/registration.d.ts +264 -0
  52. package/lib/Socket/registration.js +166 -0
  53. package/lib/Socket/socket.d.ts +44 -0
  54. package/lib/Socket/socket.js +643 -0
  55. package/lib/Socket/usync.d.ts +37 -0
  56. package/lib/Socket/usync.js +70 -0
  57. package/lib/Store/index.d.ts +3 -0
  58. package/lib/Store/index.js +10 -0
  59. package/lib/Store/make-cache-manager-store.d.ts +14 -0
  60. package/lib/Store/make-cache-manager-store.js +83 -0
  61. package/lib/Store/make-in-memory-store.d.ts +118 -0
  62. package/lib/Store/make-in-memory-store.js +431 -0
  63. package/lib/Store/make-ordered-dictionary.d.ts +13 -0
  64. package/lib/Store/make-ordered-dictionary.js +81 -0
  65. package/lib/Store/object-repository.d.ts +10 -0
  66. package/lib/Store/object-repository.js +27 -0
  67. package/lib/Types/Auth.d.ts +109 -0
  68. package/lib/Types/Auth.js +2 -0
  69. package/lib/Types/Call.d.ts +13 -0
  70. package/lib/Types/Call.js +2 -0
  71. package/lib/Types/Chat.d.ts +107 -0
  72. package/lib/Types/Chat.js +4 -0
  73. package/lib/Types/Contact.d.ts +19 -0
  74. package/lib/Types/Contact.js +2 -0
  75. package/lib/Types/Events.d.ts +172 -0
  76. package/lib/Types/Events.js +2 -0
  77. package/lib/Types/GroupMetadata.d.ts +56 -0
  78. package/lib/Types/GroupMetadata.js +2 -0
  79. package/lib/Types/Label.d.ts +46 -0
  80. package/lib/Types/Label.js +27 -0
  81. package/lib/Types/LabelAssociation.d.ts +29 -0
  82. package/lib/Types/LabelAssociation.js +9 -0
  83. package/lib/Types/Message.d.ts +433 -0
  84. package/lib/Types/Message.js +9 -0
  85. package/lib/Types/Newsletter.d.ts +92 -0
  86. package/lib/Types/Newsletter.js +32 -0
  87. package/lib/Types/Product.d.ts +78 -0
  88. package/lib/Types/Product.js +2 -0
  89. package/lib/Types/Signal.d.ts +57 -0
  90. package/lib/Types/Signal.js +2 -0
  91. package/lib/Types/Socket.d.ts +116 -0
  92. package/lib/Types/Socket.js +2 -0
  93. package/lib/Types/State.d.ts +27 -0
  94. package/lib/Types/State.js +2 -0
  95. package/lib/Types/USync.d.ts +25 -0
  96. package/lib/Types/USync.js +2 -0
  97. package/lib/Types/index.d.ts +66 -0
  98. package/lib/Types/index.js +42 -0
  99. package/lib/Utils/auth-utils.d.ts +18 -0
  100. package/lib/Utils/auth-utils.js +227 -0
  101. package/lib/Utils/baileys-event-stream.d.ts +16 -0
  102. package/lib/Utils/baileys-event-stream.js +63 -0
  103. package/lib/Utils/business.d.ts +22 -0
  104. package/lib/Utils/business.js +234 -0
  105. package/lib/Utils/chat-utils.d.ts +70 -0
  106. package/lib/Utils/chat-utils.js +745 -0
  107. package/lib/Utils/crypto.d.ts +40 -0
  108. package/lib/Utils/crypto.js +199 -0
  109. package/lib/Utils/decode-wa-message.d.ts +36 -0
  110. package/lib/Utils/decode-wa-message.js +234 -0
  111. package/lib/Utils/event-buffer.d.ts +35 -0
  112. package/lib/Utils/event-buffer.js +517 -0
  113. package/lib/Utils/generics.d.ts +88 -0
  114. package/lib/Utils/generics.js +402 -0
  115. package/lib/Utils/history.d.ts +19 -0
  116. package/lib/Utils/history.js +94 -0
  117. package/lib/Utils/index.d.ts +17 -0
  118. package/lib/Utils/index.js +33 -0
  119. package/lib/Utils/link-preview.d.ts +21 -0
  120. package/lib/Utils/link-preview.js +93 -0
  121. package/lib/Utils/logger.d.ts +2 -0
  122. package/lib/Utils/logger.js +7 -0
  123. package/lib/Utils/lt-hash.d.ts +12 -0
  124. package/lib/Utils/lt-hash.js +51 -0
  125. package/lib/Utils/make-mutex.d.ts +7 -0
  126. package/lib/Utils/make-mutex.js +43 -0
  127. package/lib/Utils/messages-media.d.ts +113 -0
  128. package/lib/Utils/messages-media.js +643 -0
  129. package/lib/Utils/messages.d.ts +77 -0
  130. package/lib/Utils/messages.js +1221 -0
  131. package/lib/Utils/noise-handler.d.ts +20 -0
  132. package/lib/Utils/noise-handler.js +160 -0
  133. package/lib/Utils/process-message.d.ts +41 -0
  134. package/lib/Utils/process-message.js +373 -0
  135. package/lib/Utils/signal.d.ts +33 -0
  136. package/lib/Utils/signal.js +159 -0
  137. package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
  138. package/lib/Utils/use-multi-file-auth-state.js +295 -0
  139. package/lib/Utils/use-single-file-auth-state.d.ts +12 -0
  140. package/lib/Utils/use-single-file-auth-state.js +75 -0
  141. package/lib/Utils/validate-connection.d.ts +11 -0
  142. package/lib/Utils/validate-connection.js +205 -0
  143. package/lib/WABinary/constants.d.ts +27 -0
  144. package/lib/WABinary/constants.js +40 -0
  145. package/lib/WABinary/decode.d.ts +6 -0
  146. package/lib/WABinary/decode.js +264 -0
  147. package/lib/WABinary/encode.d.ts +2 -0
  148. package/lib/WABinary/encode.js +252 -0
  149. package/lib/WABinary/generic-utils.d.ts +14 -0
  150. package/lib/WABinary/generic-utils.js +110 -0
  151. package/lib/WABinary/index.d.ts +5 -0
  152. package/lib/WABinary/index.js +21 -0
  153. package/lib/WABinary/jid-utils.d.ts +31 -0
  154. package/lib/WABinary/jid-utils.js +62 -0
  155. package/lib/WABinary/types.d.ts +18 -0
  156. package/lib/WABinary/types.js +2 -0
  157. package/lib/WAM/BinaryInfo.d.ts +8 -0
  158. package/lib/WAM/BinaryInfo.js +13 -0
  159. package/lib/WAM/constants.d.ts +38 -0
  160. package/lib/WAM/constants.js +15350 -0
  161. package/lib/WAM/encode.d.ts +2 -0
  162. package/lib/WAM/encode.js +155 -0
  163. package/lib/WAM/index.d.ts +3 -0
  164. package/lib/WAM/index.js +19 -0
  165. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.js +32 -0
  167. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
  168. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  169. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
  170. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  171. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
  172. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
  173. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
  174. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +53 -0
  175. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +8 -0
  176. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -0
  177. package/lib/WAUSync/Protocols/index.d.ts +4 -0
  178. package/lib/WAUSync/Protocols/index.js +20 -0
  179. package/lib/WAUSync/USyncQuery.d.ts +28 -0
  180. package/lib/WAUSync/USyncQuery.js +89 -0
  181. package/lib/WAUSync/USyncUser.d.ts +10 -0
  182. package/lib/WAUSync/USyncUser.js +26 -0
  183. package/lib/WAUSync/index.d.ts +3 -0
  184. package/lib/WAUSync/index.js +19 -0
  185. package/lib/index.js +31 -0
  186. package/package.json +51 -0
  187. package/src/Defaults/baileys-version.json +3 -0
  188. package/src/Defaults/index.ts +133 -0
  189. package/src/Signal/Group/ciphertext-message.ts +9 -0
  190. package/src/Signal/Group/group-session-builder.ts +56 -0
  191. package/src/Signal/Group/group_cipher.ts +117 -0
  192. package/src/Signal/Group/index.ts +11 -0
  193. package/src/Signal/Group/keyhelper.ts +28 -0
  194. package/src/Signal/Group/sender-chain-key.ts +34 -0
  195. package/src/Signal/Group/sender-key-distribution-message.ts +95 -0
  196. package/src/Signal/Group/sender-key-message.ts +96 -0
  197. package/src/Signal/Group/sender-key-name.ts +66 -0
  198. package/src/Signal/Group/sender-key-record.ts +69 -0
  199. package/src/Signal/Group/sender-key-state.ts +134 -0
  200. package/src/Signal/Group/sender-message-key.ts +36 -0
  201. package/src/Signal/libsignal.ts +447 -0
  202. package/src/Signal/lid-mapping.ts +209 -0
  203. package/src/Socket/Client/index.ts +2 -0
  204. package/src/Socket/Client/types.ts +22 -0
  205. package/src/Socket/Client/websocket.ts +56 -0
  206. package/src/Socket/business.ts +421 -0
  207. package/src/Socket/chats.ts +1223 -0
  208. package/src/Socket/communities.ts +477 -0
  209. package/src/Socket/groups.ts +361 -0
  210. package/src/Socket/index.ts +22 -0
  211. package/src/Socket/messages-recv.ts +1563 -0
  212. package/src/Socket/messages-send.ts +1210 -0
  213. package/src/Socket/mex.ts +58 -0
  214. package/src/Socket/newsletter.ts +229 -0
  215. package/src/Socket/socket.ts +1072 -0
  216. package/src/Types/Auth.ts +115 -0
  217. package/src/Types/Bussines.ts +20 -0
  218. package/src/Types/Call.ts +14 -0
  219. package/src/Types/Chat.ts +138 -0
  220. package/src/Types/Contact.ts +24 -0
  221. package/src/Types/Events.ts +132 -0
  222. package/src/Types/GroupMetadata.ts +70 -0
  223. package/src/Types/Label.ts +48 -0
  224. package/src/Types/LabelAssociation.ts +35 -0
  225. package/src/Types/Message.ts +424 -0
  226. package/src/Types/Newsletter.ts +98 -0
  227. package/src/Types/Product.ts +85 -0
  228. package/src/Types/Signal.ts +76 -0
  229. package/src/Types/Socket.ts +150 -0
  230. package/src/Types/State.ts +43 -0
  231. package/src/Types/USync.ts +27 -0
  232. package/src/Types/globals.d.ts +8 -0
  233. package/src/Types/index.ts +67 -0
  234. package/src/Utils/auth-utils.ts +331 -0
  235. package/src/Utils/browser-utils.ts +31 -0
  236. package/src/Utils/business.ts +286 -0
  237. package/src/Utils/chat-utils.ts +933 -0
  238. package/src/Utils/crypto.ts +184 -0
  239. package/src/Utils/decode-wa-message.ts +355 -0
  240. package/src/Utils/event-buffer.ts +662 -0
  241. package/src/Utils/generics.ts +470 -0
  242. package/src/Utils/history.ts +114 -0
  243. package/src/Utils/index.ts +18 -0
  244. package/src/Utils/link-preview.ts +111 -0
  245. package/src/Utils/logger.ts +13 -0
  246. package/src/Utils/lt-hash.ts +65 -0
  247. package/src/Utils/make-mutex.ts +45 -0
  248. package/src/Utils/message-retry-manager.ts +229 -0
  249. package/src/Utils/messages-media.ts +820 -0
  250. package/src/Utils/messages.ts +1137 -0
  251. package/src/Utils/noise-handler.ts +192 -0
  252. package/src/Utils/pre-key-manager.ts +126 -0
  253. package/src/Utils/process-message.ts +622 -0
  254. package/src/Utils/signal.ts +214 -0
  255. package/src/Utils/use-multi-file-auth-state.ts +136 -0
  256. package/src/Utils/validate-connection.ts +253 -0
  257. package/src/WABinary/constants.ts +1305 -0
  258. package/src/WABinary/decode.ts +281 -0
  259. package/src/WABinary/encode.ts +253 -0
  260. package/src/WABinary/generic-utils.ts +127 -0
  261. package/src/WABinary/index.ts +5 -0
  262. package/src/WABinary/jid-utils.ts +128 -0
  263. package/src/WABinary/types.ts +17 -0
  264. package/src/WAM/BinaryInfo.ts +12 -0
  265. package/src/WAM/constants.ts +22889 -0
  266. package/src/WAM/encode.ts +169 -0
  267. package/src/WAM/index.ts +3 -0
  268. package/src/WAUSync/Protocols/USyncContactProtocol.ts +32 -0
  269. package/src/WAUSync/Protocols/USyncDeviceProtocol.ts +78 -0
  270. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.ts +35 -0
  271. package/src/WAUSync/Protocols/USyncStatusProtocol.ts +44 -0
  272. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.ts +76 -0
  273. package/src/WAUSync/Protocols/UsyncLIDProtocol.ts +33 -0
  274. package/src/WAUSync/Protocols/index.ts +4 -0
  275. package/src/WAUSync/USyncQuery.ts +133 -0
  276. package/src/WAUSync/USyncUser.ts +32 -0
  277. package/src/WAUSync/index.ts +3 -0
  278. package/src/index.ts +13 -0
@@ -0,0 +1,820 @@
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