mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: add media retry for MD
This commit is contained in:
@@ -10,9 +10,11 @@ import { join } from 'path'
|
||||
import type { Logger } from 'pino'
|
||||
import { Readable, Transform } from 'stream'
|
||||
import { URL } from 'url'
|
||||
import { proto } from '../../WAProto'
|
||||
import { DEFAULT_ORIGIN, MEDIA_PATH_MAP } from '../Defaults'
|
||||
import { CommonSocketConfig, DownloadableMessage, MediaConnInfo, MediaDecryptionKeyInfo, MediaType, MessageType, WAGenericMediaMessage, WAMediaUpload, WAMediaUploadFunction, WAMessageContent, WAProto } from '../Types'
|
||||
import { hkdf } from './crypto'
|
||||
import { BaileysEventMap, CommonSocketConfig, DownloadableMessage, MediaConnInfo, MediaDecryptionKeyInfo, MediaType, MessageType, WAGenericMediaMessage, WAMediaUpload, WAMediaUploadFunction, WAMessageContent, WAProto } from '../Types'
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary'
|
||||
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto'
|
||||
import { generateMessageID } from './generics'
|
||||
|
||||
const getTmpFilesDirectory = () => tmpdir()
|
||||
@@ -344,12 +346,14 @@ export type MediaDownloadOptions = {
|
||||
endByte?: number
|
||||
}
|
||||
|
||||
export const getUrlFromDirectPath = (directPath: string) => `https://${DEF_HOST}${directPath}`
|
||||
|
||||
export const downloadContentFromMessage = (
|
||||
{ mediaKey, directPath, url }: DownloadableMessage,
|
||||
type: MediaType,
|
||||
opts: MediaDownloadOptions = { }
|
||||
) => {
|
||||
const downloadUrl = url || `https://${DEF_HOST}${directPath}`
|
||||
const downloadUrl = url || getUrlFromDirectPath(directPath)
|
||||
const keys = getMediaKeys(mediaKey, type)
|
||||
|
||||
return downloadEncryptedContent(downloadUrl, keys, opts)
|
||||
@@ -558,4 +562,98 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger }: C
|
||||
|
||||
return urls
|
||||
}
|
||||
}
|
||||
|
||||
const GCM_AUTH_TAG_LENGTH: number | undefined = 128 >> 3
|
||||
|
||||
const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => {
|
||||
return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
||||
*/
|
||||
export const encryptMediaRetryRequest = (
|
||||
key: proto.IMessageKey,
|
||||
mediaKey: Buffer | Uint8Array,
|
||||
meId: string
|
||||
) => {
|
||||
const recp: proto.IServerErrorReceipt = { stanzaId: key.id }
|
||||
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
|
||||
|
||||
const iv = Crypto.randomBytes(12)
|
||||
const retryKey = getMediaRetryKey(mediaKey)
|
||||
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id))
|
||||
|
||||
const req: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: key.id,
|
||||
to: jidNormalizedUser(meId),
|
||||
type: 'server-error'
|
||||
},
|
||||
content: [
|
||||
// this encrypt node is actually pretty useless
|
||||
// the media is returned even without this node
|
||||
// keeping it here to maintain parity with WA Web
|
||||
{
|
||||
tag: 'encrypt',
|
||||
attrs: { },
|
||||
content: [
|
||||
{ tag: 'enc_p', attrs: { }, content: ciphertext },
|
||||
{ tag: 'enc_iv', attrs: { }, content: iv }
|
||||
]
|
||||
},
|
||||
{
|
||||
tag: 'rmr',
|
||||
attrs: {
|
||||
jid: key.remoteJid,
|
||||
from_me: (!!key.fromMe).toString(),
|
||||
participant: key.participant || undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
export const decodeMediaRetryNode = (node: BinaryNode) => {
|
||||
const rmrNode = getBinaryNodeChild(node, 'rmr')
|
||||
|
||||
const event: BaileysEventMap<any>['messages.media-update'][number] = {
|
||||
key: {
|
||||
id: node.attrs.id,
|
||||
remoteJid: rmrNode.attrs.jid,
|
||||
fromMe: rmrNode.attrs.from_me === 'true',
|
||||
participant: rmrNode.attrs.participant
|
||||
}
|
||||
}
|
||||
|
||||
const errorNode = getBinaryNodeChild(node, 'error')
|
||||
if(errorNode) {
|
||||
const errorCode = +errorNode.attrs.code
|
||||
event.error = new Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs })
|
||||
} else {
|
||||
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt')
|
||||
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
|
||||
const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
|
||||
if(ciphertext && iv) {
|
||||
event.media = { ciphertext, iv }
|
||||
} else {
|
||||
event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
export const decryptMediaRetryData = (
|
||||
{ ciphertext, iv }: { ciphertext: Uint8Array, iv: Uint8Array },
|
||||
mediaKey: Uint8Array,
|
||||
msgId: string
|
||||
) => {
|
||||
const retryKey = getMediaRetryKey(mediaKey)
|
||||
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
|
||||
return proto.MediaRetryNotification.decode(plaintext)
|
||||
}
|
||||
Reference in New Issue
Block a user