diff --git a/package.json b/package.json index 5b846a0..1e44850 100644 --- a/package.json +++ b/package.json @@ -98,4 +98,4 @@ } }, "packageManager": "yarn@1.22.19" -} +} \ No newline at end of file diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index c1985ba..2a4c459 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -302,19 +302,22 @@ export const makeMessagesSocket = (config: SocketConfig) => { const relayMessage = async( jid: string, message: proto.IMessage, - { messageId: msgId, participant, additionalAttributes, useUserDevicesCache, cachedGroupMetadata }: MessageRelayOptions + { messageId: msgId, participant, additionalAttributes, useUserDevicesCache, cachedGroupMetadata, statusJidList }: MessageRelayOptions ) => { const meId = authState.creds.me!.id let shouldIncludeDeviceIdentity = false const { user, server } = jidDecode(jid)! + const statusJid = 'status@broadcast' const isGroup = server === 'g.us' + const isStatus = jid === statusJid + msgId = msgId || generateMessageID() useUserDevicesCache = useUserDevicesCache !== false 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 devices: JidWithDevice[] = [] @@ -329,7 +332,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { // when the retry request is not for a group // 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 - if(!isGroup) { + if(!isGroup && !isStatus) { additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' } } @@ -340,7 +343,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { await authState.keys.transaction( async() => { const mediaType = getMediaType(message) - if(isGroup) { + if(isGroup || isStatus) { const [groupData, senderKeyMap] = await Promise.all([ (async() => { 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') } - if(!groupData) { + if(!groupData && !isStatus) { groupData = await groupMetadata(jid) } return groupData })(), (async() => { - if(!participant) { + if(!participant && !isStatus) { const result = await authState.keys.get('sender-key-memory', [jid]) return result[jid] || { } } @@ -365,7 +368,11 @@ export const makeMessagesSocket = (config: SocketConfig) => { ]) 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) devices.push(...additionalDevices) } @@ -746,7 +753,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { 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) { process.nextTick(() => { processingMutex.mutex(() => ( diff --git a/src/Types/Message.ts b/src/Types/Message.ts index 28d2cfd..385ed7c 100644 --- a/src/Types/Message.ts +++ b/src/Types/Message.ts @@ -198,6 +198,8 @@ export type MessageRelayOptions = MinimalRelayOptions & { additionalAttributes?: { [_: string]: string } /** should we use the devices cache, or fetch afresh from the server; default assumed to be "true" */ useUserDevicesCache?: boolean + /** jid list of participants for status@broadcast */ + statusJidList?: string[] } export type MiscMessageGenerationOptions = MinimalRelayOptions & { @@ -209,6 +211,12 @@ export type MiscMessageGenerationOptions = MinimalRelayOptions & { ephemeralExpiration?: number | string /** timeout for media upload to WA server */ 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 & { userJid: string @@ -226,6 +234,10 @@ export type MediaGenerationOptions = { mediaUploadTimeoutMs?: number options?: AxiosRequestConfig + + backgroundColor?: string + + font?: number } export type MessageContentGenerationOptions = MediaGenerationOptions & { getUrlInfo?: (text: string) => Promise diff --git a/src/Utils/messages-media.ts b/src/Utils/messages-media.ts index a23e02c..1938cdf 100644 --- a/src/Utils/messages-media.ts +++ b/src/Utils/messages-media.ts @@ -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 */ -export async function getAudioWaveform(bodyPath: string, logger?: Logger) { +export async function getAudioWaveform(buffer: Buffer | string | Readable, logger?: Logger) { try { - const { default: audioDecode } = await import('audio-decode') - const fileBuffer = await fs.readFile(bodyPath) - const audioBuffer = await audioDecode.default(fileBuffer) + const audioDecode = (...args) => import('audio-decode').then(({ default: audioDecode }) => audioDecode(...args)) + let audioData: Buffer + 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 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.GENERAL_ERROR]: 418, } as const + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function __importStar(arg0: any): any { + throw new Error('Function not implemented.') +} diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index d8c61a7..4e7d135 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -23,7 +23,7 @@ import { WAProto, WATextMessage, } from '../Types' -import { isJidGroup, jidNormalizedUser } from '../WABinary' +import { isJidGroup, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary' import { sha256 } from './crypto' import { generateMessageID, getKeyAuthor, unixTimestampSeconds } from './generics' import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, MediaDownloadOptions } from './messages-media' @@ -31,7 +31,7 @@ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudi type MediaUploadData = { media: WAMediaUpload caption?: string - ptt?: boolean + ptt?: boolean | string seconds?: number gifPlayback?: boolean fileName?: string @@ -40,6 +40,7 @@ type MediaUploadData = { width?: number height?: number waveform?: Uint8Array + backgroundArgb?: number } 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( message: AnyMediaMessageContent, options: MediaGenerationOptions @@ -139,7 +155,8 @@ export const prepareWAMessageMedia = async( const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined' const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && (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 { mediaKey, @@ -195,6 +212,16 @@ export const prepareWAMessageMedia = async( uploadData.waveform = await getAudioWaveform(bodyPath!, logger) 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) { 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 } else if('contacts' in message) { const contactLen = message.contacts.contacts.length @@ -583,7 +618,7 @@ export const generateWAMessageFromContent = ( message: message, messageTimestamp: timestamp, messageStubParameters: [], - participant: isJidGroup(jid) ? userJid : undefined, + participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, status: WAMessageStatus.PENDING } return WAProto.WebMessageInfo.fromObject(messageJSON) diff --git a/yarn.lock b/yarn.lock index a2a6e07..647bd09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7704,4 +7704,4 @@ yargs@^16.2.0: yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== \ No newline at end of file