mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
generic media uploads
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import BinaryNode from "../BinaryNode";
|
import BinaryNode from "../BinaryNode";
|
||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { Chat, Presence, WAMessageCursor, SocketConfig, WAMessage, WAMessageKey, ParticipantAction, WAMessageProto, WAMessageStatus, WAMessageStubType, GroupMetadata, AnyMessageContent, MiscMessageGenerationOptions, WAFlag, WAMetric, WAUrlInfo, MediaConnInfo, MessageUpdateType, MessageInfo, MessageInfoUpdate } from "../Types";
|
import { Chat, Presence, WAMessageCursor, SocketConfig, WAMessage, WAMessageKey, ParticipantAction, WAMessageProto, WAMessageStatus, WAMessageStubType, GroupMetadata, AnyMessageContent, MiscMessageGenerationOptions, WAFlag, WAMetric, WAUrlInfo, MediaConnInfo, MessageUpdateType, MessageInfo, MessageInfoUpdate, WAMediaUploadFunction, MediaType } from "../Types";
|
||||||
import { isGroupID, toNumber, whatsappID, generateWAMessage, decryptMediaMessageBuffer } from "../Utils";
|
import { isGroupID, toNumber, whatsappID, generateWAMessage, decryptMediaMessageBuffer } from "../Utils";
|
||||||
import makeChatsSocket from "./chats";
|
import makeChatsSocket from "./chats";
|
||||||
import { WA_DEFAULT_EPHEMERAL } from "../Defaults";
|
import { DEFAULT_ORIGIN, MEDIA_PATH_MAP, WA_DEFAULT_EPHEMERAL } from "../Defaults";
|
||||||
|
import got from "got";
|
||||||
|
|
||||||
const STATUS_MAP = {
|
const STATUS_MAP = {
|
||||||
read: WAMessageStatus.READ,
|
read: WAMessageStatus.READ,
|
||||||
@@ -211,6 +212,51 @@ const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const waUploadToServer: WAMediaUploadFunction = async(stream, { mediaType, fileEncSha256B64 }) => {
|
||||||
|
// send a query JSON to obtain the url & auth token to upload our media
|
||||||
|
let uploadInfo = await refreshMediaConn(false)
|
||||||
|
|
||||||
|
let mediaUrl: string
|
||||||
|
for (let host of uploadInfo.hosts) {
|
||||||
|
const auth = encodeURIComponent(uploadInfo.auth) // the auth token
|
||||||
|
const url = `https://${host.hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {body: responseText} = await got.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Origin': DEFAULT_ORIGIN
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
https: config.agent
|
||||||
|
},
|
||||||
|
body: stream
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const result = JSON.parse(responseText)
|
||||||
|
mediaUrl = result?.url
|
||||||
|
|
||||||
|
if (mediaUrl) break
|
||||||
|
else {
|
||||||
|
uploadInfo = await refreshMediaConn(true)
|
||||||
|
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const isLast = host.hostname === uploadInfo.hosts[uploadInfo.hosts.length-1].hostname
|
||||||
|
logger.debug(`Error in uploading to ${host.hostname} (${error}) ${isLast ? '' : ', retrying...'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mediaUrl) {
|
||||||
|
throw new Boom(
|
||||||
|
'Media upload failed on all hosts',
|
||||||
|
{ statusCode: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { mediaUrl }
|
||||||
|
}
|
||||||
|
|
||||||
/** Query a string to check if it has a url, if it does, return WAUrlInfo */
|
/** Query a string to check if it has a url, if it does, return WAUrlInfo */
|
||||||
const generateUrlInfo = async(text: string) => {
|
const generateUrlInfo = async(text: string) => {
|
||||||
const response = await query({
|
const response = await query({
|
||||||
@@ -484,7 +530,7 @@ const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
...options,
|
...options,
|
||||||
userJid: userJid,
|
userJid: userJid,
|
||||||
getUrlInfo: generateUrlInfo,
|
getUrlInfo: generateUrlInfo,
|
||||||
getMediaOptions: refreshMediaConn
|
upload: waUploadToServer
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await relayWAMessage(msg, { waitForAck: options.waitForAck })
|
await relayWAMessage(msg, { waitForAck: options.waitForAck })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import P from "pino"
|
import P from "pino"
|
||||||
import type { SocketConfig } from "../Types"
|
import type { MediaType, SocketConfig } from "../Types"
|
||||||
import { Browsers } from "../Utils/generics"
|
import { Browsers } from "../Utils/generics"
|
||||||
|
|
||||||
export const UNAUTHORIZED_CODES = [401, 403, 419]
|
export const UNAUTHORIZED_CODES = [401, 403, 419]
|
||||||
@@ -34,3 +34,14 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = {
|
|||||||
maxQRCodes: Infinity,
|
maxQRCodes: Infinity,
|
||||||
printQRInTerminal: false,
|
printQRInTerminal: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const MEDIA_PATH_MAP: { [T in MediaType]: string } = {
|
||||||
|
image: '/mms/image',
|
||||||
|
video: '/mms/video',
|
||||||
|
document: '/mms/document',
|
||||||
|
audio: '/mms/audio',
|
||||||
|
sticker: '/mms/image',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
||||||
8
src/Tests/test.message-gen.ts
Normal file
8
src/Tests/test.message-gen.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
describe('Message Generation', () => {
|
||||||
|
|
||||||
|
it('should generate a text message', () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Agent } from "https"
|
import type { ReadStream } from "fs"
|
||||||
import type { Logger } from "pino"
|
import type { Logger } from "pino"
|
||||||
import type { URL } from "url"
|
import type { URL } from "url"
|
||||||
import { proto } from '../../WAMessage'
|
import { proto } from '../../WAMessage'
|
||||||
@@ -125,10 +125,12 @@ export type MiscMessageGenerationOptions = {
|
|||||||
export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & {
|
export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & {
|
||||||
userJid: string
|
userJid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WAMediaUploadFunction = (readStream: ReadStream, opts: { fileEncSha256B64: string, mediaType: MediaType }) => Promise<{ mediaUrl: string }>
|
||||||
|
|
||||||
export type MediaGenerationOptions = {
|
export type MediaGenerationOptions = {
|
||||||
logger?: Logger
|
logger?: Logger
|
||||||
agent?: Agent
|
upload: WAMediaUploadFunction
|
||||||
getMediaOptions: (refresh: boolean) => Promise<MediaConnInfo>
|
|
||||||
}
|
}
|
||||||
export type MessageContentGenerationOptions = MediaGenerationOptions & {
|
export type MessageContentGenerationOptions = MediaGenerationOptions & {
|
||||||
getUrlInfo?: (text: string) => Promise<WAUrlInfo>
|
getUrlInfo?: (text: string) => Promise<WAUrlInfo>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { createReadStream, promises as fs } from "fs"
|
import { createReadStream, promises as fs } from "fs"
|
||||||
import got from "got"
|
|
||||||
import { proto } from '../../WAMessage'
|
import { proto } from '../../WAMessage'
|
||||||
import { DEFAULT_ORIGIN, URL_REGEX, WA_DEFAULT_EPHEMERAL } from "../Defaults"
|
import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from "../Defaults"
|
||||||
import {
|
import {
|
||||||
AnyMediaMessageContent,
|
AnyMediaMessageContent,
|
||||||
AnyMessageContent,
|
AnyMessageContent,
|
||||||
@@ -33,14 +32,6 @@ type MediaUploadData = {
|
|||||||
mimetype?: string
|
mimetype?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MEDIA_PATH_MAP: { [T in MediaType]: string } = {
|
|
||||||
image: '/mms/image',
|
|
||||||
video: '/mms/video',
|
|
||||||
document: '/mms/document',
|
|
||||||
audio: '/mms/audio',
|
|
||||||
sticker: '/mms/image',
|
|
||||||
} as const
|
|
||||||
|
|
||||||
const MIMETYPE_MAP: { [T in MediaType]: string } = {
|
const MIMETYPE_MAP: { [T in MediaType]: string } = {
|
||||||
image: 'image/jpeg',
|
image: 'image/jpeg',
|
||||||
video: 'video/mp4',
|
video: 'video/mp4',
|
||||||
@@ -57,8 +48,6 @@ const MessageTypeProto = {
|
|||||||
'document': WAMessageProto.DocumentMessage,
|
'document': WAMessageProto.DocumentMessage,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
|
|
||||||
|
|
||||||
export const prepareWAMessageMedia = async(
|
export const prepareWAMessageMedia = async(
|
||||||
message: AnyMediaMessageContent,
|
message: AnyMediaMessageContent,
|
||||||
options: MediaGenerationOptions
|
options: MediaGenerationOptions
|
||||||
@@ -110,47 +99,10 @@ export const prepareWAMessageMedia = async(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
options.logger?.debug ({ error }, 'failed to obtain audio duration: ' + error.message)
|
options.logger?.debug ({ error }, 'failed to obtain audio duration: ' + error.message)
|
||||||
}
|
}
|
||||||
// send a query JSON to obtain the url & auth token to upload our media
|
const {mediaUrl} = await options.upload(
|
||||||
let uploadInfo = await options.getMediaOptions(false)
|
createReadStream(encBodyPath),
|
||||||
|
{ fileEncSha256B64, mediaType }
|
||||||
let mediaUrl: string
|
)
|
||||||
for (let host of uploadInfo.hosts) {
|
|
||||||
const auth = encodeURIComponent(uploadInfo.auth) // the auth token
|
|
||||||
const url = `https://${host.hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {body: responseText} = await got.post(
|
|
||||||
url,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/octet-stream',
|
|
||||||
'Origin': DEFAULT_ORIGIN
|
|
||||||
},
|
|
||||||
agent: {
|
|
||||||
https: options.agent
|
|
||||||
},
|
|
||||||
body: createReadStream(encBodyPath)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const result = JSON.parse(responseText)
|
|
||||||
mediaUrl = result?.url
|
|
||||||
|
|
||||||
if (mediaUrl) break
|
|
||||||
else {
|
|
||||||
uploadInfo = await options.getMediaOptions(true)
|
|
||||||
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const isLast = host.hostname === uploadInfo.hosts[uploadInfo.hosts.length-1].hostname
|
|
||||||
options.logger?.debug(`Error in uploading to ${host.hostname} (${error}) ${isLast ? '' : ', retrying...'}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mediaUrl) {
|
|
||||||
throw new Boom(
|
|
||||||
'Media upload failed on all hosts',
|
|
||||||
{ statusCode: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// remove tmp files
|
// remove tmp files
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user