From 882ef5245a3a386763370e285994660c9d37ca2a Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Thu, 27 Feb 2025 21:40:01 -0300 Subject: [PATCH 01/20] feat: Add configurable file/cache storage for authentication state - Update Baileys package to version 6.7.10 - Implement conditional storage mechanism for authentication state - Add support for file-based or Redis cache storage based on environment configuration - Re-enable previously commented out file handling utility functions --- package-lock.json | 75 ++++++++++--------- package.json | 4 +- src/utils/use-multi-file-auth-state-prisma.ts | 60 ++++++++------- 3 files changed, 78 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index ada8fbce..4889f9a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@aws-sdk/client-sqs": "^3.723.0", "@hapi/boom": "^10.0.1", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^6.1.0", + "@prisma/client": "^5.22.0", "@sentry/node": "^8.47.0", "amqplib": "^0.10.5", "axios": "^1.7.9", @@ -41,7 +41,7 @@ "node-cache": "^5.1.2", "node-cron": "^3.0.3", "pino": "^8.11.0", - "prisma": "^6.1.0", + "prisma": "^5.22.0", "pusher": "^5.2.0", "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", @@ -2390,12 +2390,13 @@ } }, "node_modules/@prisma/client": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", - "integrity": "sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", "hasInstallScript": true, + "license": "Apache-2.0", "engines": { - "node": ">=18.18" + "node": ">=16.13" }, "peerDependencies": { "prisma": "*" @@ -2407,43 +2408,48 @@ } }, "node_modules/@prisma/debug": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.2.1.tgz", - "integrity": "sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==" + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.2.1.tgz", - "integrity": "sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1", - "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "@prisma/fetch-engine": "6.2.1", - "@prisma/get-platform": "6.2.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz", - "integrity": "sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==" + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz", - "integrity": "sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1", - "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "@prisma/get-platform": "6.2.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.2.1.tgz", - "integrity": "sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1" + "@prisma/debug": "5.22.0" } }, "node_modules/@prisma/instrumentation": { @@ -9296,18 +9302,19 @@ } }, "node_modules/prisma": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.2.1.tgz", - "integrity": "sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "6.2.1" + "@prisma/engines": "5.22.0" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=18.18" + "node": ">=16.13" }, "optionalDependencies": { "fsevents": "2.3.3" diff --git a/package.json b/package.json index 04ccf1ef..b9e92ab5 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@aws-sdk/client-sqs": "^3.723.0", "@hapi/boom": "^10.0.1", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^6.1.0", + "@prisma/client": "^5.22.0", "@sentry/node": "^8.47.0", "amqplib": "^0.10.5", "axios": "^1.7.9", @@ -81,7 +81,7 @@ "node-cache": "^5.1.2", "node-cron": "^3.0.3", "pino": "^8.11.0", - "prisma": "^6.1.0", + "prisma": "^5.22.0", "pusher": "^5.2.0", "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", diff --git a/src/utils/use-multi-file-auth-state-prisma.ts b/src/utils/use-multi-file-auth-state-prisma.ts index 02f96f15..e16dc8b0 100644 --- a/src/utils/use-multi-file-auth-state-prisma.ts +++ b/src/utils/use-multi-file-auth-state-prisma.ts @@ -5,14 +5,14 @@ import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from import fs from 'fs/promises'; import path from 'path'; -// const fixFileName = (file: string): string | undefined => { -// if (!file) { -// return undefined; -// } -// const replacedSlash = file.replace(/\//g, '__'); -// const replacedColon = replacedSlash.replace(/:/g, '-'); -// return replacedColon; -// }; +const fixFileName = (file: string): string | undefined => { + if (!file) { + return undefined; + } + const replacedSlash = file.replace(/\//g, '__'); + const replacedColon = replacedSlash.replace(/:/g, '-'); + return replacedColon; +}; export async function keyExists(sessionId: string): Promise { try { @@ -63,14 +63,14 @@ async function deleteAuthKey(sessionId: string): Promise { } } -// async function fileExists(file: string): Promise { -// try { -// const stat = await fs.stat(file); -// if (stat.isFile()) return true; -// } catch (error) { -// return; -// } -// } +async function fileExists(file: string): Promise { + try { + const stat = await fs.stat(file); + if (stat.isFile()) return true; + } catch (error) { + return; + } +} export default async function useMultiFileAuthStatePrisma( sessionId: string, @@ -80,16 +80,19 @@ export default async function useMultiFileAuthStatePrisma( saveCreds: () => Promise; }> { const localFolder = path.join(INSTANCE_DIR, sessionId); - // const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json'); + const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json'); await fs.mkdir(localFolder, { recursive: true }); async function writeData(data: any, key: string): Promise { const dataString = JSON.stringify(data, BufferJSON.replacer); if (key != 'creds') { - return await cache.hSet(sessionId, key, data); - // await fs.writeFile(localFile(key), dataString); - // return; + if (process.env.CACHE_REDIS_ENABLED === 'true') { + return await cache.hSet(sessionId, key, data); + } else { + await fs.writeFile(localFile(key), dataString); + return; + } } await saveKey(sessionId, dataString); return; @@ -100,9 +103,13 @@ export default async function useMultiFileAuthStatePrisma( let rawData; if (key != 'creds') { - return await cache.hGet(sessionId, key); - // if (!(await fileExists(localFile(key)))) return null; - // rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' }); + if (process.env.CACHE_REDIS_ENABLED === 'true') { + return await cache.hGet(sessionId, key); + } else { + if (!(await fileExists(localFile(key)))) return null; + rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' }); + return JSON.parse(rawData, BufferJSON.reviver); + } } else { rawData = await getAuthKey(sessionId); } @@ -117,8 +124,11 @@ export default async function useMultiFileAuthStatePrisma( async function removeData(key: string): Promise { try { if (key != 'creds') { - return await cache.hDelete(sessionId, key); - // await fs.unlink(localFile(key)); + if (process.env.CACHE_REDIS_ENABLED === 'true') { + return await cache.hDelete(sessionId, key); + } else { + await fs.unlink(localFile(key)); + } } else { await deleteAuthKey(sessionId); } From 66e35f9f0cd46123522462a99249544c06fb5620 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 01:49:54 -0300 Subject: [PATCH 02/20] feat(rabbitmq): Add prefix key configuration for queue names --- .env.example | 2 ++ CHANGELOG.md | 6 ++++++ .../event/rabbitmq/rabbitmq.controller.ts | 11 +++++++++-- src/config/env.config.ts | 2 ++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index a3f438ea..d057d187 100644 --- a/.env.example +++ b/.env.example @@ -49,6 +49,8 @@ RABBITMQ_URI=amqp://localhost RABBITMQ_EXCHANGE_NAME=evolution # Global events - By enabling this variable, events from all instances are sent in the same event queue. RABBITMQ_GLOBAL_ENABLED=false +# Prefix key to queue name +RABBITMQ_PREFIX_KEY=evolution # Choose the events you want to send to RabbitMQ RABBITMQ_EVENTS_APPLICATION_STARTUP=false RABBITMQ_EVENTS_INSTANCE_CREATE=false diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e95d1d..728a93fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.2 (develop) + +### Features + +* Added prefix key to queue name in RabbitMQ + # 2.2.1 (2025-01-22 14:37) ### Features diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index d7623441..71997405 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -87,6 +87,7 @@ export class RabbitmqController extends EventController implements EventControll const rabbitmqLocal = instanceRabbitmq?.events; const rabbitmqGlobal = configService.get('RABBITMQ').GLOBAL_ENABLED; const rabbitmqEvents = configService.get('RABBITMQ').EVENTS; + const prefixKey = configService.get('RABBITMQ').PREFIX_KEY; const rabbitmqExchangeName = configService.get('RABBITMQ').EXCHANGE_NAME; const we = event.replace(/[.-]/gm, '_').toUpperCase(); const logEnabled = configService.get('LOG').LEVEL.includes('WEBHOOKS'); @@ -159,7 +160,9 @@ export class RabbitmqController extends EventController implements EventControll autoDelete: false, }); - const queueName = event; + const queueName = prefixKey + ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` + : event.replace(/_/g, '.').toLowerCase(); await this.amqpChannel.assertQueue(queueName, { durable: true, @@ -195,6 +198,7 @@ export class RabbitmqController extends EventController implements EventControll const rabbitmqExchangeName = configService.get('RABBITMQ').EXCHANGE_NAME; const events = configService.get('RABBITMQ').EVENTS; + const prefixKey = configService.get('RABBITMQ').PREFIX_KEY; if (!events) { this.logger.warn('No events to initialize on AMQP'); @@ -207,7 +211,10 @@ export class RabbitmqController extends EventController implements EventControll eventKeys.forEach((event) => { if (events[event] === false) return; - const queueName = `${event.replace(/_/g, '.').toLowerCase()}`; + const queueName = + prefixKey !== '' + ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` + : `${event.replace(/_/g, '.').toLowerCase()}`; const exchangeName = rabbitmqExchangeName; this.amqpChannel.assertExchange(exchangeName, 'topic', { diff --git a/src/config/env.config.ts b/src/config/env.config.ts index a46fb2aa..78ca891c 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -97,6 +97,7 @@ export type Rabbitmq = { EXCHANGE_NAME: string; GLOBAL_ENABLED: boolean; EVENTS: EventsRabbitmq; + PREFIX_KEY: string; }; export type Sqs = { @@ -355,6 +356,7 @@ export class ConfigService { RABBITMQ: { ENABLED: process.env?.RABBITMQ_ENABLED === 'true', GLOBAL_ENABLED: process.env?.RABBITMQ_GLOBAL_ENABLED === 'true', + PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY || 'evolution', EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange', URI: process.env.RABBITMQ_URI || '', EVENTS: { From d3d9e33cf1b1a1cb76314fc922c53fb93896f261 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 02:05:30 -0300 Subject: [PATCH 03/20] version: 2.2.2 --- CHANGELOG.md | 6 +++++- Dockerfile | 2 +- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 728a93fc..397ce2e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ -# 2.2.2 (develop) +# 2.2.2 (2025-01-31 06:55) ### Features * Added prefix key to queue name in RabbitMQ +### Fixed + +* Update Baileys Version + # 2.2.1 (2025-01-22 14:37) ### Features diff --git a/Dockerfile b/Dockerfile index b360e768..3324594f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:20-alpine AS builder RUN apk update && \ apk add git wget curl bash openssl -LABEL version="2.2.1" description="Api to control whatsapp features through http requests." +LABEL version="2.2.2" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL contact="contato@atendai.com" diff --git a/package.json b/package.json index b9e92ab5..6c96d822 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "2.2.1", + "version": "2.2.2", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs", From 1898d4683b726e593df9373eedce20bef3cd90c0 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 02:18:41 -0300 Subject: [PATCH 04/20] fix: Disable group metadata caching in Baileys service - Remove group metadata caching mechanisms - Modify group-related cache update methods - Simplify group metadata retrieval process --- CHANGELOG.md | 7 +++++++ .../whatsapp/whatsapp.baileys.service.ts | 21 ++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397ce2e6..53119159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.2.3 (develop) + +### Fixed + +* Fix cache in local file system +* Update Baileys Version + # 2.2.2 (2025-01-31 06:55) ### Features diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index ddd93603..8cb11f6d 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -546,7 +546,7 @@ export class BaileysStartupService extends ChannelStartupService { return isGroupJid || isBroadcast || isNewsletter; }, syncFullHistory: this.localSettings.syncFullHistory, - cachedGroupMetadata: this.getGroupMetadataCache, + //cachedGroupMetadata: this.getGroupMetadataCache, userDevicesCache: this.userDevicesCache, transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, patchMessageBeforeSending(message) { @@ -1267,11 +1267,11 @@ export class BaileysStartupService extends ChannelStartupService { 'groups.update': (groupMetadataUpdate: Partial[]) => { this.sendDataWebhook(Events.GROUPS_UPDATE, groupMetadataUpdate); - groupMetadataUpdate.forEach((group) => { - if (isJidGroup(group.id)) { - this.updateGroupMetadataCache(group.id); - } - }); + // groupMetadataUpdate.forEach((group) => { + // if (isJidGroup(group.id)) { + // this.updateGroupMetadataCache(group.id); + // } + // }); }, 'group-participants.update': (participantsUpdate: { @@ -1281,7 +1281,7 @@ export class BaileysStartupService extends ChannelStartupService { }) => { this.sendDataWebhook(Events.GROUP_PARTICIPANTS_UPDATE, participantsUpdate); - this.updateGroupMetadataCache(participantsUpdate.id); + //this.updateGroupMetadataCache(participantsUpdate.id); }, }; @@ -1830,9 +1830,10 @@ export class BaileysStartupService extends ChannelStartupService { if (isJidGroup(sender)) { let group; try { - const cache = this.configService.get('CACHE'); - if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner'); - else group = await this.getGroupMetadataCache(sender); + // const cache = this.configService.get('CACHE'); + // if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner'); + // else group = await this.getGroupMetadataCache(sender); + group = await this.findGroup({ groupJid: sender }, 'inner'); } catch (error) { throw new NotFoundException('Group not found'); } From 15eccc9f85d30b60e1cd93fd7f746bedb20e4d7c Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 13:31:38 -0300 Subject: [PATCH 05/20] version: 2.2.3 --- Dockerfile | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3324594f..9c31e4e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:20-alpine AS builder RUN apk update && \ apk add git wget curl bash openssl -LABEL version="2.2.2" description="Api to control whatsapp features through http requests." +LABEL version="2.2.3" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL contact="contato@atendai.com" diff --git a/package.json b/package.json index 6c96d822..a354744f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "2.2.2", + "version": "2.2.3", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs", From b29cf1b8b3ef07a1906712f2b9a1d9313e3a724c Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 13:41:11 -0300 Subject: [PATCH 06/20] Refactor logical message deletion in BaileysStartupService --- .../channel/whatsapp/whatsapp.baileys.service.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 8cb11f6d..d2ce2897 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -2983,25 +2983,31 @@ export class BaileysStartupService extends ChannelStartupService { const messageId = response.message?.protocolMessage?.key?.id; if (messageId) { const isLogicalDeleted = configService.get('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE; - let message = await this.prismaRepository.message.findUnique({ - where: { id: messageId }, + let message = await this.prismaRepository.message.findFirst({ + where: { + key: { + path: ['id'], + equals: messageId, + }, + }, }); if (isLogicalDeleted) { if (!message) return response; const existingKey = typeof message?.key === 'object' && message.key !== null ? message.key : {}; message = await this.prismaRepository.message.update({ - where: { id: messageId }, + where: { id: message.id }, data: { key: { ...existingKey, deleted: true, }, + status: 'DELETED', }, }); } else { await this.prismaRepository.message.deleteMany({ where: { - id: messageId, + id: message.id, }, }); } @@ -3010,7 +3016,7 @@ export class BaileysStartupService extends ChannelStartupService { instanceId: message.instanceId, key: message.key, messageType: message.messageType, - status: message.status, + status: 'DELETED', source: message.source, messageTimestamp: message.messageTimestamp, pushName: message.pushName, From 0f3de52e558a68c325de374d16e190d1ff0af488 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 13:59:27 -0300 Subject: [PATCH 07/20] chore: Upgrade Baileys to version 6.7.12 - Update Baileys package to latest version - Bump package version to 2.2.3 --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4889f9a0..250442a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "evolution-api", - "version": "2.2.1", + "version": "2.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "evolution-api", - "version": "2.2.1", + "version": "2.2.3", "license": "Apache-2.0", "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", @@ -4724,8 +4724,8 @@ } }, "node_modules/baileys/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "version": "6.7.12", + "resolved": "git+ssh://git@github.com/EvolutionAPI/Baileys.git#ce92d5d32f1174f050d2bba8fd637dc6e45faafa", "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", From 8732225b7da24ddbb6442199778f560f2dbca08b Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 16:39:54 -0300 Subject: [PATCH 08/20] chore: Update Baileys package to latest commit hash --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 250442a0..9a1b8a56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4599,8 +4599,8 @@ "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" }, "node_modules/baileys": { - "version": "6.7.9", - "resolved": "git+ssh://git@github.com/EvolutionAPI/Baileys.git#d39c74f2f0ec463b5ad7bde7db7f1e2e40720b01", + "version": "6.7.12", + "resolved": "git+ssh://git@github.com/EvolutionAPI/Baileys.git#2c69f65d4b6c4e779d6e3d2c0c32689a5425df95", "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", "@hapi/boom": "^9.1.3", From 55a2206e5437122c07268a0067abf996215980c9 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 16:49:27 -0300 Subject: [PATCH 09/20] feat: Re-enable group metadata caching in Baileys service - Restore group metadata caching mechanisms - Uncomment cache-related methods for group updates and participants - Implement conditional group metadata retrieval based on cache configuration --- .../whatsapp/whatsapp.baileys.service.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index d2ce2897..9de4f4a0 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -546,7 +546,7 @@ export class BaileysStartupService extends ChannelStartupService { return isGroupJid || isBroadcast || isNewsletter; }, syncFullHistory: this.localSettings.syncFullHistory, - //cachedGroupMetadata: this.getGroupMetadataCache, + cachedGroupMetadata: this.getGroupMetadataCache, userDevicesCache: this.userDevicesCache, transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, patchMessageBeforeSending(message) { @@ -1267,11 +1267,11 @@ export class BaileysStartupService extends ChannelStartupService { 'groups.update': (groupMetadataUpdate: Partial[]) => { this.sendDataWebhook(Events.GROUPS_UPDATE, groupMetadataUpdate); - // groupMetadataUpdate.forEach((group) => { - // if (isJidGroup(group.id)) { - // this.updateGroupMetadataCache(group.id); - // } - // }); + groupMetadataUpdate.forEach((group) => { + if (isJidGroup(group.id)) { + this.updateGroupMetadataCache(group.id); + } + }); }, 'group-participants.update': (participantsUpdate: { @@ -1281,7 +1281,7 @@ export class BaileysStartupService extends ChannelStartupService { }) => { this.sendDataWebhook(Events.GROUP_PARTICIPANTS_UPDATE, participantsUpdate); - //this.updateGroupMetadataCache(participantsUpdate.id); + this.updateGroupMetadataCache(participantsUpdate.id); }, }; @@ -1830,10 +1830,10 @@ export class BaileysStartupService extends ChannelStartupService { if (isJidGroup(sender)) { let group; try { - // const cache = this.configService.get('CACHE'); - // if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner'); - // else group = await this.getGroupMetadataCache(sender); - group = await this.findGroup({ groupJid: sender }, 'inner'); + const cache = this.configService.get('CACHE'); + if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner'); + else group = await this.getGroupMetadataCache(sender); + // group = await this.findGroup({ groupJid: sender }, 'inner'); } catch (error) { throw new NotFoundException('Group not found'); } From f31fe50f70387b728cf696d9b119bec549fbb874 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 28 Feb 2025 16:52:17 -0300 Subject: [PATCH 10/20] fix: SQL query column quoting in ChannelStartupService --- src/api/services/channel.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 29bed428..66c59679 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -559,7 +559,7 @@ export class ChannelStartupService { "Message"."messageTimestamp" DESC ) SELECT * FROM rankedMessages - ORDER BY updatedAt DESC NULLS LAST; + ORDER BY "updatedAt" DESC NULLS LAST; `; if (results && isArray(results) && results.length > 0) { From 5c285bed9a82f98cb142a73855ba0f33cd1d5229 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Mon, 3 Mar 2025 22:45:57 -0300 Subject: [PATCH 11/20] Refactor BaileysStartupService updateMessage method --- .../whatsapp/whatsapp.baileys.service.ts | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 9de4f4a0..1a71dbc4 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3290,7 +3290,7 @@ export class BaileysStartupService extends ChannelStartupService { } public async updateMessage(data: UpdateMessageDto) { - const jid = createJid(data.number); + const jid = this.createJid(data.number); const options = await this.formatUpdateMessage(data); @@ -3300,13 +3300,72 @@ export class BaileysStartupService extends ChannelStartupService { } try { - return await this.client.sendMessage(jid, { + const response = await this.client.sendMessage(jid, { ...(options as any), edit: data.key, }); + if (response) { + const messageId = response.message?.protocolMessage?.key?.id; + if (messageId) { + let message = await this.prismaRepository.message.findFirst({ + where: { + key: { + path: ['id'], + equals: messageId, + }, + }, + }); + if (!message) throw new NotFoundException('Message not found'); + + if (!(message.key.valueOf() as any).fromMe) { + new BadRequestException('You cannot edit others messages'); + } + if ((message.key.valueOf() as any)?.deleted) { + new BadRequestException('You cannot edit deleted messages'); + } + + const updateMessage = this.prepareMessage({ ...response }); + message = await this.prismaRepository.message.update({ + where: { id: message.id }, + data: { + message: { + ...updateMessage?.message?.[updateMessage.messageType]?.editedMessage, + }, + status: 'EDITED', + }, + }); + const messageUpdate: any = { + messageId: message.id, + keyId: messageId, + remoteJid: response.key.remoteJid, + fromMe: response.key.fromMe, + participant: response.key?.remoteJid, + status: 'EDITED', + instanceId: this.instanceId, + }; + await this.prismaRepository.messageUpdate.create({ + data: messageUpdate, + }); + + this.sendDataWebhook(Events.MESSAGES_EDITED, { + id: message.id, + instanceId: message.instanceId, + key: message.key, + messageType: message.messageType, + status: 'EDITED', + source: message.source, + messageTimestamp: message.messageTimestamp, + pushName: message.pushName, + participant: message.participant, + message: message.message, + }); + } + } + + return response; } catch (error) { this.logger.error(error); - throw new BadRequestException(error.toString()); + throw error; } } From 23970e5c1b163fc2a8fe67950b411e74ac216214 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Mon, 3 Mar 2025 22:47:51 -0300 Subject: [PATCH 12/20] Refactor message deletion in BaileysStartupService --- .../channel/whatsapp/whatsapp.baileys.service.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 1a71dbc4..3a2b83e0 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3004,6 +3004,18 @@ export class BaileysStartupService extends ChannelStartupService { 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, + }); } else { await this.prismaRepository.message.deleteMany({ where: { From a64bc5cd3b9c2035fbb07697d1b7548d72305c6e Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Mon, 3 Mar 2025 22:51:49 -0300 Subject: [PATCH 13/20] Refactor createJid method in BaileysStartupService --- .../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 3a2b83e0..3df6ac30 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3302,7 +3302,7 @@ export class BaileysStartupService extends ChannelStartupService { } public async updateMessage(data: UpdateMessageDto) { - const jid = this.createJid(data.number); + const jid = createJid(data.number); const options = await this.formatUpdateMessage(data); From f2d782362ee2c114681df96d4030fdbf151c1d62 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Mon, 3 Mar 2025 22:53:26 -0300 Subject: [PATCH 14/20] version: 2.2.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53119159..2d316041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.2.3 (develop) +# 2.2.3 (2025-02-03 11:52) ### Fixed From b46286b0712911cfd976cb21289050293014dc6f Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Wed, 5 Mar 2025 22:19:08 -0300 Subject: [PATCH 15/20] fix: added cache to identify duplicated messages on events - Update Docker image repository to evoapicloud/evolution-api - Modify contact email to contato@evolution-api.com - Update Docker Compose, Dockerfile, and workflow files - Add Docker image badge to README - Include additional content creator in README - Implement message deduplication cache in Baileys service --- .github/workflows/publish_docker_image.yml | 49 +++++++++++++++++++ .../publish_docker_image_homolog.yml | 2 +- .../workflows/publish_docker_image_latest.yml | 2 +- Docker/swarm/evolution_api_v2.yaml | 2 +- Dockerfile | 2 +- LICENSE | 2 +- README.md | 1 + docker-compose.yaml | 2 +- package.json | 2 +- .../whatsapp/whatsapp.baileys.service.ts | 22 +++++++++ 10 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/publish_docker_image.yml diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml new file mode 100644 index 00000000..d30615db --- /dev/null +++ b/.github/workflows/publish_docker_image.yml @@ -0,0 +1,49 @@ +name: Build Docker image + +on: + push: + tags: + - "*.*.*" + +jobs: + build_deploy: + name: Build and Deploy + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: atendai/evolution-api + images: evoapicloud/evolution-api + tags: type=semver,pattern=v{{version}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file diff --git a/.github/workflows/publish_docker_image_homolog.yml b/.github/workflows/publish_docker_image_homolog.yml index 9bc638ee..c6397a9a 100644 --- a/.github/workflows/publish_docker_image_homolog.yml +++ b/.github/workflows/publish_docker_image_homolog.yml @@ -20,7 +20,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: atendai/evolution-api-lite + images: evoapicloud/evolution-api-lite tags: homolog - name: Set up QEMU diff --git a/.github/workflows/publish_docker_image_latest.yml b/.github/workflows/publish_docker_image_latest.yml index 0f262a16..79335ddc 100644 --- a/.github/workflows/publish_docker_image_latest.yml +++ b/.github/workflows/publish_docker_image_latest.yml @@ -20,7 +20,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: atendai/evolution-api-lite + images: evoapicloud/evolution-api-lite tags: latest - name: Set up QEMU diff --git a/Docker/swarm/evolution_api_v2.yaml b/Docker/swarm/evolution_api_v2.yaml index e06cf9e1..ba677514 100644 --- a/Docker/swarm/evolution_api_v2.yaml +++ b/Docker/swarm/evolution_api_v2.yaml @@ -2,7 +2,7 @@ version: "3.7" services: evolution_v2: - image: atendai/evolution-api-lite:latest + image: evoapicloud/evolution-api:latest volumes: - evolution_instances:/evolution/instances networks: diff --git a/Dockerfile b/Dockerfile index 9c31e4e7..ad1641cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apk update && \ LABEL version="2.2.3" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" -LABEL contact="contato@atendai.com" +LABEL contact="contato@evolution-api.com" WORKDIR /evolution diff --git a/LICENSE b/LICENSE index da01e779..ad430f14 100644 --- a/LICENSE +++ b/LICENSE @@ -8,7 +8,7 @@ a. LOGO and copyright information: In the process of using Evolution API's front b. Usage Notification Requirement: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer. -Please contact contato@atendai.com to inquire about licensing matters. +Please contact contato@evolution-api.com to inquire about licensing matters. 2. As a contributor, you should agree that: diff --git a/README.md b/README.md index e27851bb..efe25d62 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@
+[![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) diff --git a/docker-compose.yaml b/docker-compose.yaml index bd7325aa..33918c38 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: api: container_name: evolution_api - image: atendai/evolution-api-lite:latest + image: evoapicloud/evolution-api:latest restart: always depends_on: - redis diff --git a/package.json b/package.json index a354744f..c7c84c99 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ ], "author": { "name": "Davidson Gomes", - "email": "contato@atendai.com" + "email": "contato@evolution-api.com" }, "license": "Apache-2.0", "bugs": { diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 3df6ac30..c4e457a4 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -936,6 +936,17 @@ export class BaileysStartupService extends ChannelStartupService { await this.baileysCache.delete(received.key.id); } + // Cache to avoid duplicate messages + const messageKey = `${this.instance.id}_${received.key.id}`; + const cached = await this.baileysCache.get(messageKey); + + if (cached) { + this.logger.info(`Message duplicated ignored: ${received.key.id}`); + continue; + } + + await this.baileysCache.set(messageKey, true, 30 * 60); + if ( (type !== 'notify' && type !== 'append') || received.message?.protocolMessage || @@ -1145,6 +1156,17 @@ export class BaileysStartupService extends ChannelStartupService { continue; } + const updateKey = `${this.instance.id}_${key.id}_${update.status}`; + + const cached = await this.baileysCache.get(updateKey); + + if (cached) { + this.logger.info(`Message duplicated ignored: ${key.id}`); + continue; + } + + await this.baileysCache.set(updateKey, true, 30 * 60); + if (key.remoteJid !== 'status@broadcast') { let pollUpdates: any; From d58c6def276f371eaa70331b43a25a4d825c378e Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Wed, 5 Mar 2025 22:25:25 -0300 Subject: [PATCH 16/20] fix: improve message deduplication and edited message handling in Baileys service - Refactor edited message detection logic - Prevent duplicate message processing for edited messages - Optimize message key caching mechanism --- .gitignore | 1 + .../channel/whatsapp/whatsapp.baileys.service.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index a8226ede..3a20ac21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /dist /node_modules +.cursor* /Docker/.env .vscode diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index c4e457a4..c7851234 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -910,9 +910,11 @@ export class BaileysStartupService extends ChannelStartupService { } } + const editedMessage = + received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; + if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { - const editedMessage = - received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; + if (editedMessage) { await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage); } @@ -940,7 +942,7 @@ export class BaileysStartupService extends ChannelStartupService { const messageKey = `${this.instance.id}_${received.key.id}`; const cached = await this.baileysCache.get(messageKey); - if (cached) { + if (cached && !editedMessage) { this.logger.info(`Message duplicated ignored: ${received.key.id}`); continue; } @@ -1166,7 +1168,7 @@ export class BaileysStartupService extends ChannelStartupService { } await this.baileysCache.set(updateKey, true, 30 * 60); - + if (key.remoteJid !== 'status@broadcast') { let pollUpdates: any; From fa67b931a6acdcb0c8553ca150cbc90a6e1cc0da Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Thu, 6 Mar 2025 07:58:51 -0300 Subject: [PATCH 17/20] feat: Add support to get Catalogs and Collections with new routes: '{{baseUrl}}/chat/fetchCatalogs' and '{{baseUrl}}/chat/fetchCollections' --- src/api/controllers/chat.controller.ts | 10 ++ src/api/dto/chat.dto.ts | 11 ++ .../whatsapp/whatsapp.baileys.service.ts | 119 ++++++++++++++++++ src/api/routes/chat.router.ts | 27 +++- src/validate/chat.schema.ts | 18 +++ 5 files changed, 184 insertions(+), 1 deletion(-) diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 207d8ba5..e814b8a8 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -13,6 +13,8 @@ import { SendPresenceDto, UpdateMessageDto, WhatsAppNumberDto, + getCatalogDto, + getCollectionsDto, } from '@api/dto/chat.dto'; import { InstanceDto } from '@api/dto/instance.dto'; import { Query } from '@api/repository/repository.service'; @@ -109,4 +111,12 @@ export class ChatController { public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) { return await this.waMonitor.waInstances[instanceName].blockUser(data); } + + public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) { + return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data); + } + + public async fetchCatalogCollections({ instanceName }: InstanceDto, data: getCollectionsDto) { + return await this.waMonitor.waInstances[instanceName].fetchCatalogCollections(instanceName, data); + } } diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 00da7fdd..2514c4cc 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -126,3 +126,14 @@ export class BlockUserDto { number: string; status: 'block' | 'unblock'; } + +export class getCatalogDto { + number?: string; + limit?: number; + cursor?: string; +} + +export class getCollectionsDto { + number?: string; + limit?: number; +} \ No newline at end of file diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index c7851234..01cf365f 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -13,6 +13,8 @@ import { SendPresenceDto, UpdateMessageDto, WhatsAppNumberDto, + getCatalogDto, + getCollectionsDto, } from '@api/dto/chat.dto'; import { AcceptGroupInvite, @@ -117,6 +119,9 @@ import makeWASocket, { WAMessageUpdate, WAPresence, WASocket, + Product, + GetCatalogOptions, + CatalogCollection, } from 'baileys'; import { Label } from 'baileys/lib/Types/Label'; import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; @@ -4017,4 +4022,118 @@ export class BaileysStartupService extends ChannelStartupService { return response; } + + //Catalogs and collections + public async fetchCatalog(instanceName: string, data: getCatalogDto) { + const jid = data.number ? createJid(data.number) : this.client?.user?.id; + const limit = data.limit || 10; + const cursor = data.cursor || null; + + const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + + if (!onWhatsapp.exists) { + throw new BadRequestException(onWhatsapp); + } + + try { + const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + const business = await this.fetchBusinessProfile(info?.jid); + const catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); + + return { + wuid: info?.jid || jid, + name: info?.name, + numberExists: info?.exists, + isBusiness: business.isBusiness, + catalogLength: catalog?.products.length, + catalog: catalog?.products, + }; + } catch (error) { + console.log(error); + return { + wuid: jid, + name: null, + isBusiness: false, + }; + } + } + + public async getCatalog({ + jid, + limit, + cursor, + }: GetCatalogOptions): Promise<{ products: Product[]; nextPageCursor: string | undefined }> { + try { + jid = jid ? createJid(jid) : this.instance.wuid; + + const catalog = await this.client.getCatalog({ jid, limit: limit, cursor: cursor }); + + if (!catalog) { + return { + products: undefined, + nextPageCursor: undefined, + }; + } + + return catalog; + } catch (error) { + throw new InternalServerErrorException('Error getCatalog', error.toString()); + } + } + + public async fetchCatalogCollections(instanceName: string, data: getCollectionsDto) { + const jid = data.number ? createJid(data.number) : this.client?.user?.id; + const limit = data.limit || 10; + + const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + + if (!onWhatsapp.exists) { + throw new BadRequestException(onWhatsapp); + } + + try { + const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + const business = await this.fetchBusinessProfile(info?.jid); + const catalogCollections = await this.getCollections(info?.jid, limit); + + return { + wuid: info?.jid || jid, + name: info?.name, + numberExists: info?.exists, + isBusiness: business.isBusiness, + catalogLength: catalogCollections?.length, + catalogCollections: catalogCollections, + }; + } catch (error) { + console.log(error); + return { + wuid: jid, + name: null, + isBusiness: false, + }; + } + } + + public async getCollections(jid?: string | undefined, limit?: number): Promise { + try { + jid = jid ? createJid(jid) : this.instance.wuid; + + const result = await this.client.getCollections(jid, limit); + + if (!result) { + return [ + { + id: undefined, + name: undefined, + products: [], + status: undefined, + }, + ]; + } + + return result.collections; + } catch (error) { + throw new InternalServerErrorException('Error getCatalog', error.toString()); + } + } } diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index 20126c1a..866fb46e 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -36,6 +36,8 @@ import { readMessageSchema, updateMessageSchema, whatsappNumberSchema, + catalogSchema, + collectionsSchema, } from '@validate/validate.schema'; import { RequestHandler, Router } from 'express'; @@ -267,7 +269,30 @@ export class ChatRouter extends RouterBroker { }); return res.status(HttpStatus.CREATED).json(response); - }); + }) + + .post(this.routerPath('fetchCatalog'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: catalogSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchCatalog(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + + .post(this.routerPath('fetchCollections'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: collectionsSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchCatalogCollections(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + } public readonly router: Router = Router(); diff --git a/src/validate/chat.schema.ts b/src/validate/chat.schema.ts index dba27995..fd324c10 100644 --- a/src/validate/chat.schema.ts +++ b/src/validate/chat.schema.ts @@ -315,3 +315,21 @@ export const profileSchema: JSONSchema7 = { isBusiness: { type: 'boolean' }, }, }; + +export const catalogSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + cursor: { type: 'string' }, + }, +}; + +export const collectionsSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + cursor: { type: 'string' }, + }, +}; From db3d2a68da34afe6d63ee40cd868dd6564b38188 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Thu, 6 Mar 2025 08:06:37 -0300 Subject: [PATCH 18/20] chore: eslint applied --- src/api/controllers/chat.controller.ts | 4 ++-- .../channel/whatsapp/whatsapp.baileys.service.ts | 10 +++++----- src/api/routes/chat.router.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index e814b8a8..8c922f11 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -3,6 +3,8 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, + getCatalogDto, + getCollectionsDto, MarkChatUnreadDto, NumberDto, PrivacySettingDto, @@ -13,8 +15,6 @@ import { SendPresenceDto, UpdateMessageDto, WhatsAppNumberDto, - getCatalogDto, - getCollectionsDto, } from '@api/dto/chat.dto'; import { InstanceDto } from '@api/dto/instance.dto'; import { Query } from '@api/repository/repository.service'; diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 01cf365f..d7058d74 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -4,6 +4,8 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, + getCatalogDto, + getCollectionsDto, LastMessage, MarkChatUnreadDto, NumberBusiness, @@ -13,8 +15,6 @@ import { SendPresenceDto, UpdateMessageDto, WhatsAppNumberDto, - getCatalogDto, - getCollectionsDto, } from '@api/dto/chat.dto'; import { AcceptGroupInvite, @@ -89,6 +89,7 @@ import makeWASocket, { BufferedEventData, BufferJSON, CacheStore, + CatalogCollection, Chat, ConnectionState, Contact, @@ -98,6 +99,7 @@ import makeWASocket, { fetchLatestBaileysVersion, generateWAMessageFromContent, getAggregateVotesInPollMessage, + GetCatalogOptions, getContentType, getDevice, GroupMetadata, @@ -111,6 +113,7 @@ import makeWASocket, { MiscMessageGenerationOptions, ParticipantAction, prepareWAMessageMedia, + Product, proto, UserFacingSocketConfig, WABrowserDescription, @@ -119,9 +122,6 @@ import makeWASocket, { WAMessageUpdate, WAPresence, WASocket, - Product, - GetCatalogOptions, - CatalogCollection, } from 'baileys'; import { Label } from 'baileys/lib/Types/Label'; import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index 866fb46e..d4b27c83 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -22,6 +22,8 @@ import { Contact, Message, MessageUpdate } from '@prisma/client'; import { archiveChatSchema, blockUserSchema, + catalogSchema, + collectionsSchema, contactValidateSchema, deleteMessageSchema, markChatUnreadSchema, @@ -36,8 +38,6 @@ import { readMessageSchema, updateMessageSchema, whatsappNumberSchema, - catalogSchema, - collectionsSchema, } from '@validate/validate.schema'; import { RequestHandler, Router } from 'express'; From 056bafeeca81884783dcbdc463b6122be24c9a5e Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Thu, 6 Mar 2025 08:08:34 -0300 Subject: [PATCH 19/20] refactor: Make RabbitMQ prefix key optional in configuration --- src/config/env.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 78ca891c..8ba1fa83 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -97,7 +97,7 @@ export type Rabbitmq = { EXCHANGE_NAME: string; GLOBAL_ENABLED: boolean; EVENTS: EventsRabbitmq; - PREFIX_KEY: string; + PREFIX_KEY?: string; }; export type Sqs = { @@ -356,7 +356,7 @@ export class ConfigService { RABBITMQ: { ENABLED: process.env?.RABBITMQ_ENABLED === 'true', GLOBAL_ENABLED: process.env?.RABBITMQ_GLOBAL_ENABLED === 'true', - PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY || 'evolution', + PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY, EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange', URI: process.env.RABBITMQ_URI || '', EVENTS: { From 1d5e6cfb33d6cadb3886491814800ab44cb7c116 Mon Sep 17 00:00:00 2001 From: matheusmartinsInsper Date: Fri, 7 Mar 2025 10:58:50 -0300 Subject: [PATCH 20/20] add lint --- src/api/dto/chat.dto.ts | 4 ++-- .../channel/whatsapp/whatsapp.baileys.service.ts | 7 +++---- .../integrations/event/rabbitmq/rabbitmq.controller.ts | 10 +++++----- src/api/routes/chat.router.ts | 3 +-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 2514c4cc..1693165e 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -126,7 +126,7 @@ export class BlockUserDto { number: string; status: 'block' | 'unblock'; } - + export class getCatalogDto { number?: string; limit?: number; @@ -136,4 +136,4 @@ export class getCatalogDto { export class getCollectionsDto { number?: string; limit?: number; -} \ No newline at end of file +} diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index d7058d74..42695d8d 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -916,10 +916,9 @@ export class BaileysStartupService extends ChannelStartupService { } const editedMessage = - received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; + received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { - if (editedMessage) { await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage); } @@ -4023,8 +4022,8 @@ export class BaileysStartupService extends ChannelStartupService { return response; } - //Catalogs and collections - public async fetchCatalog(instanceName: string, data: getCatalogDto) { + //Catalogs and collections + public async fetchCatalog(instanceName: string, data: getCatalogDto) { const jid = data.number ? createJid(data.number) : this.client?.user?.id; const limit = data.limit || 10; const cursor = data.cursor || null; diff --git a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts index 71997405..22defde5 100644 --- a/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts +++ b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts @@ -161,8 +161,8 @@ export class RabbitmqController extends EventController implements EventControll }); const queueName = prefixKey - ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` - : event.replace(/_/g, '.').toLowerCase(); + ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` + : event.replace(/_/g, '.').toLowerCase(); await this.amqpChannel.assertQueue(queueName, { durable: true, @@ -212,9 +212,9 @@ export class RabbitmqController extends EventController implements EventControll if (events[event] === false) return; const queueName = - prefixKey !== '' - ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` - : `${event.replace(/_/g, '.').toLowerCase()}`; + prefixKey !== '' + ? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}` + : `${event.replace(/_/g, '.').toLowerCase()}`; const exchangeName = rabbitmqExchangeName; this.amqpChannel.assertExchange(exchangeName, 'topic', { diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index d4b27c83..aac9fe39 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -291,8 +291,7 @@ export class ChatRouter extends RouterBroker { }); return res.status(HttpStatus.OK).json(response); - }) - + }); } public readonly router: Router = Router();