mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: Send Status (status@broadcast) {text, media, audio(with waveform)} (#249)
Co-authored-by: Davidson Gomes <contato@agenciadgcode.com>
This commit is contained in:
@@ -98,4 +98,4 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.19"
|
"packageManager": "yarn@1.22.19"
|
||||||
}
|
}
|
||||||
@@ -302,19 +302,22 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
const relayMessage = async(
|
const relayMessage = async(
|
||||||
jid: string,
|
jid: string,
|
||||||
message: proto.IMessage,
|
message: proto.IMessage,
|
||||||
{ messageId: msgId, participant, additionalAttributes, useUserDevicesCache, cachedGroupMetadata }: MessageRelayOptions
|
{ messageId: msgId, participant, additionalAttributes, useUserDevicesCache, cachedGroupMetadata, statusJidList }: MessageRelayOptions
|
||||||
) => {
|
) => {
|
||||||
const meId = authState.creds.me!.id
|
const meId = authState.creds.me!.id
|
||||||
|
|
||||||
let shouldIncludeDeviceIdentity = false
|
let shouldIncludeDeviceIdentity = false
|
||||||
|
|
||||||
const { user, server } = jidDecode(jid)!
|
const { user, server } = jidDecode(jid)!
|
||||||
|
const statusJid = 'status@broadcast'
|
||||||
const isGroup = server === 'g.us'
|
const isGroup = server === 'g.us'
|
||||||
|
const isStatus = jid === statusJid
|
||||||
|
|
||||||
msgId = msgId || generateMessageID()
|
msgId = msgId || generateMessageID()
|
||||||
useUserDevicesCache = useUserDevicesCache !== false
|
useUserDevicesCache = useUserDevicesCache !== false
|
||||||
|
|
||||||
const participants: BinaryNode[] = []
|
const participants: BinaryNode[] = []
|
||||||
const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net')
|
const destinationJid = (!isStatus) ? jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net') : statusJid
|
||||||
const binaryNodeContent: BinaryNode[] = []
|
const binaryNodeContent: BinaryNode[] = []
|
||||||
const devices: JidWithDevice[] = []
|
const devices: JidWithDevice[] = []
|
||||||
|
|
||||||
@@ -329,7 +332,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
// when the retry request is not for a group
|
// when the retry request is not for a group
|
||||||
// only send to the specific device that asked for a retry
|
// only send to the specific device that asked for a retry
|
||||||
// otherwise the message is sent out to every device that should be a recipient
|
// otherwise the message is sent out to every device that should be a recipient
|
||||||
if(!isGroup) {
|
if(!isGroup && !isStatus) {
|
||||||
additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' }
|
additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +343,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
await authState.keys.transaction(
|
await authState.keys.transaction(
|
||||||
async() => {
|
async() => {
|
||||||
const mediaType = getMediaType(message)
|
const mediaType = getMediaType(message)
|
||||||
if(isGroup) {
|
if(isGroup || isStatus) {
|
||||||
const [groupData, senderKeyMap] = await Promise.all([
|
const [groupData, senderKeyMap] = await Promise.all([
|
||||||
(async() => {
|
(async() => {
|
||||||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
||||||
@@ -348,14 +351,14 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
|
logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!groupData) {
|
if(!groupData && !isStatus) {
|
||||||
groupData = await groupMetadata(jid)
|
groupData = await groupMetadata(jid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupData
|
return groupData
|
||||||
})(),
|
})(),
|
||||||
(async() => {
|
(async() => {
|
||||||
if(!participant) {
|
if(!participant && !isStatus) {
|
||||||
const result = await authState.keys.get('sender-key-memory', [jid])
|
const result = await authState.keys.get('sender-key-memory', [jid])
|
||||||
return result[jid] || { }
|
return result[jid] || { }
|
||||||
}
|
}
|
||||||
@@ -365,7 +368,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
if(!participant) {
|
if(!participant) {
|
||||||
const participantsList = groupData.participants.map(p => p.id)
|
const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
|
||||||
|
if(isStatus && statusJidList) {
|
||||||
|
participantsList.push(...statusJidList)
|
||||||
|
}
|
||||||
|
|
||||||
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
|
||||||
devices.push(...additionalDevices)
|
devices.push(...additionalDevices)
|
||||||
}
|
}
|
||||||
@@ -746,7 +753,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
additionalAttributes.edit = '1'
|
additionalAttributes.edit = '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes })
|
await relayMessage(jid, fullMsg.message!, { messageId: fullMsg.key.id!, cachedGroupMetadata: options.cachedGroupMetadata, additionalAttributes, statusJidList: options.statusJidList })
|
||||||
if(config.emitOwnEvents) {
|
if(config.emitOwnEvents) {
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
processingMutex.mutex(() => (
|
processingMutex.mutex(() => (
|
||||||
|
|||||||
@@ -198,6 +198,8 @@ export type MessageRelayOptions = MinimalRelayOptions & {
|
|||||||
additionalAttributes?: { [_: string]: string }
|
additionalAttributes?: { [_: string]: string }
|
||||||
/** should we use the devices cache, or fetch afresh from the server; default assumed to be "true" */
|
/** should we use the devices cache, or fetch afresh from the server; default assumed to be "true" */
|
||||||
useUserDevicesCache?: boolean
|
useUserDevicesCache?: boolean
|
||||||
|
/** jid list of participants for status@broadcast */
|
||||||
|
statusJidList?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MiscMessageGenerationOptions = MinimalRelayOptions & {
|
export type MiscMessageGenerationOptions = MinimalRelayOptions & {
|
||||||
@@ -209,6 +211,12 @@ export type MiscMessageGenerationOptions = MinimalRelayOptions & {
|
|||||||
ephemeralExpiration?: number | string
|
ephemeralExpiration?: number | string
|
||||||
/** timeout for media upload to WA server */
|
/** timeout for media upload to WA server */
|
||||||
mediaUploadTimeoutMs?: number
|
mediaUploadTimeoutMs?: number
|
||||||
|
/** jid list of participants for status@broadcast */
|
||||||
|
statusJidList?: string[]
|
||||||
|
/** backgroundcolor for status */
|
||||||
|
backgroundColor?: string
|
||||||
|
/** font type for status */
|
||||||
|
font?: number
|
||||||
}
|
}
|
||||||
export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & {
|
export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & {
|
||||||
userJid: string
|
userJid: string
|
||||||
@@ -226,6 +234,10 @@ export type MediaGenerationOptions = {
|
|||||||
mediaUploadTimeoutMs?: number
|
mediaUploadTimeoutMs?: number
|
||||||
|
|
||||||
options?: AxiosRequestConfig
|
options?: AxiosRequestConfig
|
||||||
|
|
||||||
|
backgroundColor?: string
|
||||||
|
|
||||||
|
font?: number
|
||||||
}
|
}
|
||||||
export type MessageContentGenerationOptions = MediaGenerationOptions & {
|
export type MessageContentGenerationOptions = MediaGenerationOptions & {
|
||||||
getUrlInfo?: (text: string) => Promise<WAUrlInfo | undefined>
|
getUrlInfo?: (text: string) => Promise<WAUrlInfo | undefined>
|
||||||
|
|||||||
@@ -207,11 +207,20 @@ export async function getAudioDuration(buffer: Buffer | string | Readable) {
|
|||||||
/**
|
/**
|
||||||
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
||||||
*/
|
*/
|
||||||
export async function getAudioWaveform(bodyPath: string, logger?: Logger) {
|
export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: Logger) {
|
||||||
try {
|
try {
|
||||||
const { default: audioDecode } = await import('audio-decode')
|
const audioDecode = (...args) => import('audio-decode').then(({ default: audioDecode }) => audioDecode(...args))
|
||||||
const fileBuffer = await fs.readFile(bodyPath)
|
let audioData: Buffer
|
||||||
const audioBuffer = await audioDecode.default(fileBuffer)
|
if(Buffer.isBuffer(buffer)) {
|
||||||
|
audioData = buffer
|
||||||
|
} else if(typeof buffer === 'string') {
|
||||||
|
const rStream = createReadStream(buffer)
|
||||||
|
audioData = await toBuffer(rStream)
|
||||||
|
} else {
|
||||||
|
audioData = await toBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioBuffer = await audioDecode(audioData)
|
||||||
|
|
||||||
const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
|
const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
|
||||||
const samples = 64 // Number of samples we want to have in our final data set
|
const samples = 64 // Number of samples we want to have in our final data set
|
||||||
@@ -773,3 +782,8 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|||||||
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
||||||
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
function __importStar(arg0: any): any {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
WAProto,
|
WAProto,
|
||||||
WATextMessage,
|
WATextMessage,
|
||||||
} from '../Types'
|
} from '../Types'
|
||||||
import { isJidGroup, jidNormalizedUser } from '../WABinary'
|
import { isJidGroup, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
||||||
import { sha256 } from './crypto'
|
import { sha256 } from './crypto'
|
||||||
import { generateMessageID, getKeyAuthor, unixTimestampSeconds } from './generics'
|
import { generateMessageID, getKeyAuthor, unixTimestampSeconds } from './generics'
|
||||||
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, MediaDownloadOptions } from './messages-media'
|
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, MediaDownloadOptions } from './messages-media'
|
||||||
@@ -31,7 +31,7 @@ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudi
|
|||||||
type MediaUploadData = {
|
type MediaUploadData = {
|
||||||
media: WAMediaUpload
|
media: WAMediaUpload
|
||||||
caption?: string
|
caption?: string
|
||||||
ptt?: boolean
|
ptt?: boolean | string
|
||||||
seconds?: number
|
seconds?: number
|
||||||
gifPlayback?: boolean
|
gifPlayback?: boolean
|
||||||
fileName?: string
|
fileName?: string
|
||||||
@@ -40,6 +40,7 @@ type MediaUploadData = {
|
|||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
waveform?: Uint8Array
|
waveform?: Uint8Array
|
||||||
|
backgroundArgb?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIMETYPE_MAP: { [T in MediaType]?: string } = {
|
const MIMETYPE_MAP: { [T in MediaType]?: string } = {
|
||||||
@@ -82,6 +83,21 @@ export const generateLinkPreviewIfRequired = async(text: string, getUrlInfo: Mes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assertColor = async(color) => {
|
||||||
|
let assertedColor
|
||||||
|
if(typeof color === 'number') {
|
||||||
|
assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
|
||||||
|
} else {
|
||||||
|
let hex = color.trim().replace('#', '')
|
||||||
|
if(hex.length <= 6) {
|
||||||
|
hex = 'FF' + hex.padStart(6, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
assertedColor = parseInt(hex, 16)
|
||||||
|
return assertedColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const prepareWAMessageMedia = async(
|
export const prepareWAMessageMedia = async(
|
||||||
message: AnyMediaMessageContent,
|
message: AnyMediaMessageContent,
|
||||||
options: MediaGenerationOptions
|
options: MediaGenerationOptions
|
||||||
@@ -139,7 +155,8 @@ export const prepareWAMessageMedia = async(
|
|||||||
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
|
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
|
||||||
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') &&
|
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') &&
|
||||||
(typeof uploadData['jpegThumbnail'] === 'undefined')
|
(typeof uploadData['jpegThumbnail'] === 'undefined')
|
||||||
const requiresWaveformProcessing = mediaType === 'audio' && uploadData?.ptt === true
|
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === 'true' || true
|
||||||
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === 'true' || true
|
||||||
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
|
||||||
const {
|
const {
|
||||||
mediaKey,
|
mediaKey,
|
||||||
@@ -195,6 +212,16 @@ export const prepareWAMessageMedia = async(
|
|||||||
uploadData.waveform = await getAudioWaveform(bodyPath!, logger)
|
uploadData.waveform = await getAudioWaveform(bodyPath!, logger)
|
||||||
logger?.debug('processed waveform')
|
logger?.debug('processed waveform')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(requiresWaveformProcessing) {
|
||||||
|
uploadData.waveform = await getAudioWaveform(bodyPath!, logger)
|
||||||
|
logger?.debug('processed waveform')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(requiresAudioBackground) {
|
||||||
|
uploadData.backgroundArgb = await assertColor(options.backgroundColor)
|
||||||
|
logger?.debug('computed backgroundColor audio status')
|
||||||
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
logger?.warn({ trace: error.stack }, 'failed to obtain extra info')
|
||||||
}
|
}
|
||||||
@@ -321,6 +348,14 @@ export const generateWAMessageContent = async(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(options.backgroundColor) {
|
||||||
|
extContent.backgroundArgb = await assertColor(options.backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.font) {
|
||||||
|
extContent.font = options.font
|
||||||
|
}
|
||||||
|
|
||||||
m.extendedTextMessage = extContent
|
m.extendedTextMessage = extContent
|
||||||
} else if('contacts' in message) {
|
} else if('contacts' in message) {
|
||||||
const contactLen = message.contacts.contacts.length
|
const contactLen = message.contacts.contacts.length
|
||||||
@@ -583,7 +618,7 @@ export const generateWAMessageFromContent = (
|
|||||||
message: message,
|
message: message,
|
||||||
messageTimestamp: timestamp,
|
messageTimestamp: timestamp,
|
||||||
messageStubParameters: [],
|
messageStubParameters: [],
|
||||||
participant: isJidGroup(jid) ? userJid : undefined,
|
participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined,
|
||||||
status: WAMessageStatus.PENDING
|
status: WAMessageStatus.PENDING
|
||||||
}
|
}
|
||||||
return WAProto.WebMessageInfo.fromObject(messageJSON)
|
return WAProto.WebMessageInfo.fromObject(messageJSON)
|
||||||
|
|||||||
@@ -7704,4 +7704,4 @@ yargs@^16.2.0:
|
|||||||
yn@3.1.1:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
||||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||||
Reference in New Issue
Block a user