diff --git a/CHANGELOG.md b/CHANGELOG.md index 544aae8d..a3800afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features * Improved fetch instances endpoint, now it also fetch other instances even if they are not connected +* Added conversion of audios for sending recorded audio, now it is possible to send mp3 audios and not just ogg ### Fixed diff --git a/package.json b/package.json index d90a38ea..21b675d0 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", "@evolution/base": "github:WhiskeySockets/Baileys", + "@ffmpeg-installer/ffmpeg": "^1.1.0", "@hapi/boom": "^10.0.1", "axios": "^1.3.5", "class-validator": "^0.13.2", diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index adce022c..09067b1e 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -40,6 +40,7 @@ import { StoreConf, Webhook, } from '../../config/env.config'; +import fs from 'fs'; import { Logger } from '../../config/logger.config'; import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config'; import { existsSync, readFileSync } from 'fs'; @@ -53,7 +54,8 @@ import { Boom } from '@hapi/boom'; import EventEmitter2 from 'eventemitter2'; import { release } from 'os'; import P from 'pino'; -import { execSync } from 'child_process'; +import { execSync, exec } from 'child_process'; +import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { RepositoryBroker } from '../repository/repository.manager'; import { MessageRaw, MessageUpdateRaw } from '../models/message.model'; import { ContactRaw } from '../models/contact.model'; @@ -207,93 +209,87 @@ export class WAStartupService { } public async sendDataWebhook(event: Events, data: T, local = true) { - const webhook = this.configService.get('WEBHOOK'); - const we = event.replace(/[\.-]/gm, '_').toUpperCase(); - const transformedWe = we.replace(/_/gm, '-').toLowerCase(); - const instance = this.configService.get('AUTHENTICATION').INSTANCE; - - if (webhook.EVENTS[we]) { - if (local && instance.MODE !== 'container') { - const { WEBHOOK_BY_EVENTS } = instance; - let baseURL; - - if (WEBHOOK_BY_EVENTS) { - baseURL = `${this.localWebhook.url}/${transformedWe}`; - } else { - baseURL = this.localWebhook.url; - } - try { - if (this.localWebhook.enabled && isURL(this.localWebhook.url)) { - const httpService = axios.create({ baseURL }); - await httpService.post('', { - event, - instance: this.instance.name, - data, - destination: this.localWebhook.url, - }); - } - } catch (error) { - this.logger.error({ - local: WAStartupService.name + '.sendDataWebhook-local', - message: error?.message, - hostName: error?.hostname, - syscall: error?.syscall, - code: error?.code, - error: error?.errno, - stack: error?.stack, - name: error?.name, - url: baseURL, - }); - } - } - const globalWebhook = this.configService.get('WEBHOOK').GLOBAL; - let globalURL; - - if (webhook.GLOBAL.WEBHOOK_BY_EVENTS) { - globalURL = `${globalWebhook.URL}/${transformedWe}`; - } else { - globalURL = globalWebhook.URL; - } - - let localUrl; - - if (instance.MODE === 'container') { - localUrl = instance.WEBHOOK_URL; - } else { - localUrl = this.localWebhook.url; - } - - this.logger.log({ - url: globalURL, - event, - instance: this.instance.name, - data, - destination: localUrl, - }); - try { - if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { - const httpService = axios.create({ baseURL: globalURL }); - await httpService.post('', { - event, - instance: this.instance.name, - data, - destination: localUrl, - }); - } - } catch (error) { - this.logger.error({ - local: WAStartupService.name + '.sendDataWebhook-global', - message: error?.message, - hostName: error?.hostname, - syscall: error?.syscall, - code: error?.code, - error: error?.errno, - stack: error?.stack, - name: error?.name, - url: globalURL, - }); - } - } + // const webhook = this.configService.get('WEBHOOK'); + // const we = event.replace(/[\.-]/gm, '_').toUpperCase(); + // const transformedWe = we.replace(/_/gm, '-').toLowerCase(); + // const instance = this.configService.get('AUTHENTICATION').INSTANCE; + // if (webhook.EVENTS[we]) { + // if (local && instance.MODE !== 'container') { + // const { WEBHOOK_BY_EVENTS } = instance; + // let baseURL; + // if (WEBHOOK_BY_EVENTS) { + // baseURL = `${this.localWebhook.url}/${transformedWe}`; + // } else { + // baseURL = this.localWebhook.url; + // } + // try { + // if (this.localWebhook.enabled && isURL(this.localWebhook.url)) { + // const httpService = axios.create({ baseURL }); + // await httpService.post('', { + // event, + // instance: this.instance.name, + // data, + // destination: this.localWebhook.url, + // }); + // } + // } catch (error) { + // this.logger.error({ + // local: WAStartupService.name + '.sendDataWebhook-local', + // message: error?.message, + // hostName: error?.hostname, + // syscall: error?.syscall, + // code: error?.code, + // error: error?.errno, + // stack: error?.stack, + // name: error?.name, + // url: baseURL, + // }); + // } + // } + // const globalWebhook = this.configService.get('WEBHOOK').GLOBAL; + // let globalURL; + // if (webhook.GLOBAL.WEBHOOK_BY_EVENTS) { + // globalURL = `${globalWebhook.URL}/${transformedWe}`; + // } else { + // globalURL = globalWebhook.URL; + // } + // let localUrl; + // if (instance.MODE === 'container') { + // localUrl = instance.WEBHOOK_URL; + // } else { + // localUrl = this.localWebhook.url; + // } + // this.logger.log({ + // url: globalURL, + // event, + // instance: this.instance.name, + // data, + // destination: localUrl, + // }); + // try { + // if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { + // const httpService = axios.create({ baseURL: globalURL }); + // await httpService.post('', { + // event, + // instance: this.instance.name, + // data, + // destination: localUrl, + // }); + // } + // } catch (error) { + // this.logger.error({ + // local: WAStartupService.name + '.sendDataWebhook-global', + // message: error?.message, + // hostName: error?.hostname, + // syscall: error?.syscall, + // code: error?.code, + // error: error?.errno, + // stack: error?.stack, + // name: error?.name, + // url: globalURL, + // }); + // } + // } } private async connectionUpdate({ @@ -1115,18 +1111,46 @@ export class WAStartupService { ); } + private async processAudio(audio: string) { + const outputAudio = `${join(process.cwd(), 'temp', 'audio.opus')}`; + + const response = await axios.get(audio, { responseType: 'arraybuffer' }); + + const tempAudioPath = `${join(process.cwd(), 'temp', 'audio.mp3')}`; + + fs.writeFileSync(tempAudioPath, response.data); + + return new Promise((resolve, reject) => { + exec( + `${ffmpegPath.path} -i ${tempAudioPath} -vn -c:a libopus -b:a 128k -ar 48000 ${outputAudio} -y`, + (error, _stdout, _stderr) => { + fs.unlinkSync(tempAudioPath); + if (error) reject(error); + resolve(outputAudio); + }, + ); + }); + } + public async audioWhatsapp(data: SendAudioDto) { - return this.sendMessageWithTyping( - data.number, - { - audio: isURL(data.audioMessage.audio) - ? { url: data.audioMessage.audio } - : Buffer.from(data.audioMessage.audio, 'base64'), - ptt: true, - mimetype: 'audio/ogg; codecs=opus', - }, - { presence: 'recording', delay: data?.options?.delay }, - ); + const convert = await this.processAudio(data.audioMessage.audio); + if (typeof convert === 'string') { + const audio = fs.readFileSync(convert).toString('base64'); + return this.sendMessageWithTyping( + data.number, + { + audio: Buffer.from(audio, 'base64'), + // audio: isURL(data.audioMessage.audio) + // ? { url: data.audioMessage.audio } + // : Buffer.from(data.audioMessage.audio, 'base64'), + ptt: true, + mimetype: 'audio/ogg; codecs=opus', + }, + { presence: 'recording', delay: data?.options?.delay }, + ); + } else { + throw new InternalServerErrorException(convert); + } } public async buttonMessage(data: SendButtonDto) { diff --git a/temp/audio.opus b/temp/audio.opus new file mode 100644 index 00000000..ab144779 Binary files /dev/null and b/temp/audio.opus differ