From b35b33ca50fc0761cbe0dc85bf67083732ed6a56 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:00:52 -0500 Subject: [PATCH 01/29] Update docker-compose.yaml --- docker-compose.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 33918c38..c3c74065 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -39,9 +39,9 @@ services: ports: - 5432:5432 environment: - - POSTGRES_USER=user - - POSTGRES_PASSWORD=pass - - POSTGRES_DB=evolution + - POSTGRES_DB=${POSTGRES_DATABASE} + - POSTGRES_USER=${POSTGRES_USERNAME} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_HOST_AUTH_METHOD=trust volumes: - postgres_data:/var/lib/postgresql/data From f7e7a6c901024bafa8a34344af8c18f694403acd Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:02:34 -0500 Subject: [PATCH 02/29] Update docker-compose.yaml --- docker-compose.yaml | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index c3c74065..bf8f2e22 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,3 +1,5 @@ +version: "3.8" + services: api: container_name: evolution_api @@ -7,37 +9,44 @@ services: - redis - postgres ports: - - 8080:8080 + - "8080:8080" volumes: - evolution_instances:/evolution/instances networks: - evolution-net + - dokploy-network env_file: - .env expose: - - 8080 + - "8080" redis: image: redis:latest - networks: - - evolution-net container_name: redis command: > redis-server --port 6379 --appendonly yes volumes: - evolution_redis:/data ports: - - 6379:6379 + - "6379:6379" + expose: + - "6379" + networks: + - evolution-net + - dokploy-network postgres: container_name: postgres image: postgres:15 - networks: - - evolution-net - command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"] + command: + - postgres + - -c + - max_connections=1000 + - -c + - listen_addresses=* restart: always ports: - - 5432:5432 + - "5432:5432" environment: - POSTGRES_DB=${POSTGRES_DATABASE} - POSTGRES_USER=${POSTGRES_USERNAME} @@ -46,15 +55,19 @@ services: volumes: - postgres_data:/var/lib/postgresql/data expose: - - 5432 + - "5432" + networks: + - evolution-net + - dokploy-network volumes: evolution_instances: evolution_redis: postgres_data: - networks: evolution-net: name: evolution-net driver: bridge + dokploy-network: + external: true From 17f97fb0516cb0bfd3e27ed722e60b87f60764b8 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:03:45 -0500 Subject: [PATCH 03/29] Update docker-compose.yaml --- docker-compose.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index bf8f2e22..656b8e16 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -57,8 +57,12 @@ services: expose: - "5432" networks: - - evolution-net - - dokploy-network + evolution-net: + aliases: + - evolution-postgres + dokploy-network: + aliases: + - evolution-postgres volumes: evolution_instances: From 419324837c5d8852508827519f5a084e15fa95a5 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:05:12 -0500 Subject: [PATCH 04/29] Update docker-compose.yaml --- docker-compose.yaml | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 656b8e16..750d30d5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: restart: always depends_on: - redis - - postgres + - evolution-postgres ports: - "8080:8080" volumes: @@ -21,48 +21,43 @@ services: - "8080" redis: + container_name: evolution_redis image: redis:latest - container_name: redis + restart: always command: > redis-server --port 6379 --appendonly yes volumes: - evolution_redis:/data - ports: - - "6379:6379" - expose: - - "6379" networks: - evolution-net - dokploy-network + expose: + - "6379" - postgres: - container_name: postgres + evolution-postgres: + container_name: evolution_postgres image: postgres:15 + restart: always + env_file: + - .env command: - postgres - -c - max_connections=1000 - -c - listen_addresses=* - restart: always - ports: - - "5432:5432" environment: - - POSTGRES_DB=${POSTGRES_DATABASE} - - POSTGRES_USER=${POSTGRES_USERNAME} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - - POSTGRES_HOST_AUTH_METHOD=trust + POSTGRES_DB: ${POSTGRES_DATABASE} + POSTGRES_USER: ${POSTGRES_USERNAME} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_HOST_AUTH_METHOD: trust volumes: - postgres_data:/var/lib/postgresql/data + networks: + - evolution-net + - dokploy-network expose: - "5432" - networks: - evolution-net: - aliases: - - evolution-postgres - dokploy-network: - aliases: - - evolution-postgres volumes: evolution_instances: From 1c247498d89e49f4189de353b68c34f4758a8ad7 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:25:44 -0500 Subject: [PATCH 05/29] Update docker-compose.yaml --- docker-compose.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 750d30d5..31d0734e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -47,10 +47,9 @@ services: - -c - listen_addresses=* environment: - POSTGRES_DB: ${POSTGRES_DATABASE} - POSTGRES_USER: ${POSTGRES_USERNAME} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_HOST_AUTH_METHOD: trust + - POSTGRES_DB=${POSTGRES_DATABASE} + - POSTGRES_USER=${POSTGRES_USERNAME} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data networks: From 6dfbfe2d839515f566beac22f3b56538292db6ff Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:27:42 -0500 Subject: [PATCH 06/29] Update docker-compose.yaml --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 31d0734e..f0840acf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -36,7 +36,7 @@ services: evolution-postgres: container_name: evolution_postgres - image: postgres:15 + image: postgres:16-alpine restart: always env_file: - .env From 675745ae3c542feac2fafa28e05b0d287f34ee4e Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:28:42 -0500 Subject: [PATCH 07/29] Update docker-compose.yaml --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index f0840acf..31d0734e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -36,7 +36,7 @@ services: evolution-postgres: container_name: evolution_postgres - image: postgres:16-alpine + image: postgres:15 restart: always env_file: - .env From 505490d237eb1948af5ab5469e8ff1bb98627300 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:36:21 -0500 Subject: [PATCH 08/29] Update docker-compose.yaml --- docker-compose.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 31d0734e..20f05d3b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -29,8 +29,12 @@ services: volumes: - evolution_redis:/data networks: - - evolution-net - - dokploy-network + evolution-net: + aliases: + - evolution-redis + dokploy-network: + aliases: + - evolution-redis expose: - "6379" From 192c34caa077ac00daac7a0d1b04f45c442b9ae3 Mon Sep 17 00:00:00 2001 From: codingbox2022 <119642326+codingbox2022@users.noreply.github.com> Date: Thu, 17 Jul 2025 12:47:36 -0500 Subject: [PATCH 09/29] Update docker-compose.yaml --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 20f05d3b..005769a1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,7 +9,7 @@ services: - redis - evolution-postgres ports: - - "8080:8080" + - "127.0.0.1:8080:8080" volumes: - evolution_instances:/evolution/instances networks: From 6101c8d65140fe8a2716e0507bee700db23ac5f1 Mon Sep 17 00:00:00 2001 From: William Dumes Date: Fri, 25 Jul 2025 17:08:14 -0300 Subject: [PATCH 10/29] fix: corrigido disparo de eventos quando nao usa a opcao da ENV de salvar mensagens no banco --- .../whatsapp/whatsapp.baileys.service.ts | 91 +++++++++++-------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index f454473a..7d7da2be 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1443,16 +1443,7 @@ export class BaileysStartupService extends ChannelStartupService { } } - const findMessage = await this.prismaRepository.message.findFirst({ - where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, - }); - - if (!findMessage) { - continue; - } - const message: any = { - messageId: findMessage.id, keyId: key.id, remoteJid: key?.remoteJid, fromMe: key.fromMe, @@ -1462,6 +1453,16 @@ export class BaileysStartupService extends ChannelStartupService { instanceId: this.instanceId, }; + let findMessage: any; + const configDatabaseData = this.configService.get('DATABASE').SAVE_DATA; + if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) { + findMessage = await this.prismaRepository.message.findFirst({ + where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } }, + }); + + if (findMessage) message.messageId = findMessage.id; + } + if (update.message === null && update.status === undefined) { this.sendDataWebhook(Events.MESSAGES_DELETE, key); @@ -1477,7 +1478,9 @@ export class BaileysStartupService extends ChannelStartupService { } continue; - } else if (update.status !== undefined && status[update.status] !== findMessage.status) { + } + + if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { if (!key.fromMe && key.remoteJid) { readChatToUpdate[key.remoteJid] = true; @@ -3431,16 +3434,18 @@ export class BaileysStartupService extends ChannelStartupService { where: { id: message.id }, data: { key: { ...existingKey, deleted: true }, status: 'DELETED' }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: response.key.remoteJid, - fromMe: response.key.fromMe, - participant: response.key?.remoteJid, - status: 'DELETED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: response.key.remoteJid, + fromMe: response.key.fromMe, + participant: response.key?.remoteJid, + status: 'DELETED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } else { await this.prismaRepository.message.deleteMany({ where: { id: message.id } }); } @@ -3771,6 +3776,10 @@ export class BaileysStartupService extends ChannelStartupService { private async formatUpdateMessage(data: UpdateMessageDto) { try { + if (!this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + return data; + } + const msg: any = await this.getMessage(data.key, true); if (msg?.messageType === 'conversation' || msg?.messageType === 'extendedTextMessage') { @@ -3804,13 +3813,15 @@ export class BaileysStartupService extends ChannelStartupService { try { const oldMessage: any = await this.getMessage(data.key, true); - if (!oldMessage) throw new NotFoundException('Message not found'); - if (oldMessage?.key?.remoteJid !== jid) { - throw new BadRequestException('RemoteJid does not match'); - } - if (oldMessage?.messageTimestamp > Date.now() + 900000) { - // 15 minutes in milliseconds - throw new BadRequestException('Message is older than 15 minutes'); + if (this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { + if (!oldMessage) throw new NotFoundException('Message not found'); + if (oldMessage?.key?.remoteJid !== jid) { + throw new BadRequestException('RemoteJid does not match'); + } + if (oldMessage?.messageTimestamp > Date.now() + 900000) { + // 15 minutes in milliseconds + throw new BadRequestException('Message is older than 15 minutes'); + } } const messageSent = await this.client.sendMessage(jid, { ...(options as any), edit: data.key }); @@ -3828,7 +3839,7 @@ export class BaileysStartupService extends ChannelStartupService { ); const messageId = messageSent.message?.protocolMessage?.key?.id; - if (messageId) { + if (messageId && this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE) { let message = await this.prismaRepository.message.findFirst({ where: { key: { path: ['id'], equals: messageId } }, }); @@ -3840,6 +3851,7 @@ export class BaileysStartupService extends ChannelStartupService { if ((message.key.valueOf() as any)?.deleted) { new BadRequestException('You cannot edit deleted messages'); } + if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') { oldMessage.message.conversation = data.text; } else { @@ -3853,16 +3865,19 @@ export class BaileysStartupService extends ChannelStartupService { messageTimestamp: Math.floor(Date.now() / 1000), // Convert to int32 by dividing by 1000 to get seconds }, }); - const messageUpdate: any = { - messageId: message.id, - keyId: messageId, - remoteJid: messageSent.key.remoteJid, - fromMe: messageSent.key.fromMe, - participant: messageSent.key?.remoteJid, - status: 'EDITED', - instanceId: this.instanceId, - }; - await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + + if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) { + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: messageSent.key.remoteJid, + fromMe: messageSent.key.fromMe, + participant: messageSent.key?.remoteJid, + status: 'EDITED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); + } } } } From 69726f0dc2b77c6c017d08fd65491195059617c7 Mon Sep 17 00:00:00 2001 From: William Dumes Date: Mon, 28 Jul 2025 09:24:22 -0300 Subject: [PATCH 11/29] fix(evo): melhorado controle de recebimento do ack das msgs --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 02130c43..261b734c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,6 @@ COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts ENV DOCKER_ENV=true -EXPOSE 8080 +EXPOSE 9000 ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ] From a62f9ebe465fc6a933716b787063387435f1fe7e Mon Sep 17 00:00:00 2001 From: William Dumes Date: Mon, 28 Jul 2025 11:19:07 -0300 Subject: [PATCH 12/29] chore: voltar porta do dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 261b734c..02130c43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,6 @@ COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts ENV DOCKER_ENV=true -EXPOSE 9000 +EXPOSE 8080 ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ] From 095e435561a2eb955f50daee6c9c0ff2fb2c6d21 Mon Sep 17 00:00:00 2001 From: Bilal Iqbal Date: Thu, 31 Jul 2025 14:26:05 +0500 Subject: [PATCH 13/29] Fixed boolean and integer type attributes --- prisma/mysql-schema.prisma | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index 33d09d2b..70efea47 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -647,22 +647,22 @@ model IsOnWhatsapp { model N8n { id String @id @default(cuid()) - enabled Boolean @default(true) @db.Boolean + enabled Boolean @default(true) @db.TinyInt(1) description String? @db.VarChar(255) webhookUrl String? @db.VarChar(255) basicAuthUser String? @db.VarChar(255) basicAuthPass String? @db.VarChar(255) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int triggerType TriggerType? triggerOperator TriggerOperator? triggerValue String? @@ -677,15 +677,15 @@ model N8nSetting { id String @id @default(cuid()) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp Fallback N8n? @relation(fields: [n8nIdFallback], references: [id]) @@ -696,21 +696,21 @@ model N8nSetting { model Evoai { id String @id @default(cuid()) - enabled Boolean @default(true) @db.Boolean + enabled Boolean @default(true) @db.TinyInt(1) description String? @db.VarChar(255) agentUrl String? @db.VarChar(255) apiKey String? @db.VarChar(255) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int triggerType TriggerType? triggerOperator TriggerOperator? triggerValue String? @@ -725,15 +725,15 @@ model EvoaiSetting { id String @id @default(cuid()) expire Int? @default(0) @db.Int keywordFinish String? @db.VarChar(100) - delayMessage Int? @db.Integer + delayMessage Int? @db.Int unknownMessage String? @db.VarChar(100) listeningFromMe Boolean? @default(false) stopBotFromMe Boolean? @default(false) keepOpen Boolean? @default(false) - debounceTime Int? @db.Integer + debounceTime Int? @db.Int ignoreJids Json? splitMessages Boolean? @default(false) - timePerChar Int? @default(50) @db.Integer + timePerChar Int? @default(50) @db.Int createdAt DateTime? @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id]) From bc11d0f751f68816db68a46788c6821d7328de2f Mon Sep 17 00:00:00 2001 From: William Dumes Date: Thu, 31 Jul 2025 17:27:06 -0300 Subject: [PATCH 14/29] fix: corrigido delete de mensagem quando nao salvo no banco de dados --- .../integrations/channel/whatsapp/whatsapp.baileys.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 7d7da2be..8496a759 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3447,6 +3447,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.prismaRepository.messageUpdate.create({ data: messageUpdate }); } } else { + if (!message) return response; await this.prismaRepository.message.deleteMany({ where: { id: message.id } }); } this.sendDataWebhook(Events.MESSAGES_DELETE, { From 79f4a22217e6cb52e9ed41254c5f1037ed545aa4 Mon Sep 17 00:00:00 2001 From: William Dumes Date: Mon, 4 Aug 2025 13:56:16 -0300 Subject: [PATCH 15/29] refactor: lint check --- .../integrations/channel/whatsapp/whatsapp.baileys.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index f9dbeed0..c036a281 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1482,7 +1482,7 @@ export class BaileysStartupService extends ChannelStartupService { continue; } - + if (findMessage && update.status !== undefined && status[update.status] !== findMessage.status) { if (!key.fromMe && key.remoteJid) { readChatToUpdate[key.remoteJid] = true; From 4f043f9576bbda13b16ae47981fd77151a199fad Mon Sep 17 00:00:00 2001 From: Felipe Augusto Rieck Date: Mon, 4 Aug 2025 16:34:20 -0300 Subject: [PATCH 16/29] Securing websockets --- src/api/integrations/event/websocket/websocket.controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index a1cef2db..c0d3e5de 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -28,10 +28,11 @@ export class WebsocketController extends EventController implements EventControl allowRequest: async (req, callback) => { try { const url = new URL(req.url || '', 'http://localhost'); + const isInternalConnection = req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::1'; const params = new URLSearchParams(url.search); // Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4) - if (params.has('EIO')) { + if (params.has('EIO') && isInternalConnection) { return callback(null, true); } From d4eb61f64d9fe263270146926e973018cf803880 Mon Sep 17 00:00:00 2001 From: Felipe Augusto Rieck Date: Mon, 4 Aug 2025 18:14:33 -0300 Subject: [PATCH 17/29] Improving localhost check --- .../integrations/event/websocket/websocket.controller.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index c0d3e5de..f7250b7a 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -28,11 +28,14 @@ export class WebsocketController extends EventController implements EventControl allowRequest: async (req, callback) => { try { const url = new URL(req.url || '', 'http://localhost'); - const isInternalConnection = req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::1'; const params = new URLSearchParams(url.search); + const remoteAddress = req.socket.remoteAddress; + const isLocalhost = + remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1'; + // Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4) - if (params.has('EIO') && isInternalConnection) { + if (params.has('EIO') && isLocalhost) { return callback(null, true); } From fb11f3f99cc19bf1631cb42538e29eb46cb87484 Mon Sep 17 00:00:00 2001 From: Felipe Augusto Rieck Date: Mon, 4 Aug 2025 18:19:14 -0300 Subject: [PATCH 18/29] Code quality --- src/api/integrations/event/websocket/websocket.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index f7250b7a..3f4afd9b 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -30,7 +30,7 @@ export class WebsocketController extends EventController implements EventControl const url = new URL(req.url || '', 'http://localhost'); const params = new URLSearchParams(url.search); - const remoteAddress = req.socket.remoteAddress; + const { remoteAddress } = req.socket; const isLocalhost = remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1'; From 3390958314f20b603788b7a975694014110b0975 Mon Sep 17 00:00:00 2001 From: Henry Barreto Date: Fri, 1 Aug 2025 19:40:39 -0300 Subject: [PATCH 19/29] feat: add support to socks proxy --- package-lock.json | 64 +++++++++++++++++++++++++++++++++++++ package.json | 1 + src/utils/makeProxyAgent.ts | 27 ++++++++++++++-- 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb9f0c48..6e81cc53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "sharp": "^0.34.2", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", + "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5" }, @@ -7739,6 +7740,19 @@ "node": ">= 0.4" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -8249,6 +8263,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -10761,6 +10781,16 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", @@ -10897,6 +10927,34 @@ } } }, + "node_modules/socks": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", @@ -10921,6 +10979,12 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index 9de9d07c..107cca49 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "sharp": "^0.34.2", "socket.io": "^4.8.1", "socket.io-client": "^4.8.1", + "socks-proxy-agent": "^8.0.5", "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5" }, diff --git a/src/utils/makeProxyAgent.ts b/src/utils/makeProxyAgent.ts index dcf560f6..3b1379bd 100644 --- a/src/utils/makeProxyAgent.ts +++ b/src/utils/makeProxyAgent.ts @@ -1,4 +1,5 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; +import { SocksProxyAgent } from 'socks-proxy-agent'; type Proxy = { host: string; @@ -8,9 +9,28 @@ type Proxy = { username?: string; }; -export function makeProxyAgent(proxy: Proxy | string) { +function selectProxyAgent(proxyUrl: string): HttpsProxyAgent | SocksProxyAgent { + const url = new URL(proxyUrl); + + // NOTE: The following constants are not used in the function but are defined for clarity. + // When a proxy URL is used to build the URL object, the protocol returned by procotol's property contains a `:` at + // the end so, we add the protocol constants without the `:` to avoid confusion. + const PROXY_HTTP_PROTOCOL = 'http:'; + const PROXY_SOCKS_PROTOCOL = 'socks:'; + + switch (url.protocol) { + case PROXY_HTTP_PROTOCOL: + return new HttpsProxyAgent(url); + case PROXY_SOCKS_PROTOCOL: + return new SocksProxyAgent(url); + default: + throw new Error(`Unsupported proxy protocol: ${url.protocol}`); + } +} + +export function makeProxyAgent(proxy: Proxy | string): HttpsProxyAgent | SocksProxyAgent { if (typeof proxy === 'string') { - return new HttpsProxyAgent(proxy); + return selectProxyAgent(proxy); } const { host, password, port, protocol, username } = proxy; @@ -19,5 +39,6 @@ export function makeProxyAgent(proxy: Proxy | string) { if (username && password) { proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`; } - return new HttpsProxyAgent(proxyUrl); + + return selectProxyAgent(proxyUrl); } From ab9e0edad684ec9ca6a15a4422ff3e01393f9ebd Mon Sep 17 00:00:00 2001 From: Henry Barreto Date: Fri, 1 Aug 2025 12:49:15 -0300 Subject: [PATCH 20/29] feat: enhance logging for proxy testing errors This commit improves the logging in the testProxy method of the ProxyController class. Now, when an Axios error occurs, the specific error message will be logged if available. For unexpected errors, the error object is included for better insight. For reference, see the "message" field in the Axios documentation: [Axios Error Handling](https://axios-http.com/docs/handling_errors). --- src/api/controllers/proxy.controller.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/api/controllers/proxy.controller.ts b/src/api/controllers/proxy.controller.ts index fac00375..b6e7d153 100644 --- a/src/api/controllers/proxy.controller.ts +++ b/src/api/controllers/proxy.controller.ts @@ -53,15 +53,21 @@ export class ProxyController { httpsAgent: makeProxyAgent(proxy), }); - return response?.data !== serverIp?.data; - } catch (error) { - if (axios.isAxiosError(error) && error.response?.data) { - logger.error('testProxy error: ' + error.response.data); - } else if (axios.isAxiosError(error)) { - logger.error('testProxy error: '); + const result = response?.data !== serverIp?.data; + if (result) { + logger.info('testProxy: proxy connection successful'); } else { - logger.error('testProxy error: '); + logger.warn("testProxy: proxy connection doesn't change the origin IP"); } + + return result; + } catch (error) { + if (axios.isAxiosError(error)) { + logger.error('testProxy error: axios error: ' + error.message); + } else { + logger.error('testProxy error: unexpected error: ' + error); + } + return false; } } From 4945345519478345d04becafabd055e566de7ad6 Mon Sep 17 00:00:00 2001 From: "Neto, Aristides da Silva" Date: Tue, 5 Aug 2025 22:07:20 -0300 Subject: [PATCH 21/29] docs(readme): corrigidos badge Docker image no README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrigida formatação do badge Docker image no README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ad5fa0a..6d9b3344 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![Docker Image (https://img.shields.io/badge/Docker-Image-blue)](https://hub.docker.com/r/evoapicloud/evolution-api)] +[![Docker Image](https://img.shields.io/badge/Docker-image-blue)](https://hub.docker.com/r/evoapicloud/evolution-api) [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp) [![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord) [![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman) From 74cb65c4ea9ee84f4b85eb238773e94e73cd2add Mon Sep 17 00:00:00 2001 From: Bilal Iqbal Date: Thu, 14 Aug 2025 00:51:41 +0500 Subject: [PATCH 22/29] Added key id into webhook payload in n8n service --- src/api/integrations/chatbot/n8n/services/n8n.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/integrations/chatbot/n8n/services/n8n.service.ts b/src/api/integrations/chatbot/n8n/services/n8n.service.ts index 2d0802b9..5bb40890 100644 --- a/src/api/integrations/chatbot/n8n/services/n8n.service.ts +++ b/src/api/integrations/chatbot/n8n/services/n8n.service.ts @@ -49,6 +49,7 @@ export class N8nService extends BaseChatbotService { sessionId: session.sessionId, remoteJid: remoteJid, pushName: pushName, + keyId: msg?.key?.id, fromMe: msg?.key?.fromMe, instanceName: instance.instanceName, serverUrl: this.configService.get('SERVER').URL, From 6e652d6ea25819fdfe22e398a6b0c61c07db0360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=C3=ADque=20Zanetoni=20Fim?= <50633626+caiquezanetoni@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:43:21 -0300 Subject: [PATCH 23/29] Update migration.sql --- .../migration.sql | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql b/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql index 26898a08..00f9bc7d 100644 --- a/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql +++ b/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql @@ -6,14 +6,4 @@ Warnings: */ -- AlterTable -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = 'Setting' - AND column_name = 'wavoipToken' - ) THEN - ALTER TABLE "Setting" ADD COLUMN "wavoipToken" VARCHAR(100); - END IF; -END $$; \ No newline at end of file +ALTER TABLE "Setting" ADD COLUMN IF NOT EXISTS "wavoipToken" VARCHAR(100); From 7a99fba556c183194bdf64e7dc4aed5026a3f55a Mon Sep 17 00:00:00 2001 From: luissantosjs Date: Wed, 20 Aug 2025 12:29:45 +0100 Subject: [PATCH 24/29] feat: enhance RabbitMQ controller with improved connection management and shutdown procedures --- .../event/rabbitmq/rabbitmq.controller.ts | 381 +++++++++++++----- 1 file changed, 270 insertions(+), 111 deletions(-) diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index be73b157..a6d1e565 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -14,6 +14,9 @@ export class RabbitmqController extends EventController implements EventControll private maxReconnectAttempts = 10; private reconnectDelay = 5000; // 5 seconds private isReconnecting = false; + private reconnectTimer: NodeJS.Timeout | null = null; + private connectionStatus: 'connected' | 'disconnected' | 'connecting' | 'reconnecting' = 'disconnected'; + private isShuttingDown = false; constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) { super(prismaRepository, waMonitor, configService.get('RABBITMQ')?.ENABLED, 'rabbitmq'); @@ -27,7 +30,91 @@ export class RabbitmqController extends EventController implements EventControll await this.connect(); } + public async shutdown(): Promise { + this.logger.info('Shutting down RabbitMQ controller...'); + this.isShuttingDown = true; + + // Clear any pending reconnect timer + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + + // Close channel and connection gracefully + await this.closeConnection(); + this.logger.info('RabbitMQ controller shutdown complete'); + } + + private async closeConnection(): Promise { + try { + if (this.amqpChannel) { + await new Promise((resolve) => { + this.amqpChannel?.close((err) => { + if (err) { + this.logger.warn(`Error closing channel: ${err.message}`); + } + resolve(); + }); + }); + this.amqpChannel = null; + } + + if (this.amqpConnection) { + await new Promise((resolve) => { + this.amqpConnection?.close((err) => { + if (err) { + this.logger.warn(`Error closing connection: ${err.message}`); + } + resolve(); + }); + }); + this.amqpConnection = null; + } + } catch (error) { + this.logger.error({ + local: 'RabbitmqController.closeConnection', + message: 'Error during connection cleanup', + error: error.message || error, + }); + } + } + + public getConnectionStatus(): string { + return this.connectionStatus; + } + + public isConnected(): boolean { + return this.connectionStatus === 'connected' && this.amqpChannel !== null && this.amqpConnection !== null; + } + + public async forceReconnect(): Promise { + this.logger.info('Force reconnect requested'); + + // Reset reconnect attempts for forced reconnect + this.reconnectAttempts = 0; + + // Close existing connections + await this.closeConnection(); + + // Clear any pending reconnect + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + + this.isReconnecting = false; + + // Attempt immediate reconnection + await this.connect(); + } + private async connect(): Promise { + if (this.isShuttingDown) { + return; + } + + this.connectionStatus = this.reconnectAttempts > 0 ? 'reconnecting' : 'connecting'; + return new Promise((resolve, reject) => { const uri = configService.get('RABBITMQ').URI; const frameMax = configService.get('RABBITMQ').FRAME_MAX; @@ -47,6 +134,7 @@ export class RabbitmqController extends EventController implements EventControll amqp.connect(connectionOptions, (error, connection) => { if (error) { + this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.connect', message: 'Failed to connect to RabbitMQ', @@ -63,16 +151,25 @@ export class RabbitmqController extends EventController implements EventControll message: 'RabbitMQ connection error', error: err.message || err, }); - this.handleConnectionLoss(); + this.handleConnectionLoss('connection_error', err); }); connection.on('close', () => { this.logger.warn('RabbitMQ connection closed'); - this.handleConnectionLoss(); + this.handleConnectionLoss('connection_closed'); + }); + + connection.on('blocked', (reason) => { + this.logger.warn(`RabbitMQ connection blocked: ${reason}`); + }); + + connection.on('unblocked', () => { + this.logger.info('RabbitMQ connection unblocked'); }); connection.createChannel((channelError, channel) => { if (channelError) { + this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.createChannel', message: 'Failed to create RabbitMQ channel', @@ -89,12 +186,21 @@ export class RabbitmqController extends EventController implements EventControll message: 'RabbitMQ channel error', error: err.message || err, }); - this.handleConnectionLoss(); + this.handleConnectionLoss('channel_error', err); }); channel.on('close', () => { this.logger.warn('RabbitMQ channel closed'); - this.handleConnectionLoss(); + this.handleConnectionLoss('channel_closed'); + }); + + channel.on('return', (msg) => { + this.logger.warn('RabbitMQ message returned' + JSON.stringify({ + exchange: msg.fields.exchange, + routingKey: msg.fields.routingKey, + replyCode: msg.fields.replyCode, + replyText: msg.fields.replyText, + })); }); const exchangeName = rabbitmqExchangeName; @@ -102,25 +208,37 @@ export class RabbitmqController extends EventController implements EventControll channel.assertExchange(exchangeName, 'topic', { durable: true, autoDelete: false, + }, (exchangeError) => { + if (exchangeError) { + this.connectionStatus = 'disconnected'; + this.logger.error({ + local: 'RabbitmqController.assertExchange', + message: 'Failed to assert exchange', + error: exchangeError.message || exchangeError, + }); + reject(exchangeError); + return; + } + + this.amqpConnection = connection; + this.amqpChannel = channel; + this.reconnectAttempts = 0; // Reset reconnect attempts on successful connection + this.isReconnecting = false; + this.connectionStatus = 'connected'; + + this.logger.info('AMQP initialized successfully'); + resolve(); }); - - this.amqpConnection = connection; - this.amqpChannel = channel; - this.reconnectAttempts = 0; // Reset reconnect attempts on successful connection - this.isReconnecting = false; - - this.logger.info('AMQP initialized successfully'); - - resolve(); }); }); }) .then(() => { if (configService.get('RABBITMQ')?.GLOBAL_ENABLED) { - this.initGlobalQueues(); + return this.initGlobalQueues(); } }) .catch((error) => { + this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.init', message: 'Failed to initialize AMQP', @@ -131,21 +249,30 @@ export class RabbitmqController extends EventController implements EventControll }); } - private handleConnectionLoss(): void { - if (this.isReconnecting) { - return; // Already attempting to reconnect + private handleConnectionLoss(reason?: string, error?: any): void { + if (this.isReconnecting || this.isShuttingDown) { + return; // Already attempting to reconnect or shutting down } + this.logger.warn(`Connection lost due to: ${reason || 'unknown reason'}` + JSON.stringify(error ? { error: error.message || error } : {})); + + this.connectionStatus = 'disconnected'; this.amqpChannel = null; this.amqpConnection = null; + this.scheduleReconnect(); } private scheduleReconnect(): void { + if (this.isShuttingDown) { + return; + } + if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.logger.error( `Maximum reconnect attempts (${this.maxReconnectAttempts}) reached. Stopping reconnection attempts.`, ); + this.connectionStatus = 'disconnected'; return; } @@ -162,7 +289,11 @@ export class RabbitmqController extends EventController implements EventControll `Scheduling RabbitMQ reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`, ); - setTimeout(async () => { + this.reconnectTimer = setTimeout(async () => { + if (this.isShuttingDown) { + return; + } + try { this.logger.info( `Attempting to reconnect to RabbitMQ (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`, @@ -177,6 +308,8 @@ export class RabbitmqController extends EventController implements EventControll }); this.isReconnecting = false; this.scheduleReconnect(); + } finally { + this.reconnectTimer = null; } }, delay); } @@ -190,9 +323,9 @@ export class RabbitmqController extends EventController implements EventControll } private async ensureConnection(): Promise { - if (!this.amqpChannel) { + if (!this.amqpChannel || !this.isConnected()) { this.logger.warn('AMQP channel is not available, attempting to reconnect...'); - if (!this.isReconnecting) { + if (!this.isReconnecting && !this.isShuttingDown) { this.scheduleReconnect(); } return false; @@ -200,6 +333,25 @@ export class RabbitmqController extends EventController implements EventControll return true; } + public async waitForConnection(timeoutMs: number = 30000): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + if (this.isConnected()) { + return true; + } + + if (this.isShuttingDown) { + return false; + } + + // Wait 100ms before checking again + await new Promise(resolve => setTimeout(resolve, 100)); + } + + return false; + } + public async emit({ instanceName, origin, @@ -246,106 +398,113 @@ export class RabbitmqController extends EventController implements EventControll if (instanceRabbitmq?.enabled && this.amqpChannel) { if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) { const exchangeName = instanceName ?? rabbitmqExchangeName; - - let retry = 0; - - while (retry < 3) { - try { - await this.amqpChannel.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); - - const eventName = event.replace(/_/g, '.').toLowerCase(); - - const queueName = `${instanceName}.${eventName}`; - - await this.amqpChannel.assertQueue(queueName, { - durable: true, - autoDelete: false, - arguments: { - 'x-queue-type': 'quorum', - }, - }); - - await this.amqpChannel.bindQueue(queueName, exchangeName, eventName); - - await this.amqpChannel.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); - - if (logEnabled) { - const logData = { - local: `${origin}.sendData-RabbitMQ`, - ...message, - }; - - this.logger.log(logData); - } - - break; - } catch (error) { - this.logger.error({ - local: 'RabbitmqController.emit', - message: `Error publishing local RabbitMQ message (attempt ${retry + 1}/3)`, - error: error.message || error, - }); - retry++; - if (retry >= 3) { - this.handleConnectionLoss(); - } - } - } + await this.publishMessage(exchangeName, event, message, instanceName, origin, logEnabled, 'local'); } } if (rabbitmqGlobal && rabbitmqEvents[we] && this.amqpChannel) { const exchangeName = rabbitmqExchangeName; + await this.publishMessage(exchangeName, event, message, instanceName, origin, logEnabled, 'global'); + } + } - let retry = 0; + private async publishMessage( + exchangeName: string, + event: string, + message: any, + instanceName: string, + origin: string, + logEnabled: boolean, + type: 'local' | 'global' + ): Promise { + let retry = 0; + const maxRetries = 3; - while (retry < 3) { - try { - await this.amqpChannel.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); + while (retry < maxRetries) { + try { + if (!(await this.ensureConnection())) { + throw new Error('No AMQP connection available'); + } - const queueName = prefixKey + await this.amqpChannel.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); + + let queueName: string; + let routingKey: string; + + if (type === 'local') { + const eventName = event.replace(/_/g, '.').toLowerCase(); + queueName = `${instanceName}.${eventName}`; + routingKey = eventName; + } else { + const prefixKey = configService.get('RABBITMQ').PREFIX_KEY; + queueName = prefixKey ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` : event.replace(/_/g, '.').toLowerCase(); - - await this.amqpChannel.assertQueue(queueName, { - durable: true, - autoDelete: false, - arguments: { - 'x-queue-type': 'quorum', - }, - }); - - await this.amqpChannel.bindQueue(queueName, exchangeName, event); - - await this.amqpChannel.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); - - if (logEnabled) { - const logData = { - local: `${origin}.sendData-RabbitMQ-Global`, - ...message, - }; - - this.logger.log(logData); - } - - break; - } catch (error) { - this.logger.error({ - local: 'RabbitmqController.emit', - message: `Error publishing global RabbitMQ message (attempt ${retry + 1}/3)`, - error: error.message || error, - }); - retry++; - if (retry >= 3) { - this.handleConnectionLoss(); - } + routingKey = event; } + + await this.amqpChannel.assertQueue(queueName, { + durable: true, + autoDelete: false, + arguments: { + 'x-queue-type': 'quorum', + }, + }); + + await this.amqpChannel.bindQueue(queueName, exchangeName, routingKey); + + const published = await new Promise((resolve) => { + const success = this.amqpChannel.publish( + exchangeName, + routingKey, + Buffer.from(JSON.stringify(message)), + { persistent: true }, + (err) => { + if (err) { + resolve(false); + } else { + resolve(true); + } + } + ); + + if (!success) { + resolve(false); + } + }); + + if (!published) { + throw new Error('Failed to publish message - channel write buffer full'); + } + + if (logEnabled) { + const logData = { + local: `${origin}.sendData-RabbitMQ${type === 'global' ? '-Global' : ''}`, + ...message, + }; + + this.logger.log(logData); + } + + break; // Success, exit retry loop + } catch (error) { + this.logger.error({ + local: 'RabbitmqController.publishMessage', + message: `Error publishing ${type} RabbitMQ message (attempt ${retry + 1}/${maxRetries})`, + error: error.message || error, + }); + retry++; + + if (retry >= maxRetries) { + this.handleConnectionLoss('publish_error', error); + throw error; + } + + // Wait before retry + await new Promise(resolve => setTimeout(resolve, 1000 * retry)); } } } @@ -401,9 +560,9 @@ export class RabbitmqController extends EventController implements EventControll message: `Failed to initialize global queue for event ${event}`, error: error.message || error, }); - this.handleConnectionLoss(); + this.handleConnectionLoss('queue_init_error', error); break; } } } -} +} \ No newline at end of file From 4681576cfca1a6042dc615ba724ce59ba4a3579d Mon Sep 17 00:00:00 2001 From: luissantosjs Date: Fri, 22 Aug 2025 22:49:40 +0100 Subject: [PATCH 25/29] up --- .../event/rabbitmq/rabbitmq.controller.ts | 369 +++++------------- 1 file changed, 105 insertions(+), 264 deletions(-) diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index a6d1e565..24468ac8 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -14,9 +14,6 @@ export class RabbitmqController extends EventController implements EventControll private maxReconnectAttempts = 10; private reconnectDelay = 5000; // 5 seconds private isReconnecting = false; - private reconnectTimer: NodeJS.Timeout | null = null; - private connectionStatus: 'connected' | 'disconnected' | 'connecting' | 'reconnecting' = 'disconnected'; - private isShuttingDown = false; constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) { super(prismaRepository, waMonitor, configService.get('RABBITMQ')?.ENABLED, 'rabbitmq'); @@ -30,91 +27,7 @@ export class RabbitmqController extends EventController implements EventControll await this.connect(); } - public async shutdown(): Promise { - this.logger.info('Shutting down RabbitMQ controller...'); - this.isShuttingDown = true; - - // Clear any pending reconnect timer - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = null; - } - - // Close channel and connection gracefully - await this.closeConnection(); - this.logger.info('RabbitMQ controller shutdown complete'); - } - - private async closeConnection(): Promise { - try { - if (this.amqpChannel) { - await new Promise((resolve) => { - this.amqpChannel?.close((err) => { - if (err) { - this.logger.warn(`Error closing channel: ${err.message}`); - } - resolve(); - }); - }); - this.amqpChannel = null; - } - - if (this.amqpConnection) { - await new Promise((resolve) => { - this.amqpConnection?.close((err) => { - if (err) { - this.logger.warn(`Error closing connection: ${err.message}`); - } - resolve(); - }); - }); - this.amqpConnection = null; - } - } catch (error) { - this.logger.error({ - local: 'RabbitmqController.closeConnection', - message: 'Error during connection cleanup', - error: error.message || error, - }); - } - } - - public getConnectionStatus(): string { - return this.connectionStatus; - } - - public isConnected(): boolean { - return this.connectionStatus === 'connected' && this.amqpChannel !== null && this.amqpConnection !== null; - } - - public async forceReconnect(): Promise { - this.logger.info('Force reconnect requested'); - - // Reset reconnect attempts for forced reconnect - this.reconnectAttempts = 0; - - // Close existing connections - await this.closeConnection(); - - // Clear any pending reconnect - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = null; - } - - this.isReconnecting = false; - - // Attempt immediate reconnection - await this.connect(); - } - private async connect(): Promise { - if (this.isShuttingDown) { - return; - } - - this.connectionStatus = this.reconnectAttempts > 0 ? 'reconnecting' : 'connecting'; - return new Promise((resolve, reject) => { const uri = configService.get('RABBITMQ').URI; const frameMax = configService.get('RABBITMQ').FRAME_MAX; @@ -134,7 +47,6 @@ export class RabbitmqController extends EventController implements EventControll amqp.connect(connectionOptions, (error, connection) => { if (error) { - this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.connect', message: 'Failed to connect to RabbitMQ', @@ -151,25 +63,16 @@ export class RabbitmqController extends EventController implements EventControll message: 'RabbitMQ connection error', error: err.message || err, }); - this.handleConnectionLoss('connection_error', err); + this.handleConnectionLoss(); }); connection.on('close', () => { this.logger.warn('RabbitMQ connection closed'); - this.handleConnectionLoss('connection_closed'); - }); - - connection.on('blocked', (reason) => { - this.logger.warn(`RabbitMQ connection blocked: ${reason}`); - }); - - connection.on('unblocked', () => { - this.logger.info('RabbitMQ connection unblocked'); + this.handleConnectionLoss(); }); connection.createChannel((channelError, channel) => { if (channelError) { - this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.createChannel', message: 'Failed to create RabbitMQ channel', @@ -186,21 +89,12 @@ export class RabbitmqController extends EventController implements EventControll message: 'RabbitMQ channel error', error: err.message || err, }); - this.handleConnectionLoss('channel_error', err); + this.handleConnectionLoss(); }); channel.on('close', () => { this.logger.warn('RabbitMQ channel closed'); - this.handleConnectionLoss('channel_closed'); - }); - - channel.on('return', (msg) => { - this.logger.warn('RabbitMQ message returned' + JSON.stringify({ - exchange: msg.fields.exchange, - routingKey: msg.fields.routingKey, - replyCode: msg.fields.replyCode, - replyText: msg.fields.replyText, - })); + this.handleConnectionLoss(); }); const exchangeName = rabbitmqExchangeName; @@ -208,37 +102,25 @@ export class RabbitmqController extends EventController implements EventControll channel.assertExchange(exchangeName, 'topic', { durable: true, autoDelete: false, - }, (exchangeError) => { - if (exchangeError) { - this.connectionStatus = 'disconnected'; - this.logger.error({ - local: 'RabbitmqController.assertExchange', - message: 'Failed to assert exchange', - error: exchangeError.message || exchangeError, - }); - reject(exchangeError); - return; - } - - this.amqpConnection = connection; - this.amqpChannel = channel; - this.reconnectAttempts = 0; // Reset reconnect attempts on successful connection - this.isReconnecting = false; - this.connectionStatus = 'connected'; - - this.logger.info('AMQP initialized successfully'); - resolve(); }); + + this.amqpConnection = connection; + this.amqpChannel = channel; + this.reconnectAttempts = 0; // Reset reconnect attempts on successful connection + this.isReconnecting = false; + + this.logger.info('AMQP initialized successfully'); + + resolve(); }); }); }) .then(() => { if (configService.get('RABBITMQ')?.GLOBAL_ENABLED) { - return this.initGlobalQueues(); + this.initGlobalQueues(); } }) .catch((error) => { - this.connectionStatus = 'disconnected'; this.logger.error({ local: 'RabbitmqController.init', message: 'Failed to initialize AMQP', @@ -249,30 +131,21 @@ export class RabbitmqController extends EventController implements EventControll }); } - private handleConnectionLoss(reason?: string, error?: any): void { - if (this.isReconnecting || this.isShuttingDown) { - return; // Already attempting to reconnect or shutting down + private handleConnectionLoss(): void { + if (this.isReconnecting) { + return; // Already attempting to reconnect } - this.logger.warn(`Connection lost due to: ${reason || 'unknown reason'}` + JSON.stringify(error ? { error: error.message || error } : {})); - - this.connectionStatus = 'disconnected'; this.amqpChannel = null; this.amqpConnection = null; - this.scheduleReconnect(); } private scheduleReconnect(): void { - if (this.isShuttingDown) { - return; - } - if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.logger.error( `Maximum reconnect attempts (${this.maxReconnectAttempts}) reached. Stopping reconnection attempts.`, ); - this.connectionStatus = 'disconnected'; return; } @@ -289,11 +162,7 @@ export class RabbitmqController extends EventController implements EventControll `Scheduling RabbitMQ reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`, ); - this.reconnectTimer = setTimeout(async () => { - if (this.isShuttingDown) { - return; - } - + setTimeout(async () => { try { this.logger.info( `Attempting to reconnect to RabbitMQ (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`, @@ -308,8 +177,6 @@ export class RabbitmqController extends EventController implements EventControll }); this.isReconnecting = false; this.scheduleReconnect(); - } finally { - this.reconnectTimer = null; } }, delay); } @@ -323,9 +190,9 @@ export class RabbitmqController extends EventController implements EventControll } private async ensureConnection(): Promise { - if (!this.amqpChannel || !this.isConnected()) { + if (!this.amqpChannel) { this.logger.warn('AMQP channel is not available, attempting to reconnect...'); - if (!this.isReconnecting && !this.isShuttingDown) { + if (!this.isReconnecting) { this.scheduleReconnect(); } return false; @@ -333,25 +200,6 @@ export class RabbitmqController extends EventController implements EventControll return true; } - public async waitForConnection(timeoutMs: number = 30000): Promise { - const startTime = Date.now(); - - while (Date.now() - startTime < timeoutMs) { - if (this.isConnected()) { - return true; - } - - if (this.isShuttingDown) { - return false; - } - - // Wait 100ms before checking again - await new Promise(resolve => setTimeout(resolve, 100)); - } - - return false; - } - public async emit({ instanceName, origin, @@ -398,113 +246,106 @@ export class RabbitmqController extends EventController implements EventControll if (instanceRabbitmq?.enabled && this.amqpChannel) { if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) { const exchangeName = instanceName ?? rabbitmqExchangeName; - await this.publishMessage(exchangeName, event, message, instanceName, origin, logEnabled, 'local'); + + let retry = 0; + + while (retry < 3) { + try { + await this.amqpChannel.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); + + const eventName = event.replace(/_/g, '.').toLowerCase(); + + const queueName = `${instanceName}.${eventName}`; + + await this.amqpChannel.assertQueue(queueName, { + durable: true, + autoDelete: false, + arguments: { + 'x-queue-type': 'quorum', + }, + }); + + await this.amqpChannel.bindQueue(queueName, exchangeName, eventName); + + await this.amqpChannel.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); + + if (logEnabled) { + const logData = { + local: `${origin}.sendData-RabbitMQ`, + ...message, + }; + + this.logger.log(logData); + } + + break; + } catch (error) { + this.logger.error({ + local: 'RabbitmqController.emit', + message: `Error publishing local RabbitMQ message (attempt ${retry + 1}/3)`, + error: error.message || error, + }); + retry++; + if (retry >= 3) { + this.handleConnectionLoss(); + } + } + } } } if (rabbitmqGlobal && rabbitmqEvents[we] && this.amqpChannel) { const exchangeName = rabbitmqExchangeName; - await this.publishMessage(exchangeName, event, message, instanceName, origin, logEnabled, 'global'); - } - } - private async publishMessage( - exchangeName: string, - event: string, - message: any, - instanceName: string, - origin: string, - logEnabled: boolean, - type: 'local' | 'global' - ): Promise { - let retry = 0; - const maxRetries = 3; + let retry = 0; - while (retry < maxRetries) { - try { - if (!(await this.ensureConnection())) { - throw new Error('No AMQP connection available'); - } + while (retry < 3) { + try { + await this.amqpChannel.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); - await this.amqpChannel.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); - - let queueName: string; - let routingKey: string; - - if (type === 'local') { - const eventName = event.replace(/_/g, '.').toLowerCase(); - queueName = `${instanceName}.${eventName}`; - routingKey = eventName; - } else { - const prefixKey = configService.get('RABBITMQ').PREFIX_KEY; - queueName = prefixKey + const queueName = prefixKey ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` : event.replace(/_/g, '.').toLowerCase(); - routingKey = event; - } - await this.amqpChannel.assertQueue(queueName, { - durable: true, - autoDelete: false, - arguments: { - 'x-queue-type': 'quorum', - }, - }); + await this.amqpChannel.assertQueue(queueName, { + durable: true, + autoDelete: false, + arguments: { + 'x-queue-type': 'quorum', + }, + }); - await this.amqpChannel.bindQueue(queueName, exchangeName, routingKey); + await this.amqpChannel.bindQueue(queueName, exchangeName, event); - const published = await new Promise((resolve) => { - const success = this.amqpChannel.publish( - exchangeName, - routingKey, - Buffer.from(JSON.stringify(message)), - { persistent: true }, - (err) => { - if (err) { - resolve(false); - } else { - resolve(true); - } - } - ); - - if (!success) { - resolve(false); + await this.amqpChannel.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); + + if (logEnabled) { + const logData = { + local: `${origin}.sendData-RabbitMQ-Global`, + ...message, + }; + + this.logger.log(logData); } - }); - if (!published) { - throw new Error('Failed to publish message - channel write buffer full'); + break; + } catch (error) { + this.logger.error({ + local: 'RabbitmqController.emit', + message: `Error publishing global RabbitMQ message (attempt ${retry + 1}/3)`, + error: error.message || error, + }); + retry++; + if (retry >= 3) { + this.handleConnectionLoss(); + } } - - if (logEnabled) { - const logData = { - local: `${origin}.sendData-RabbitMQ${type === 'global' ? '-Global' : ''}`, - ...message, - }; - - this.logger.log(logData); - } - - break; // Success, exit retry loop - } catch (error) { - this.logger.error({ - local: 'RabbitmqController.publishMessage', - message: `Error publishing ${type} RabbitMQ message (attempt ${retry + 1}/${maxRetries})`, - error: error.message || error, - }); - retry++; - - if (retry >= maxRetries) { - this.handleConnectionLoss('publish_error', error); - throw error; - } - - // Wait before retry - await new Promise(resolve => setTimeout(resolve, 1000 * retry)); } } } @@ -560,7 +401,7 @@ export class RabbitmqController extends EventController implements EventControll message: `Failed to initialize global queue for event ${event}`, error: error.message || error, }); - this.handleConnectionLoss('queue_init_error', error); + this.handleConnectionLoss(); break; } } From b325500310da00a827baea406cbff8192a682fb7 Mon Sep 17 00:00:00 2001 From: luissantosjs Date: Tue, 26 Aug 2025 19:15:59 +0100 Subject: [PATCH 26/29] improve rabbit controller --- .../event/rabbitmq/rabbitmq.controller.ts | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index 24468ac8..3295b12d 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -45,7 +45,7 @@ export class RabbitmqController extends EventController implements EventControll heartbeat: 30, // Add heartbeat of 30 seconds }; - amqp.connect(connectionOptions, (error, connection) => { + amqp.connect(connectionOptions, (error: Error, connection: amqp.Connection) => { if (error) { this.logger.error({ local: 'RabbitmqController.connect', @@ -57,7 +57,7 @@ export class RabbitmqController extends EventController implements EventControll } // Connection event handlers - connection.on('error', (err) => { + connection.on('error', (err: Error) => { this.logger.error({ local: 'RabbitmqController.connectionError', message: 'RabbitMQ connection error', @@ -71,7 +71,7 @@ export class RabbitmqController extends EventController implements EventControll this.handleConnectionLoss(); }); - connection.createChannel((channelError, channel) => { + connection.createChannel((channelError: Error, channel: amqp.Channel) => { if (channelError) { this.logger.error({ local: 'RabbitmqController.createChannel', @@ -83,7 +83,7 @@ export class RabbitmqController extends EventController implements EventControll } // Channel event handlers - channel.on('error', (err) => { + channel.on('error', (err: Error) => { this.logger.error({ local: 'RabbitmqController.channelError', message: 'RabbitMQ channel error', @@ -136,8 +136,7 @@ export class RabbitmqController extends EventController implements EventControll return; // Already attempting to reconnect } - this.amqpChannel = null; - this.amqpConnection = null; + this.cleanup(); this.scheduleReconnect(); } @@ -406,4 +405,25 @@ export class RabbitmqController extends EventController implements EventControll } } } -} \ No newline at end of file + + public async cleanup(): Promise { + try { + if (this.amqpChannel) { + await this.amqpChannel.close(); + this.amqpChannel = null; + } + if (this.amqpConnection) { + await this.amqpConnection.close(); + this.amqpConnection = null; + } + } catch (error) { + this.logger.warn({ + local: 'RabbitmqController.cleanup', + message: 'Error during cleanup', + error: error.message || error, + }); + this.amqpChannel = null; + this.amqpConnection = null; + } + } +} From 57b19d85d575bf4bc44018058a320b04c0cacf33 Mon Sep 17 00:00:00 2001 From: nestordavalos Date: Thu, 28 Aug 2025 00:17:15 -0300 Subject: [PATCH 27/29] feat(whatsapp): Convert outgoing images to JPEG before sending All images sent via the Baileys integration are now pre-processed and converted to JPEG format using the `sharp` library. This ensures better compatibility and prevents potential issues with unsupported formats. - Images from URLs are now downloaded via axios before processing, which allows for the use of a proxy. - The default filename and mimetype are updated to `image.jpg` and `image/jpeg` to reflect the conversion. --- .../whatsapp/whatsapp.baileys.service.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index c036a281..c70ab6f6 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -2448,9 +2448,43 @@ export class BaileysStartupService extends ChannelStartupService { try { const type = mediaMessage.mediatype === 'ptv' ? 'video' : mediaMessage.mediatype; + let mediaInput: any; + if (mediaMessage.mediatype === 'image') { + let imageBuffer: Buffer; + if (isURL(mediaMessage.media)) { + let config: any = { responseType: 'arraybuffer' }; + + if (this.localProxy?.enabled) { + config = { + ...config, + httpsAgent: makeProxyAgent({ + host: this.localProxy.host, + port: this.localProxy.port, + protocol: this.localProxy.protocol, + username: this.localProxy.username, + password: this.localProxy.password, + }), + }; + } + + const response = await axios.get(mediaMessage.media, config); + imageBuffer = Buffer.from(response.data, 'binary'); + } else { + imageBuffer = Buffer.from(mediaMessage.media, 'base64'); + } + + mediaInput = await sharp(imageBuffer).jpeg().toBuffer(); + mediaMessage.fileName ??= 'image.jpg'; + mediaMessage.mimetype = 'image/jpeg'; + } else { + mediaInput = isURL(mediaMessage.media) + ? { url: mediaMessage.media } + : Buffer.from(mediaMessage.media, 'base64'); + } + const prepareMedia = await prepareWAMessageMedia( { - [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'), + [type]: mediaInput, } as any, { upload: this.client.waUploadToServer }, ); @@ -2464,7 +2498,7 @@ export class BaileysStartupService extends ChannelStartupService { } if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { - mediaMessage.fileName = 'image.png'; + mediaMessage.fileName = 'image.jpg'; } if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { From 00ba227e153b4559739122ef7ce0e294f43e7b7e Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 1 Sep 2025 10:43:03 -0300 Subject: [PATCH 28/29] chore: update baileys dependency to version 6.7.19 --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e81cc53..fd6942a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4986,8 +4986,8 @@ } }, "node_modules/baileys": { - "version": "6.7.18", - "resolved": "git+ssh://git@github.com/WhiskeySockets/Baileys.git#b7876da2e5d8d4d4b391e215b48b668517b86f3e", + "version": "6.7.19", + "resolved": "git+ssh://git@github.com/WhiskeySockets/Baileys.git#9e04cce8d3eeb16025283a57849cc83aa26c6dd1", "hasInstallScript": true, "license": "MIT", "dependencies": { From b8953f1431c18d0d6686c74b3bf6dd9f3f47333c Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 2 Sep 2025 10:53:00 -0300 Subject: [PATCH 29/29] chore: update CHANGELOG for version 2.3.2 - Added support for socks proxy. - Included key id in webhook payload for n8n service. - Enhanced RabbitMQ controller with improved connection management and shutdown procedures. - Converted outgoing images to JPEG format before sending with Chatwoot. - Updated baileys dependency to version 6.7.19. --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383282ce..b8f25542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ -# 2.3.1 (develop) +# 2.3.2 (2025-09-02) + +### Features + +* Add support to socks proxy + +### Fixed + +* Added key id into webhook payload in n8n service +* Enhance RabbitMQ controller with improved connection management and shutdown procedures +* Convert outgoing images to JPEG before sending with Chatwoot +* Update baileys dependency to version 6.7.19 + +# 2.3.1 (2025-07-29) ### Feature