diff --git a/CHANGELOG.md b/CHANGELOG.md index db0210e0..c3714e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 1.1.3 (homolog) +### Features + +* Added configuration for Baileys log level in env +* Added audio to mp4 converter in optionally get Base64 From MediaMessage + ### Fixed * Added timestamp internally in urls to avoid caching @@ -8,6 +13,7 @@ * Fixed cash when sending stickers via url * Improved how Redis works for instances * Fixed problem when disconnecting the instance it removes the instance +* Fixed problem sending ack when preview is done by me # 1.1.2 (2023-06-28 13:43) diff --git a/Dockerfile b/Dockerfile index 04d2ddb2..338efa4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ ENV CORS_ORIGIN="*" ENV CORS_METHODS="POST,GET,PUT,DELETE" ENV CORS_CREDENTIALS=true -ENV LOG_LEVEL="ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK" +ENV LOG_LEVEL=$LOG_LEVEL ENV LOG_COLOR=true ENV DEL_INSTANCE=$DEL_INSTANCE diff --git a/docker-compose.yaml b/docker-compose.yaml index a5ad3040..ad6921e5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,10 +14,12 @@ services: - evolution_instances:/evolution/instances - evolution_store:/evolution/store environment: + - LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS + - LOG_BAILEYS=error # Determine how long the instance should be deleted from memory in case of no connection. # Default time: 5 minutes # If you don't even want an expiration, enter the value false - - DEL_INSTANCE=5 # or false + - DEL_INSTANCE=false # 5 or false # Temporary data storage - STORE_MESSAGES=true - STORE_MESSAGE_UP=true diff --git a/src/config/env.config.ts b/src/config/env.config.ts index bdb041ab..d1e0e70a 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -13,10 +13,22 @@ export type Cors = { CREDENTIALS: boolean; }; -export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK'; +export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; + +export type LogLevel = + | 'ERROR' + | 'WARN' + | 'DEBUG' + | 'INFO' + | 'LOG' + | 'VERBOSE' + | 'DARK' + | 'WEBHOOKS'; + export type Log = { LEVEL: LogLevel[]; COLOR: boolean; + BAILEYS: LogBaileys; }; export type SaveData = { @@ -204,6 +216,7 @@ export class ConfigService { LOG: { LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[], COLOR: process.env?.LOG_COLOR === 'true', + BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error', }, DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE) ? process.env.DEL_INSTANCE === 'true' diff --git a/src/dev-env.yml b/src/dev-env.yml index 72e002e2..38b219f4 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -36,12 +36,14 @@ LOG: - LOG - VERBOSE - DARK + - WEBHOOKS COLOR: true + BAILEYS: error # "fatal" | "error" | "warn" | "info" | "debug" | "trace" # Determine how long the instance should be deleted from memory in case of no connection. # Default time: 5 minutes # If you don't even want an expiration, enter the value false -DEL_INSTANCE: 5 # or false +DEL_INSTANCE: false # or false # Temporary data storage STORE: diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts index 09d75e7e..8d567a51 100644 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -9,6 +9,7 @@ import { ProfileStatusDto, ReadMessageDto, WhatsAppNumberDto, + getBase64FromMediaMessageDto, } from '../dto/chat.dto'; import { InstanceDto } from '../dto/instance.dto'; import { ContactQuery } from '../repository/contact.repository'; @@ -45,11 +46,9 @@ export class ChatController { public async getBase64FromMediaMessage( { instanceName }: InstanceDto, - message: proto.IWebMessageInfo, + data: getBase64FromMediaMessageDto, ) { - return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage( - message, - ); + return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data); } public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) { diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 293fa411..b309366a 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -207,7 +207,6 @@ export class InstanceController { ); this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); - this.waMonitor.waInstances[instanceName]?.client?.end(undefined); return { error: false, message: 'Instance logged out' }; } catch (error) { diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts index f806ce98..07757194 100644 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -2,6 +2,7 @@ import { WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue, + proto, } from '@whiskeysockets/baileys'; export class OnWhatsAppDto { @@ -12,6 +13,11 @@ export class OnWhatsAppDto { ) {} } +export class getBase64FromMediaMessageDto { + message: proto.WebMessageInfo; + convertToMp4?: boolean; +} + export class WhatsAppNumberDto { numbers: string[]; } diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index 3c8a25ad..46d05aa9 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -22,6 +22,7 @@ import { ProfileStatusDto, ReadMessageDto, WhatsAppNumberDto, + getBase64FromMediaMessageDto, } from '../dto/chat.dto'; import { ContactQuery } from '../repository/contact.repository'; import { MessageQuery } from '../repository/message.repository'; @@ -101,10 +102,10 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + const response = await this.dataValidate({ request: req, schema: null, - ClassRef: Object, + ClassRef: getBase64FromMediaMessageDto, execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data), }); diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 8feb86ec..93293233 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -253,6 +253,13 @@ export class WAMonitoringService { this.logger.warn(`Instance "${instanceName}" - REMOVED`); } }); + this.eventEmitter.on('logout.instance', async (instanceName: string) => { + try { + this.cleaningUp(instanceName); + } finally { + this.logger.warn(`Instance "${instanceName}" - LOGOUT`); + } + }); } private noConnection() { diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index a87466b3..b6024044 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -87,6 +87,7 @@ import { PrivacySettingDto, ReadMessageDto, WhatsAppNumberDto, + getBase64FromMediaMessageDto, } from '../dto/chat.dto'; import { MessageQuery } from '../repository/message.repository'; import { ContactQuery } from '../repository/contact.repository'; @@ -116,6 +117,7 @@ import NodeCache from 'node-cache'; import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; import sharp from 'sharp'; import { RedisCache } from '../../db/redis.client'; +import { Log } from '../../config/env.config'; export class WAStartupService { constructor( @@ -137,7 +139,8 @@ export class WAStartupService { private readonly msgRetryCounterCache: CacheStore = new NodeCache(); private readonly userDevicesCache: CacheStore = new NodeCache(); private endSession = false; - private store = makeInMemoryStore({ logger: P({ level: 'error' }) }); + private logBaileys = this.configService.get('LOG').BAILEYS; + private store = makeInMemoryStore({ logger: P({ level: this.logBaileys }) }); public set instanceName(name: string) { if (!name) { @@ -154,7 +157,7 @@ export class WAStartupService { public get instanceName() { return this.instance.name; } - s; + public get wuid() { return this.instance.wuid; } @@ -238,14 +241,16 @@ export class WAStartupService { baseURL = this.localWebhook.url; } - this.logger.log({ - local: WAStartupService.name + '.sendDataWebhook-local', - url: baseURL, - event, - instance: this.instance.name, - data, - destination: this.localWebhook.url, - }); + if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { + this.logger.log({ + local: WAStartupService.name + '.sendDataWebhook-local', + url: baseURL, + event, + instance: this.instance.name, + data, + destination: this.localWebhook.url, + }); + } try { if (this.localWebhook.enabled && isURL(this.localWebhook.url)) { @@ -293,14 +298,16 @@ export class WAStartupService { localUrl = this.localWebhook.url; } - this.logger.log({ - local: WAStartupService.name + '.sendDataWebhook-global', - url: globalURL, - event, - instance: this.instance.name, - data, - destination: localUrl, - }); + if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { + this.logger.log({ + local: WAStartupService.name + '.sendDataWebhook-global', + url: globalURL, + event, + instance: this.instance.name, + data, + destination: localUrl, + }); + } try { if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { @@ -409,6 +416,7 @@ export class WAStartupService { instance: this.instance.name, status: 'removed', }); + this.eventEmitter.emit('logout.instance', this.instance.name, 'inner'); this.client?.ws?.close(); this.client.end(new Error('Close connection')); } @@ -444,7 +452,7 @@ export class WAStartupService { private async getMessageStore(key: proto.IMessageKey) { if (this.store) { - const msg = await this.store.loadMessage(key.remoteJid!, key.id!); + const msg = await this.store.loadMessage(key.remoteJid, key.id); return msg?.message || undefined; } @@ -466,7 +474,6 @@ export class WAStartupService { this.instance.wuid, )}/*.json`, ); - this.store?.writeToFile(`${this.storePath}/baileys_store_multi.json`); } } } catch (error) {} @@ -500,7 +507,11 @@ export class WAStartupService { const session = this.configService.get('CONFIG_SESSION_PHONE'); const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - this.store?.readFromFile(`${this.storePath}/baileys_store_multi.json`); + this.store?.readFromFile(`${this.storePath}/baileys/store.json`); + + setInterval(() => { + this.store?.writeToFile(`${this.storePath}/baileys/store.json`); + }, 10_000); const socketConfig: UserFacingSocketConfig = { auth: { @@ -510,7 +521,7 @@ export class WAStartupService { P({ level: 'error' }), ), }, - logger: P({ level: 'error' }), + logger: P({ level: this.logBaileys }), printQRInTerminal: false, browser, version, @@ -724,7 +735,11 @@ export class WAStartupService { ) => { const received = messages[0]; - if (type !== 'notify' || received.message?.protocolMessage) { + if ( + type !== 'notify' || + received.message?.protocolMessage || + received.message?.pollUpdateMessage + ) { return; } @@ -742,7 +757,7 @@ export class WAStartupService { source: getDevice(received.key.id), }; - this.logger.log(received); + this.logger.log(messageRaw); await this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); await this.repository.message.insert([messageRaw], database.SAVE_DATA.NEW_MESSAGE); @@ -758,7 +773,11 @@ export class WAStartupService { 5: 'PLAYED', }; for await (const { key, update } of args) { - if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { + if ( + key.remoteJid !== 'status@broadcast' && + !key?.remoteJid?.match(/(:\d+)/) && + !key.fromMe + ) { let pollUpdates: any; if (update.pollUpdates) { const pollCreation = await this.getMessageStore(key); @@ -1053,10 +1072,19 @@ export class WAStartupService { ); })(); - messageSent['messageType'] = getContentType(messageSent.message); + const messageRaw: proto.IWebMessageInfo = { + key: messageSent.key, + messageTimestamp: Long.isLong(messageSent.messageTimestamp) + ? messageSent.messageTimestamp?.toNumber() + : messageSent.messageTimestamp, + pushName: messageSent.pushName, + broadcast: messageSent.broadcast, + status: 2, + message: { ...messageSent.message }, + }; this.client.ev.emit('messages.upsert', { - messages: [messageSent], + messages: [messageRaw], type: 'notify', }); @@ -1471,12 +1499,19 @@ export class WAStartupService { } } - public async getBase64FromMediaMessage(m: proto.IWebMessageInfo) { + public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto) { try { + const m = data?.message; + const convertToMp4 = data?.convertToMp4 ?? false; + const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); + if (!msg) { + throw 'Message not found'; + } + for (const subtype of MessageSubtype) { if (msg.message[subtype]) { msg.message = msg.message[subtype].message; @@ -1511,6 +1546,29 @@ export class WAStartupService { reuploadRequest: this.client.updateMediaMessage, }, ); + const typeMessage = getContentType(msg.message); + + // if for audioMessage converte para mp3 + if (convertToMp4 && typeMessage === 'audioMessage') { + const convert = await this.processAudio(buffer.toString('base64')); + + if (typeof convert === 'string') { + const audio = fs.readFileSync(convert).toString('base64'); + + return { + mediaType, + fileName: mediaMessage['fileName'], + caption: mediaMessage['caption'], + size: { + fileLength: mediaMessage['fileLength'], + height: mediaMessage['height'], + width: mediaMessage['width'], + }, + mimetype: 'audio/mp4', + base64: Buffer.from(audio, 'base64').toString('base64'), + }; + } + } return { mediaType, diff --git a/temp/audio.mp4 b/temp/audio.mp4 index 46d8950f..ae1532e9 100644 Binary files a/temp/audio.mp4 and b/temp/audio.mp4 differ