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/.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/.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/CHANGELOG.md b/CHANGELOG.md
index 28e95d1d..2d316041 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+# 2.2.3 (2025-02-03 11:52)
+
+### Fixed
+
+* Fix cache in local file system
+* Update Baileys Version
+
+# 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/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 b360e768..ad1641cd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,9 +3,9 @@ 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.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 @@
+[]
[](https://evolution-api.com/whatsapp)
[](https://evolution-api.com/discord)
[](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-lock.json b/package-lock.json
index ada8fbce..9a1b8a56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
{
"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",
"@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": {
@@ -4593,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",
@@ -4718,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",
@@ -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..c7c84c99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "evolution-api",
- "version": "2.2.1",
+ "version": "2.2.3",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/main.js",
"type": "commonjs",
@@ -41,7 +41,7 @@
],
"author": {
"name": "Davidson Gomes",
- "email": "contato@atendai.com"
+ "email": "contato@evolution-api.com"
},
"license": "Apache-2.0",
"bugs": {
@@ -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/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts
index 207d8ba5..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,
@@ -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..1693165e 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;
+}
diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
index ddd93603..42695d8d 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,
@@ -87,6 +89,7 @@ import makeWASocket, {
BufferedEventData,
BufferJSON,
CacheStore,
+ CatalogCollection,
Chat,
ConnectionState,
Contact,
@@ -96,6 +99,7 @@ import makeWASocket, {
fetchLatestBaileysVersion,
generateWAMessageFromContent,
getAggregateVotesInPollMessage,
+ GetCatalogOptions,
getContentType,
getDevice,
GroupMetadata,
@@ -109,6 +113,7 @@ import makeWASocket, {
MiscMessageGenerationOptions,
ParticipantAction,
prepareWAMessageMedia,
+ Product,
proto,
UserFacingSocketConfig,
WABrowserDescription,
@@ -910,9 +915,10 @@ 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);
}
@@ -936,6 +942,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 && !editedMessage) {
+ 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 +1162,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;
@@ -1833,6 +1861,7 @@ export class BaileysStartupService extends ChannelStartupService {
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');
}
@@ -2982,25 +3011,43 @@ 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',
},
});
+ 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: messageId,
+ id: message.id,
},
});
}
@@ -3009,7 +3056,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,
@@ -3293,13 +3340,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;
}
}
@@ -3915,4 +4021,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/integrations/event/rabbitmq/rabbitmq.controller.ts b/src/api/integrations/event/rabbitmq/rabbitmq.controller.ts
index d7623441..22defde5 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/api/routes/chat.router.ts b/src/api/routes/chat.router.ts
index 20126c1a..aac9fe39 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,
@@ -267,6 +269,28 @@ 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);
});
}
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) {
diff --git a/src/config/env.config.ts b/src/config/env.config.ts
index a46fb2aa..8ba1fa83 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,
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
URI: process.env.RABBITMQ_URI || '',
EVENTS: {
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);
}
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' },
+ },
+};