Merge branch 'master' of https://github.com/WhiskeySockets/Baileys into fix-eslint-prettier-editorconfig-rules

This commit is contained in:
canove
2025-05-28 22:42:28 -03:00
24 changed files with 16429 additions and 834 deletions

View File

@@ -1,3 +1,3 @@
{
"version": [2, 3000, 1020608496]
"version": [2, 3000, 1023223821]
}

View File

@@ -634,7 +634,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
isLid ? authState.creds.me?.lid : authState.creds.me?.id
)
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe)
const fromMe = !attrs.recipient || (
(attrs.type === 'retry' || attrs.type === 'sender')
&& isNodeFromMe
)
const key: proto.IMessageKey = {
remoteJid,

View File

@@ -264,10 +264,7 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
userJid: string
}
export type WAMediaUploadFunction = (
readStream: Readable,
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number }
) => Promise<{ mediaUrl: string; directPath: string }>
export type WAMediaUploadFunction = (encFilePath: string, opts: { fileEncSha256B64: string, mediaType: MediaType, timeoutMs?: number }) => Promise<{ mediaUrl: string, directPath: string }>
export type MediaGenerationOptions = {
logger?: ILogger

View File

@@ -1,18 +1,12 @@
import { Boom } from '@hapi/boom'
import { createHash } from 'crypto'
import {
CatalogCollection,
CatalogStatus,
OrderDetails,
OrderProduct,
Product,
ProductCreate,
ProductUpdate,
WAMediaUpload,
WAMediaUploadFunction
} from '../Types'
import { createWriteStream, promises as fs } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { CatalogCollection, CatalogStatus, OrderDetails, OrderProduct, Product, ProductCreate, ProductUpdate, WAMediaUpload, WAMediaUploadFunction } from '../Types'
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'
import { getStream, getUrlFromDirectPath, toReadable } from './messages-media'
import { generateMessageIDV2 } from './generics'
import { getStream, getUrlFromDirectPath } from './messages-media'
export const parseCatalogNode = (node: BinaryNode) => {
const catalogNode = getBinaryNodeChild(node, 'product_catalog')
@@ -238,23 +232,35 @@ export const uploadingNecessaryImages = async (
}
}
const { stream } = await getStream(img)
const hasher = createHash('sha256')
const contentBlocks: Buffer[] = []
for await (const block of stream) {
hasher.update(block)
contentBlocks.push(block)
}
const { stream } = await getStream(img)
const hasher = createHash('sha256')
const filePath = join(tmpdir(), 'img' + generateMessageIDV2())
const encFileWriteStream = createWriteStream(filePath)
for await (const block of stream) {
hasher.update(block)
encFileWriteStream.write(block)
}
const sha = hasher.digest('base64')
const { directPath } = await waUploadToServer(toReadable(Buffer.concat(contentBlocks)), {
mediaType: 'product-catalog-image',
fileEncSha256B64: sha,
timeoutMs
})
return { url: getUrlFromDirectPath(directPath) }
})
const { directPath } = await waUploadToServer(
filePath,
{
mediaType: 'product-catalog-image',
fileEncSha256B64: sha,
timeoutMs
}
)
await fs
.unlink(filePath)
.catch(err => console.log('Error deleting temp file ', err))
return { url: getUrlFromDirectPath(directPath) }
}
)
)
return results
}

View File

@@ -36,7 +36,7 @@ export const Browsers: BrowsersMap = {
export const getPlatformId = (browser: string) => {
const platformType = proto.DeviceProps.PlatformType[browser.toUpperCase()]
return platformType ? platformType.toString().charCodeAt(0).toString() : '49' //chrome
return platformType ? platformType.toString() : '1' //chrome
}
export const BufferJSON = {

View File

@@ -11,20 +11,7 @@ import { Readable, Transform } from 'stream'
import { URL } from 'url'
import { proto } from '../../WAProto'
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults'
import {
BaileysEventMap,
DownloadableMessage,
MediaConnInfo,
MediaDecryptionKeyInfo,
MediaType,
MessageType,
SocketConfig,
WAGenericMediaMessage,
WAMediaPayloadURL,
WAMediaUpload,
WAMediaUploadFunction,
WAMessageContent
} from '../Types'
import { BaileysEventMap, DownloadableMessage, MediaConnInfo, MediaDecryptionKeyInfo, MediaType, MessageType, SocketConfig, WAGenericMediaMessage, WAMediaUpload, WAMediaUploadFunction, WAMessageContent } from '../Types'
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary'
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto'
import { generateMessageIDV2 } from './generics'
@@ -339,24 +326,35 @@ export const encryptedStream = async (
const mediaKey = Crypto.randomBytes(32)
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
const encWriteStream = new Readable({ read: () => {} })
let bodyPath: string | undefined
let writeStream: WriteStream | undefined
let didSaveToTmpPath = false
if (type === 'file') {
bodyPath = (media as WAMediaPayloadURL).url.toString()
} else if (saveOriginalFileIfRequired) {
bodyPath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2())
writeStream = createWriteStream(bodyPath)
didSaveToTmpPath = true
const encFilePath = join(
getTmpFilesDirectory(),
mediaType + generateMessageIDV2() + '-enc'
)
const encFileWriteStream = createWriteStream(encFilePath)
let originalFileStream: WriteStream | undefined
let originalFilePath: string | undefined
if(saveOriginalFileIfRequired) {
originalFilePath = join(
getTmpFilesDirectory(),
mediaType + generateMessageIDV2() + '-original'
)
originalFileStream = createWriteStream(originalFilePath)
}
let fileLength = 0
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
let hmac = Crypto.createHmac('sha256', macKey!).update(iv)
let sha256Plain = Crypto.createHash('sha256')
let sha256Enc = Crypto.createHash('sha256')
const hmac = Crypto.createHmac('sha256', macKey!).update(iv)
const sha256Plain = Crypto.createHash('sha256')
const sha256Enc = Crypto.createHash('sha256')
const onChunk = (buff: Buffer) => {
sha256Enc.update(buff)
hmac.update(buff)
encFileWriteStream.write(buff)
}
try {
for await (const data of stream) {
@@ -368,66 +366,63 @@ export const encryptedStream = async (
})
}
sha256Plain = sha256Plain.update(data)
if (writeStream && !writeStream.write(data)) {
await once(writeStream, 'drain')
if(originalFileStream) {
if(!originalFileStream.write(data)) {
await once(originalFileStream, 'drain')
}
}
sha256Plain.update(data)
onChunk(aes.update(data))
}
onChunk(aes.final())
const mac = hmac.digest().slice(0, 10)
sha256Enc = sha256Enc.update(mac)
sha256Enc.update(mac)
const fileSha256 = sha256Plain.digest()
const fileEncSha256 = sha256Enc.digest()
encWriteStream.push(mac)
encWriteStream.push(null)
encFileWriteStream.write(mac)
writeStream?.end()
encFileWriteStream.end()
originalFileStream?.end?.()
stream.destroy()
logger?.debug('encrypted data successfully')
return {
mediaKey,
encWriteStream,
bodyPath,
originalFilePath,
encFilePath,
mac,
fileEncSha256,
fileSha256,
fileLength,
didSaveToTmpPath
fileLength
}
} catch (error) {
// destroy all streams with error
encWriteStream.destroy()
writeStream?.destroy()
encFileWriteStream.destroy()
originalFileStream?.destroy?.()
aes.destroy()
hmac.destroy()
sha256Plain.destroy()
sha256Enc.destroy()
stream.destroy()
if (didSaveToTmpPath) {
try {
await fs.unlink(bodyPath!)
} catch (err) {
logger?.error({ err }, 'failed to save to tmp path')
try {
await fs.unlink(encFilePath)
if(originalFilePath) {
await fs.unlink(originalFilePath)
}
} catch(err) {
logger?.error({ err }, 'failed deleting tmp files')
}
throw error
}
function onChunk(buff: Buffer) {
sha256Enc = sha256Enc.update(buff)
hmac = hmac.update(buff)
encWriteStream.push(buff)
}
}
const DEF_HOST = 'mmg.whatsapp.net'
@@ -577,7 +572,7 @@ export const getWAUploadToServer = (
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig,
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
): WAMediaUploadFunction => {
return async (stream, { mediaType, fileEncSha256B64, timeoutMs }) => {
return async(filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
// send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false)
@@ -594,19 +589,25 @@ export const getWAUploadToServer = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result: any
try {
const body = await axios.post(url, stream, {
...options,
headers: {
...(options.headers || {}),
'Content-Type': 'application/octet-stream',
Origin: DEFAULT_ORIGIN
},
httpsAgent: fetchAgent,
timeout: timeoutMs,
responseType: 'json',
maxBodyLength: Infinity,
maxContentLength: Infinity
})
const body = await axios.post(
url,
createReadStream(filePath),
{
...options,
maxRedirects: 0,
headers: {
...options.headers || { },
'Content-Type': 'application/octet-stream',
'Origin': DEFAULT_ORIGIN
},
httpsAgent: fetchAgent,
timeout: timeoutMs,
responseType: 'json',
maxBodyLength: Infinity,
maxContentLength: Infinity,
}
)
result = body.data
if (result?.url || result?.directPath) {

View File

@@ -165,8 +165,17 @@ export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, opt
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } =
await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
const {
mediaKey,
encFilePath,
originalFilePath,
fileEncSha256,
fileSha256,
fileLength
} = await encryptedStream(
uploadData.media,
options.mediaTypeOverride || mediaType,
{
logger,
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
opts: options.options
@@ -174,23 +183,21 @@ export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, opt
// url safe Base64 encode the SHA256 hash of the body
const fileEncSha256B64 = fileEncSha256.toString('base64')
const [{ mediaUrl, directPath }] = await Promise.all([
(async () => {
const result = await options.upload(encWriteStream, {
fileEncSha256B64,
mediaType,
timeoutMs: options.mediaUploadTimeoutMs
})
(async() => {
const result = await options.upload(
encFilePath,
{ fileEncSha256B64, mediaType, timeoutMs: options.mediaUploadTimeoutMs }
)
logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
return result
})(),
(async () => {
try {
if (requiresThumbnailComputation) {
const { thumbnail, originalImageDimensions } = await generateThumbnail(
bodyPath!,
mediaType as 'image' | 'video',
options
)
if(requiresThumbnailComputation) {
const {
thumbnail,
originalImageDimensions
} = await generateThumbnail(originalFilePath!, mediaType as 'image' | 'video', options)
uploadData.jpegThumbnail = thumbnail
if (!uploadData.width && originalImageDimensions) {
uploadData.width = originalImageDimensions.width
@@ -201,13 +208,13 @@ export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, opt
logger?.debug('generated thumbnail')
}
if (requiresDurationComputation) {
uploadData.seconds = await getAudioDuration(bodyPath!)
if(requiresDurationComputation) {
uploadData.seconds = await getAudioDuration(originalFilePath!)
logger?.debug('computed audio duration')
}
if (requiresWaveformProcessing) {
uploadData.waveform = await getAudioWaveform(bodyPath!, logger)
if(requiresWaveformProcessing) {
uploadData.waveform = await getAudioWaveform(originalFilePath!, logger)
logger?.debug('processed waveform')
}
@@ -218,20 +225,22 @@ export const prepareWAMessageMedia = async (message: AnyMediaMessageContent, opt
} catch (error) {
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
}
})()
]).finally(async () => {
encWriteStream.destroy()
// remove tmp files
if (didSaveToTmpPath && bodyPath) {
try {
await fs.access(bodyPath)
await fs.unlink(bodyPath)
logger?.debug('removed tmp file')
} catch (error) {
logger?.warn('failed to remove tmp file')
})(),
])
.finally(
async() => {
try {
await fs.unlink(encFilePath)
if(originalFilePath) {
await fs.unlink(originalFilePath)
}
logger?.debug('removed tmp files')
} catch(error) {
logger?.warn('failed to remove tmp file')
}
}
}
})
)
const obj = WAProto.Message.fromObject({
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({

View File

@@ -23,7 +23,7 @@ export class USyncStatusProtocol implements USyncQueryProtocol {
parser(node: BinaryNode): StatusData | undefined {
if (node.tag === 'status') {
assertNodeErrorFree(node)
let status: string | null = node?.content!.toString()
let status: string | null = node?.content?.toString() ?? null
const setAt = new Date(+(node?.attrs.t || 0) * 1000)
if (!status) {
if (+node.attrs?.code === 401) {