From 96821f5d9ab301d4be6319a5947c999049af48e2 Mon Sep 17 00:00:00 2001 From: Aditya Nandwana Date: Mon, 3 Feb 2025 11:43:53 +0530 Subject: [PATCH 1/9] 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 a5602ab6d..2bb01b4cb 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3888,7 +3888,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); @@ -3898,13 +3898,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 4137984b5dab1189507d177526958c7cb9b39ad3 Mon Sep 17 00:00:00 2001 From: Aditya Nandwana Date: Mon, 3 Feb 2025 11:44:41 +0530 Subject: [PATCH 2/9] 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 2bb01b4cb..c53e2d4ce 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3572,6 +3572,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 91e7a322099aa78ccbdbea15e0ea54e8dece48fd Mon Sep 17 00:00:00 2001 From: Aditya Nandwana Date: Mon, 3 Feb 2025 14:24:27 +0530 Subject: [PATCH 3/9] 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 c53e2d4ce..519b5c88a 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -3900,7 +3900,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 867e8493aaeb702a1c5876a1e242e32d84dfb23a Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 3 Feb 2025 15:37:11 -0300 Subject: [PATCH 4/9] 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 | 2 +- .../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 | 4 +++- docker-compose.yaml | 2 +- package.json | 2 +- .../whatsapp/whatsapp.baileys.service.ts | 22 +++++++++++++++++++ 10 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 68a08a313..09d09390e 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -20,7 +20,7 @@ jobs: 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 diff --git a/.github/workflows/publish_docker_image_homolog.yml b/.github/workflows/publish_docker_image_homolog.yml index 77032dc95..b97a5e250 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 + images: evoapicloud/evolution-api 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 641dc5e01..cffdab01e 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 + images: evoapicloud/evolution-api tags: latest - name: Set up QEMU diff --git a/Docker/swarm/evolution_api_v2.yaml b/Docker/swarm/evolution_api_v2.yaml index 41c2daa28..867251a53 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:v2.1.2 + image: evoapicloud/evolution-api:latest volumes: - evolution_instances:/evolution/instances networks: diff --git a/Dockerfile b/Dockerfile index ca61b39a8..38cb416ce 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 da01e779d..ad430f14a 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 93197499b..6e40ce741 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) @@ -87,6 +88,7 @@ https://github.com/sponsors/EvolutionAPI We are proud to collaborate with the following content creators who have contributed valuable insights and tutorials about Evolution API: - [Promovaweb](https://www.youtube.com/@promovaweb) +- [Sandeco](https://www.youtube.com/@canalsandeco) - [Comunidade ZDG](https://www.youtube.com/@ComunidadeZDG) - [Francis MNO](https://www.youtube.com/@FrancisMNO) - [Pablo Cabral](https://youtube.com/@pablocabral) @@ -111,7 +113,7 @@ Evolution API is licensed under the Apache License 2.0, with the following addit 2. **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. Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/docker-compose.yaml b/docker-compose.yaml index 9a60a9a90..33918c383 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: api: container_name: evolution_api - image: atendai/evolution-api:homolog + image: evoapicloud/evolution-api:latest restart: always depends_on: - redis diff --git a/package.json b/package.json index 7e948028a..974f2a922 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 10feb7ce1..5f006a2c5 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1161,6 +1161,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 || @@ -1422,6 +1433,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 (status[update.status] === 'READ' && key.fromMe) { if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { this.chatwootService.eventWhatsapp( From e51b6e9270bc76a77f9bd283cb9f5926eb9ff060 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 3 Feb 2025 17:19:07 -0300 Subject: [PATCH 5/9] 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 | 2 ++ .../channel/whatsapp/whatsapp.baileys.service.ts | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a8226ede1..1cecfa988 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /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 5f006a2c5..9f716545f 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1128,9 +1128,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) { if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) this.chatwootService.eventWhatsapp( @@ -1165,7 +1166,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; } From e27db0612fb9ed55253d5220b200afa062328ce3 Mon Sep 17 00:00:00 2001 From: Wayre Avelar Date: Tue, 4 Feb 2025 03:51:47 -0300 Subject: [PATCH 6/9] 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 | 24 ++++ src/validate/chat.schema.ts | 18 +++ 5 files changed, 182 insertions(+) diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 207d8ba59..e814b8a83 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 00da7fdd4..1693165ee 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 10feb7ce1..9bb04394b 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, @@ -121,6 +123,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'; @@ -4534,4 +4539,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 20126c1a5..715eba732 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,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/validate/chat.schema.ts b/src/validate/chat.schema.ts index dba279959..fd324c103 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 56a8e09ba868917321b9dcfe555aef979db9e6c9 Mon Sep 17 00:00:00 2001 From: Wayre Avelar Date: Tue, 4 Feb 2025 09:04:41 -0300 Subject: [PATCH 7/9] 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 e814b8a83..8c922f11f 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 9bb04394b..bd0628f92 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, @@ -93,6 +93,7 @@ import makeWASocket, { BufferedEventData, BufferJSON, CacheStore, + CatalogCollection, Chat, ConnectionState, Contact, @@ -102,6 +103,7 @@ import makeWASocket, { fetchLatestBaileysVersion, generateWAMessageFromContent, getAggregateVotesInPollMessage, + GetCatalogOptions, getContentType, getDevice, GroupMetadata, @@ -115,6 +117,7 @@ import makeWASocket, { MiscMessageGenerationOptions, ParticipantAction, prepareWAMessageMedia, + Product, proto, UserFacingSocketConfig, WABrowserDescription, @@ -123,9 +126,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 715eba732..aac9fe39c 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 9a72b90ab2b34b8df10bb0e103acf55ce04f0011 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 4 Feb 2025 17:57:21 -0300 Subject: [PATCH 8/9] 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 78ca891cd..8ba1fa833 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 0810fb72a4d2f1c452edd3550f388e6fe9330b2f Mon Sep 17 00:00:00 2001 From: Wayre Avelar Date: Thu, 6 Feb 2025 19:04:28 -0300 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20Criado=20um=20novo=20grupo=20de=20r?= =?UTF-8?q?otas=20(business)=20para=20tratar=20dos=20catalogos=20de=20prod?= =?UTF-8?q?utos=20e=20Cole=C3=A7=C3=B5es=20evitando=20altera=C3=A7=C3=B5es?= =?UTF-8?q?=20desnecess=C3=A1rias=20em=20arquivos=20do=20reposit=C3=B3rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/controllers/business.controller.ts | 15 ++++++ src/api/controllers/chat.controller.ts | 10 ---- src/api/dto/business.dto.ts | 14 ++++++ src/api/dto/chat.dto.ts | 11 ----- .../whatsapp/whatsapp.baileys.service.ts | 49 +++++++++++++------ src/api/routes/business.router.ts | 37 ++++++++++++++ src/api/routes/chat.router.ts | 25 ---------- src/api/routes/index.router.ts | 2 + src/api/server.module.ts | 2 + src/validate/business.schema.ts | 17 +++++++ src/validate/chat.schema.ts | 18 ------- src/validate/validate.schema.ts | 1 + 12 files changed, 122 insertions(+), 79 deletions(-) create mode 100644 src/api/controllers/business.controller.ts create mode 100644 src/api/dto/business.dto.ts create mode 100644 src/api/routes/business.router.ts create mode 100644 src/validate/business.schema.ts diff --git a/src/api/controllers/business.controller.ts b/src/api/controllers/business.controller.ts new file mode 100644 index 000000000..3c7f166cc --- /dev/null +++ b/src/api/controllers/business.controller.ts @@ -0,0 +1,15 @@ +import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto'; +import { InstanceDto } from '@api/dto/instance.dto'; +import { WAMonitoringService } from '@api/services/monitor.service'; + +export class BusinessController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) { + return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data); + } + + public async fetchCollections({ instanceName }: InstanceDto, data: getCollectionsDto) { + return await this.waMonitor.waInstances[instanceName].fetchCollections(instanceName, data); + } +} diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 8c922f11f..207d8ba59 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -3,8 +3,6 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, - getCatalogDto, - getCollectionsDto, MarkChatUnreadDto, NumberDto, PrivacySettingDto, @@ -111,12 +109,4 @@ 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/business.dto.ts b/src/api/dto/business.dto.ts new file mode 100644 index 000000000..d29b3cf97 --- /dev/null +++ b/src/api/dto/business.dto.ts @@ -0,0 +1,14 @@ +export class NumberDto { + number: string; +} + +export class getCatalogDto { + number?: string; + limit?: number; + cursor?: string; +} + +export class getCollectionsDto { + number?: string; + limit?: number; +} diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 1693165ee..00da7fdd4 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -126,14 +126,3 @@ 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 57197e176..7d33e054c 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1,11 +1,10 @@ +import { getCollectionsDto } from '@api/dto/business.dto'; import { OfferCallDto } from '@api/dto/call.dto'; import { ArchiveChatDto, BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, - getCatalogDto, - getCollectionsDto, LastMessage, MarkChatUnreadDto, NumberBusiness, @@ -4634,11 +4633,11 @@ export class BaileysStartupService extends ChannelStartupService { return response; } - //Catalogs and collections - public async fetchCatalog(instanceName: string, data: getCatalogDto) { + //Business Controller + public async fetchCatalog(instanceName: string, data: getCollectionsDto) { const jid = data.number ? createJid(data.number) : this.client?.user?.id; const limit = data.limit || 10; - const cursor = data.cursor || null; + const cursor = null; const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4649,15 +4648,36 @@ export class BaileysStartupService extends ChannelStartupService { 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 }); + + let catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); + console.log(catalog); + let nextPageCursor = catalog.nextPageCursor; + let nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; + let pagination = nextPageCursorJson?.pagination_cursor + ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) + : null; + let fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; + + let productsCatalog = catalog.products || []; + let countLoops = 0; + while (fetcherHasMore && countLoops < 4) { + catalog = await this.getCatalog({ jid: info?.jid, limit, cursor: nextPageCursor }); + nextPageCursor = catalog.nextPageCursor; + nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; + pagination = nextPageCursorJson?.pagination_cursor + ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) + : null; + fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; + productsCatalog = [...productsCatalog, ...catalog.products]; + countLoops++; + } return { wuid: info?.jid || jid, - name: info?.name, numberExists: info?.exists, isBusiness: business.isBusiness, - catalogLength: catalog?.products.length, - catalog: catalog?.products, + catalogLength: productsCatalog.length, + catalog: productsCatalog, }; } catch (error) { console.log(error); @@ -4692,9 +4712,9 @@ export class BaileysStartupService extends ChannelStartupService { } } - public async fetchCatalogCollections(instanceName: string, data: getCollectionsDto) { + public async fetchCollections(instanceName: string, data: getCollectionsDto) { const jid = data.number ? createJid(data.number) : this.client?.user?.id; - const limit = data.limit || 10; + const limit = data.limit <= 20 ? data.limit : 20; //(tem esse limite, não sei porque) const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4705,18 +4725,17 @@ export class BaileysStartupService extends ChannelStartupService { try { const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); const business = await this.fetchBusinessProfile(info?.jid); - const catalogCollections = await this.getCollections(info?.jid, limit); + const collections = 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, + collectionsLength: collections?.length, + collections: collections, }; } catch (error) { - console.log(error); return { wuid: jid, name: null, diff --git a/src/api/routes/business.router.ts b/src/api/routes/business.router.ts new file mode 100644 index 000000000..1e510a4ff --- /dev/null +++ b/src/api/routes/business.router.ts @@ -0,0 +1,37 @@ +import { RouterBroker } from '@api/abstract/abstract.router'; +import { NumberDto } from '@api/dto/chat.dto'; +import { businessController } from '@api/server.module'; +import { catalogSchema, collectionsSchema } from '@validate/validate.schema'; +import { RequestHandler, Router } from 'express'; + +import { HttpStatus } from './index.router'; + +export class BusinessRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('getCatalog'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: catalogSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCatalog(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + + .post(this.routerPath('getCollections'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: collectionsSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCollections(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router: Router = Router(); +} diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index aac9fe39c..5c556705e 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -22,8 +22,6 @@ import { Contact, Message, MessageUpdate } from '@prisma/client'; import { archiveChatSchema, blockUserSchema, - catalogSchema, - collectionsSchema, contactValidateSchema, deleteMessageSchema, markChatUnreadSchema, @@ -209,7 +207,6 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .post(this.routerPath('updateProfileName'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, @@ -269,28 +266,6 @@ 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/routes/index.router.ts b/src/api/routes/index.router.ts index a4a7c0717..9c0ecd13c 100644 --- a/src/api/routes/index.router.ts +++ b/src/api/routes/index.router.ts @@ -11,6 +11,7 @@ import fs from 'fs'; import mimeTypes from 'mime-types'; import path from 'path'; +import { BusinessRouter } from './business.router'; import { CallRouter } from './call.router'; import { ChatRouter } from './chat.router'; import { GroupRouter } from './group.router'; @@ -82,6 +83,7 @@ router .use('/message', new MessageRouter(...guards).router) .use('/call', new CallRouter(...guards).router) .use('/chat', new ChatRouter(...guards).router) + .use('/business', new BusinessRouter(...guards).router) .use('/group', new GroupRouter(...guards).router) .use('/template', new TemplateRouter(configService, ...guards).router) .use('/settings', new SettingsRouter(...guards).router) diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 49fc56952..9d8df8a67 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -3,6 +3,7 @@ import { Chatwoot, configService, ProviderSession } from '@config/env.config'; import { eventEmitter } from '@config/event.config'; import { Logger } from '@config/logger.config'; +import { BusinessController } from './controllers/business.controller'; import { CallController } from './controllers/call.controller'; import { ChatController } from './controllers/chat.controller'; import { GroupController } from './controllers/group.controller'; @@ -98,6 +99,7 @@ export const instanceController = new InstanceController( export const sendMessageController = new SendMessageController(waMonitor); export const callController = new CallController(waMonitor); export const chatController = new ChatController(waMonitor); +export const businessController = new BusinessController(waMonitor); export const groupController = new GroupController(waMonitor); export const labelController = new LabelController(waMonitor); diff --git a/src/validate/business.schema.ts b/src/validate/business.schema.ts new file mode 100644 index 000000000..91ad17b20 --- /dev/null +++ b/src/validate/business.schema.ts @@ -0,0 +1,17 @@ +import { JSONSchema7 } from 'json-schema'; + +export const catalogSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + }, +}; + +export const collectionsSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + }, +}; diff --git a/src/validate/chat.schema.ts b/src/validate/chat.schema.ts index fd324c103..dba279959 100644 --- a/src/validate/chat.schema.ts +++ b/src/validate/chat.schema.ts @@ -315,21 +315,3 @@ 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' }, - }, -}; diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index cf3d7828c..4577eae3f 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,4 +1,5 @@ // Integrations Schema +export * from './business.schema'; export * from './chat.schema'; export * from './group.schema'; export * from './instance.schema';