From 4b60ca175d10050d8e9e8d252a29635e121afef4 Mon Sep 17 00:00:00 2001 From: "@milesibastos" Date: Tue, 30 Apr 2024 17:43:45 -0300 Subject: [PATCH 01/18] chore: build docker image to platforms: linux/amd64,linux/arm64 --- .github/workflows/publish_docker_image.yml | 79 ++++++++++------------ 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 8419d7dc9..7db95e607 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -2,63 +2,52 @@ name: Build Docker image on: push: - tags: ['v*'] + branches: + - develop + - main + tags: + - v* + workflow_dispatch: jobs: - build-amd: + build: runs-on: ubuntu-latest + env: + GIT_REF: ${{ github.head_ref || github.ref_name }} # ref_name to get tags/branches + permissions: + contents: read + packages: write steps: - - name: Check out the repo - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 - - name: Extract existing image metadata - id: image-meta - uses: docker/metadata-action@v4 - with: - images: atendai/evolution-api + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + uses: docker/setup-buildx-action@v3 - - name: Build and push AMD image - uses: docker/build-push-action@v4 - with: - context: . - labels: ${{ steps.image-meta.outputs.labels }} - platforms: linux/amd64 - push: true + - name: set docker tag + run: | + echo "DOCKER_TAG=ghcr.io/stack-app-br/evolution-api:$GIT_REF" >> $GITHUB_ENV - build-arm: - runs-on: buildjet-4vcpu-ubuntu-2204-arm - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Extract existing image metadata - id: image-meta - uses: docker/metadata-action@v4 - with: - images: atendai/evolution-api - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: replace docker tag if main + if: github.ref_name == 'main' + run: | + echo "DOCKER_TAG=ghcr.io/stack-app-br/evolution-api:latest" >> $GITHUB_ENV - - name: Log in to Docker Hub - uses: docker/login-action@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push ARM image - uses: docker/build-push-action@v4 + - name: Build and push + uses: docker/build-push-action@v2 with: context: . - labels: ${{ steps.image-meta.outputs.labels }} - platforms: linux/arm64 + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 push: true + tags: ${{ env.DOCKER_TAG }} From e29b4865e814bc28d3dff4abc383dca141328d84 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Wed, 1 May 2024 19:20:40 -0300 Subject: [PATCH 02/18] update baileys --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03ce55e2b..34321368f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "aws-sdk": "^2.1499.0", "axios": "^1.6.5", - "baileys": "^6.7.0", + "baileys": "^6.7.1", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", From 8e9a1e2ba5d0b8235761e50fb28da56a22fadf3f Mon Sep 17 00:00:00 2001 From: Neander de Souza Date: Mon, 6 May 2024 09:08:36 -0300 Subject: [PATCH 03/18] feat: method to mark chat as unread --- src/api/controllers/chat.controller.ts | 6 +++ src/api/dto/chat.dto.ts | 5 +++ src/api/routes/chat.router.ts | 19 +++++++++ .../channels/whatsapp.baileys.service.ts | 40 +++++++++++++++++++ .../channels/whatsapp.business.service.ts | 3 ++ src/validate/validate.schema.ts | 27 +++++++++++++ 6 files changed, 100 insertions(+) diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 1c16260d9..9d22a8f0e 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -4,6 +4,7 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, + MarkChatUnreadDto, NumberDto, PrivacySettingDto, ProfileNameDto, @@ -40,6 +41,11 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].archiveChat(data); } + public async markChatUnread({ instanceName }: InstanceDto, data: MarkChatUnreadDto) { + logger.verbose('requested markChatUnread from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].markChatUnread(data); + } + public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) { logger.verbose('requested deleteMessage from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].deleteMessage(data); diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 0178de2a1..7952ddc63 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -73,6 +73,11 @@ export class ArchiveChatDto { archive: boolean; } +export class MarkChatUnreadDto { + lastMessage?: LastMessage; + chat?: string; +} + class PrivacySetting { readreceipts: WAReadReceiptsValue; profile: WAPrivacyValue; diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index e8c82a163..4debf3d13 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -6,6 +6,7 @@ import { blockUserSchema, contactValidateSchema, deleteMessageSchema, + markChatUnreadSchema, messageUpSchema, messageValidateSchema, presenceSchema, @@ -24,6 +25,7 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, + MarkChatUnreadDto, NumberDto, PrivacySettingDto, ProfileNameDto, @@ -98,6 +100,23 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) + .put(this.routerPath('markChatUnread'), ...guards, async (req, res) => { + logger.verbose('request received in markChatUnread'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: markChatUnreadSchema, + ClassRef: MarkChatUnreadDto, + execute: (instance, data) => chatController.markChatUnread(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) .delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => { logger.verbose('request received in deleteMessageForEveryone'); logger.verbose('request body: '); diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 7e2f8d881..ebf6d7407 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -68,6 +68,7 @@ import { DeleteMessage, getBase64FromMediaMessageDto, LastMessage, + MarkChatUnreadDto, NumberBusiness, OnWhatsAppDto, PrivacySettingDto, @@ -2714,6 +2715,45 @@ export class BaileysStartupService extends ChannelStartupService { } } + public async markChatUnread(data: MarkChatUnreadDto) { + this.logger.verbose('Marking chat as unread'); + + try { + let last_message = data.lastMessage; + let number = data.chat; + + if (!last_message && number) { + last_message = await this.getLastMessage(number); + } else { + last_message = data.lastMessage; + last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now(); + number = last_message?.key?.remoteJid; + } + + if (!last_message || Object.keys(last_message).length === 0) { + throw new NotFoundException('Last message not found'); + } + + await this.client.chatModify( + { + markRead: false, + lastMessages: [last_message], + }, + this.createJid(number), + ); + + return { + chatId: number, + markedChatUnread: true, + }; + } catch (error) { + throw new InternalServerErrorException({ + markedChatUnread: false, + message: ['An error occurred while marked unread the chat. Open a calling.', error.toString()], + }); + } + } + public async deleteMessage(del: DeleteMessage) { this.logger.verbose('Deleting message'); try { diff --git a/src/api/services/channels/whatsapp.business.service.ts b/src/api/services/channels/whatsapp.business.service.ts index d55f72e8c..09ddd2a04 100644 --- a/src/api/services/channels/whatsapp.business.service.ts +++ b/src/api/services/channels/whatsapp.business.service.ts @@ -1258,6 +1258,9 @@ export class BusinessStartupService extends ChannelStartupService { public async archiveChat() { throw new BadRequestException('Method not available on WhatsApp Business API'); } + public async markChatUnread() { + throw new BadRequestException('Method not available on WhatsApp Business API'); + } public async fetchProfile() { throw new BadRequestException('Method not available on WhatsApp Business API'); } diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 9013ca39f..8f7cb1a0e 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -597,6 +597,33 @@ export const archiveChatSchema: JSONSchema7 = { required: ['archive'], }; +export const markChatUnreadSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chat: { type: 'string' }, + lastMessage: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + messageTimestamp: { type: 'integer', minLength: 1 }, + }, + required: ['key'], + ...isNotEmpty('messageTimestamp'), + }, + }, + required: ['lastMessage'], +}; + export const deleteMessageSchema: JSONSchema7 = { $id: v4(), type: 'object', From d9d8707123e3d88810b52f33eecf14905b057638 Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Mon, 6 May 2024 16:29:37 -0300 Subject: [PATCH 04/18] chore: update baileys to version 6.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34321368f..6df3b8fd4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "aws-sdk": "^2.1499.0", "axios": "^1.6.5", - "baileys": "^6.7.1", + "baileys": "^6.7.2", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", From ea3b0b371256471a89e8a649dd380b9e72666a00 Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Mon, 6 May 2024 16:30:16 -0300 Subject: [PATCH 05/18] fix: optimize ChatwootService method for retrieving open conversations --- .../chatwoot/services/chatwoot.service.ts | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index cf122fb10..a80d64a26 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -789,26 +789,15 @@ export class ChatwootService { return null; } - const payload = [ - ['inbox_id', inbox.id.toString()], - ['contact_id', contact.id.toString()], - ['status', 'open'], - ]; + const conversations = (await client.contacts.listConversations({ + accountId: this.provider.account_id, + id: contact.id, + })) as any; return ( - ( - (await client.conversations.filter({ - accountId: this.provider.account_id, - payload: payload.map((item, i, payload) => { - return { - attribute_key: item[0], - filter_operator: 'equal_to', - values: [item[1]], - query_operator: i < payload.length - 1 ? 'AND' : null, - }; - }), - })) as { payload: conversation[] } - ).payload[0] || undefined + conversations.payload.find( + (conversation) => conversation.inbox_id === inbox.id && conversation.status === 'open', + ) || undefined ); } From aa58d7744eac8dd6c7f5465b8ea78e5956ba0c9f Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Mon, 6 May 2024 17:28:41 -0300 Subject: [PATCH 06/18] chore: update AWS SDK dependency to version 3.569.0 --- package.json | 2 +- src/api/integrations/sqs/libs/sqs.server.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 34321368f..7f9442e9f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@hapi/boom": "^10.0.1", "@sentry/node": "^7.59.2", "amqplib": "^0.10.3", - "aws-sdk": "^2.1499.0", + "@aws-sdk/client-sqs": "^3.569.0", "axios": "^1.6.5", "baileys": "^6.7.1", "class-validator": "^0.14.1", diff --git a/src/api/integrations/sqs/libs/sqs.server.ts b/src/api/integrations/sqs/libs/sqs.server.ts index e1c328cb3..3dc1d95dd 100644 --- a/src/api/integrations/sqs/libs/sqs.server.ts +++ b/src/api/integrations/sqs/libs/sqs.server.ts @@ -1,4 +1,4 @@ -import { SQS } from 'aws-sdk'; +import { SQS } from '@aws-sdk/client-sqs'; import { configService, Sqs } from '../../../../config/env.config'; import { Logger } from '../../../../config/logger.config'; @@ -12,8 +12,11 @@ export const initSQS = () => { return new Promise((resolve, reject) => { const awsConfig = configService.get('SQS'); sqs = new SQS({ - accessKeyId: awsConfig.ACCESS_KEY_ID, - secretAccessKey: awsConfig.SECRET_ACCESS_KEY, + credentials: { + accessKeyId: awsConfig.ACCESS_KEY_ID, + secretAccessKey: awsConfig.SECRET_ACCESS_KEY, + }, + region: awsConfig.REGION, }); From 39ee266598e61565daf7ad08a1c49f6f1b709217 Mon Sep 17 00:00:00 2001 From: dev2 Date: Mon, 6 May 2024 19:28:57 -0300 Subject: [PATCH 07/18] fix: correcao swagger sem authkey --- .DS_Store | Bin 8196 -> 8196 bytes src/dev-env.yml | 202 ------------------------------------------ src/docs/swagger.yaml | 2 + 3 files changed, 2 insertions(+), 202 deletions(-) delete mode 100644 src/dev-env.yml diff --git a/.DS_Store b/.DS_Store index 0ae7ca6cd2d3b1da20e291685617f13b31a9cf8a..90ea33c61501d99630fdaa08a4b416225e8556f4 100644 GIT binary patch delta 43 zcmZp1XmOa}UDU^hRb;$|KJZpO|1f@c^v3yV}RZjR?rX~7OR08ajHdjMxL=4ev}4q`MQ`$C3i!RGry YHH;fmYnV2(OMGLQd{($`V`?Q600F05h5!Hn diff --git a/src/dev-env.yml b/src/dev-env.yml deleted file mode 100644 index e7f0fae34..000000000 --- a/src/dev-env.yml +++ /dev/null @@ -1,202 +0,0 @@ -# ⚠️ -# ⚠️ ALL SETTINGS DEFINED IN THIS FILE ARE APPLIED TO ALL INSTANCES. -# ⚠️ - -# ⚠️ RENAME THIS FILE TO env.yml - -# Choose the server type for the application -SERVER: - TYPE: http # https - PORT: 8080 # 443 - URL: localhost - DISABLE_MANAGER: false - DISABLE_DOCS: false - -CORS: - ORIGIN: - - "*" - # - yourdomain.com - METHODS: - - POST - - GET - - PUT - - DELETE - CREDENTIALS: true - -# Install ssl certificate and replace string with domain name -# Access: https://certbot.eff.org/instructions?ws=other&os=ubuntufocal -SSL_CONF: - PRIVKEY: /etc/letsencrypt/live//privkey.pem - FULLCHAIN: /etc/letsencrypt/live//fullchain.pem - -# Determine the logs to be displayed -LOG: - LEVEL: - - ERROR - - WARN - - DEBUG - - INFO - - LOG - - VERBOSE - - DARK - - WEBHOOKS - COLOR: true - BAILEYS: error # fatal | error | warn | info | debug | trace - -# Determine how long the instance should be deleted from memory in case of no connection. -# Default time: 5 minutes -# If you don't even want an expiration, enter the value false -DEL_INSTANCE: false # or false -DEL_TEMP_INSTANCES: true # Delete instances with status closed on start - -# Temporary data storage -STORE: - MESSAGES: true - MESSAGE_UP: true - CONTACTS: true - CHATS: true - -CLEAN_STORE: - CLEANING_INTERVAL: 7200 # 7200 seconds === 2h - MESSAGES: true - MESSAGE_UP: true - CONTACTS: true - CHATS: true - -# Permanent data storage -DATABASE: - ENABLED: false - CONNECTION: - URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true" - DB_PREFIX_NAME: evolution - # Choose the data you want to save in the application's database or store - SAVE_DATA: - INSTANCE: false - NEW_MESSAGE: false - MESSAGE_UPDATE: false - CONTACTS: false - CHATS: false - -RABBITMQ: - ENABLED: false - MODE: "global" - EXCHANGE_NAME: "evolution_exchange" - URI: "amqp://guest:guest@localhost:5672" - -SQS: - ENABLED: true - ACCESS_KEY_ID: "" - SECRET_ACCESS_KEY: "" - ACCOUNT_ID: "" - REGION: "us-east-1" - -WEBSOCKET: - ENABLED: false - GLOBAL_EVENTS: false - -WA_BUSINESS: - TOKEN_WEBHOOK: evolution - URL: https://graph.facebook.com - VERSION: v18.0 - LANGUAGE: pt_BR - -# Global Webhook Settings -# Each instance's Webhook URL and events will be requested at the time it is created -WEBHOOK: - # Define a global webhook that will listen for enabled events from all instances - GLOBAL: - URL: - ENABLED: false - # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event - WEBHOOK_BY_EVENTS: false - # Automatically maps webhook paths - # Set the events you want to hear - EVENTS: - APPLICATION_STARTUP: false - QRCODE_UPDATED: true - MESSAGES_SET: true - MESSAGES_UPSERT: true - MESSAGES_UPDATE: true - MESSAGES_DELETE: true - SEND_MESSAGE: true - CONTACTS_SET: true - CONTACTS_UPSERT: true - CONTACTS_UPDATE: true - PRESENCE_UPDATE: true - CHATS_SET: true - CHATS_UPSERT: true - CHATS_UPDATE: true - CHATS_DELETE: true - GROUPS_UPSERT: true - GROUP_UPDATE: true - GROUP_PARTICIPANTS_UPDATE: true - CONNECTION_UPDATE: true - LABELS_EDIT: true - LABELS_ASSOCIATION: true - CALL: true - # This event fires every time a new token is requested via the refresh route - NEW_JWT_TOKEN: false - # This events is used with Typebot - TYPEBOT_START: false - TYPEBOT_CHANGE_STATUS: false - # This event is used with Chama AI - CHAMA_AI_ACTION: false - # This event is used to send errors to the webhook - ERRORS: false - ERRORS_WEBHOOK: - -CONFIG_SESSION_PHONE: - # Name that will be displayed on smartphone connection - CLIENT: "Evolution API" - NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari - -# Set qrcode display limit -QRCODE: - LIMIT: 30 - COLOR: "#198754" - -TYPEBOT: - API_VERSION: "old" # old | latest - KEEP_OPEN: false - -CHATWOOT: - # If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot. - MESSAGE_DELETE: true # false | true - # If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp. - MESSAGE_READ: false # false | true - IMPORT: - # This db connection is used to import messages from whatsapp to chatwoot database - DATABASE: - CONNECTION: - URI: "postgres://user:password@hostname:port/dbname" - PLACEHOLDER_MEDIA_MESSAGE: true - -# Cache to optimize application performance -CACHE: - REDIS: - ENABLED: false - URI: "redis://localhost:6379" - PREFIX_KEY: "evolution" - TTL: 604800 - SAVE_INSTANCES: false - LOCAL: - ENABLED: false - TTL: 86400 - -# Defines an authentication type for the api -# We recommend using the apikey because it will allow you to use a custom token, -# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token -AUTHENTICATION: - TYPE: apikey # jwt or apikey - # Define a global apikey to access all instances - API_KEY: - # OBS: This key must be inserted in the request header to create an instance. - KEY: B6D711FCDE4D4FD5936544120E713976 - # Expose the api key on return from fetch instances - EXPOSE_IN_FETCH_INSTANCES: true - # Set the secret key to encrypt and decrypt your token and its expiration time. - JWT: - EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires - SECRET: L=0YWt]b2w[WF>#>:&E` - -LANGUAGE: "pt-BR" # pt-BR, en diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index b4aa646cc..55968502d 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -194,6 +194,8 @@ paths: type: string description: Check the connection state of your instance. example: "evolution" + security: + - apikeyAuth: [] responses: "200": description: Successful response From 80c892aca3667e48987223a4f90476baf891be94 Mon Sep 17 00:00:00 2001 From: Al1stic Date: Mon, 6 May 2024 19:31:37 -0300 Subject: [PATCH 08/18] fix env --- src/dev-env.yml | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 src/dev-env.yml diff --git a/src/dev-env.yml b/src/dev-env.yml new file mode 100644 index 000000000..e7f0fae34 --- /dev/null +++ b/src/dev-env.yml @@ -0,0 +1,202 @@ +# ⚠️ +# ⚠️ ALL SETTINGS DEFINED IN THIS FILE ARE APPLIED TO ALL INSTANCES. +# ⚠️ + +# ⚠️ RENAME THIS FILE TO env.yml + +# Choose the server type for the application +SERVER: + TYPE: http # https + PORT: 8080 # 443 + URL: localhost + DISABLE_MANAGER: false + DISABLE_DOCS: false + +CORS: + ORIGIN: + - "*" + # - yourdomain.com + METHODS: + - POST + - GET + - PUT + - DELETE + CREDENTIALS: true + +# Install ssl certificate and replace string with domain name +# Access: https://certbot.eff.org/instructions?ws=other&os=ubuntufocal +SSL_CONF: + PRIVKEY: /etc/letsencrypt/live//privkey.pem + FULLCHAIN: /etc/letsencrypt/live//fullchain.pem + +# Determine the logs to be displayed +LOG: + LEVEL: + - ERROR + - WARN + - DEBUG + - INFO + - LOG + - VERBOSE + - DARK + - WEBHOOKS + COLOR: true + BAILEYS: error # fatal | error | warn | info | debug | trace + +# Determine how long the instance should be deleted from memory in case of no connection. +# Default time: 5 minutes +# If you don't even want an expiration, enter the value false +DEL_INSTANCE: false # or false +DEL_TEMP_INSTANCES: true # Delete instances with status closed on start + +# Temporary data storage +STORE: + MESSAGES: true + MESSAGE_UP: true + CONTACTS: true + CHATS: true + +CLEAN_STORE: + CLEANING_INTERVAL: 7200 # 7200 seconds === 2h + MESSAGES: true + MESSAGE_UP: true + CONTACTS: true + CHATS: true + +# Permanent data storage +DATABASE: + ENABLED: false + CONNECTION: + URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true" + DB_PREFIX_NAME: evolution + # Choose the data you want to save in the application's database or store + SAVE_DATA: + INSTANCE: false + NEW_MESSAGE: false + MESSAGE_UPDATE: false + CONTACTS: false + CHATS: false + +RABBITMQ: + ENABLED: false + MODE: "global" + EXCHANGE_NAME: "evolution_exchange" + URI: "amqp://guest:guest@localhost:5672" + +SQS: + ENABLED: true + ACCESS_KEY_ID: "" + SECRET_ACCESS_KEY: "" + ACCOUNT_ID: "" + REGION: "us-east-1" + +WEBSOCKET: + ENABLED: false + GLOBAL_EVENTS: false + +WA_BUSINESS: + TOKEN_WEBHOOK: evolution + URL: https://graph.facebook.com + VERSION: v18.0 + LANGUAGE: pt_BR + +# Global Webhook Settings +# Each instance's Webhook URL and events will be requested at the time it is created +WEBHOOK: + # Define a global webhook that will listen for enabled events from all instances + GLOBAL: + URL: + ENABLED: false + # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event + WEBHOOK_BY_EVENTS: false + # Automatically maps webhook paths + # Set the events you want to hear + EVENTS: + APPLICATION_STARTUP: false + QRCODE_UPDATED: true + MESSAGES_SET: true + MESSAGES_UPSERT: true + MESSAGES_UPDATE: true + MESSAGES_DELETE: true + SEND_MESSAGE: true + CONTACTS_SET: true + CONTACTS_UPSERT: true + CONTACTS_UPDATE: true + PRESENCE_UPDATE: true + CHATS_SET: true + CHATS_UPSERT: true + CHATS_UPDATE: true + CHATS_DELETE: true + GROUPS_UPSERT: true + GROUP_UPDATE: true + GROUP_PARTICIPANTS_UPDATE: true + CONNECTION_UPDATE: true + LABELS_EDIT: true + LABELS_ASSOCIATION: true + CALL: true + # This event fires every time a new token is requested via the refresh route + NEW_JWT_TOKEN: false + # This events is used with Typebot + TYPEBOT_START: false + TYPEBOT_CHANGE_STATUS: false + # This event is used with Chama AI + CHAMA_AI_ACTION: false + # This event is used to send errors to the webhook + ERRORS: false + ERRORS_WEBHOOK: + +CONFIG_SESSION_PHONE: + # Name that will be displayed on smartphone connection + CLIENT: "Evolution API" + NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari + +# Set qrcode display limit +QRCODE: + LIMIT: 30 + COLOR: "#198754" + +TYPEBOT: + API_VERSION: "old" # old | latest + KEEP_OPEN: false + +CHATWOOT: + # If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot. + MESSAGE_DELETE: true # false | true + # If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp. + MESSAGE_READ: false # false | true + IMPORT: + # This db connection is used to import messages from whatsapp to chatwoot database + DATABASE: + CONNECTION: + URI: "postgres://user:password@hostname:port/dbname" + PLACEHOLDER_MEDIA_MESSAGE: true + +# Cache to optimize application performance +CACHE: + REDIS: + ENABLED: false + URI: "redis://localhost:6379" + PREFIX_KEY: "evolution" + TTL: 604800 + SAVE_INSTANCES: false + LOCAL: + ENABLED: false + TTL: 86400 + +# Defines an authentication type for the api +# We recommend using the apikey because it will allow you to use a custom token, +# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token +AUTHENTICATION: + TYPE: apikey # jwt or apikey + # Define a global apikey to access all instances + API_KEY: + # OBS: This key must be inserted in the request header to create an instance. + KEY: B6D711FCDE4D4FD5936544120E713976 + # Expose the api key on return from fetch instances + EXPOSE_IN_FETCH_INSTANCES: true + # Set the secret key to encrypt and decrypt your token and its expiration time. + JWT: + EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires + SECRET: L=0YWt]b2w[WF>#>:&E` + +LANGUAGE: "pt-BR" # pt-BR, en From 09911c472db5cc1bf675fa851b8b02ced2db3eb7 Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Wed, 8 May 2024 14:34:03 -0300 Subject: [PATCH 09/18] refactor: optimize ChatwootService method for updating contact information --- .../chatwoot/services/chatwoot.service.ts | 82 +++++++------------ 1 file changed, 31 insertions(+), 51 deletions(-) diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index a80d64a26..0cafed949 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -561,53 +561,40 @@ export class ChatwootService { const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId); - const findContact = await this.findContact(instance, chatId); - - let contact: any; - if (body.key.fromMe) { - if (findContact) { - contact = await this.updateContact(instance, findContact.id, { - avatar_url: picture_url.profilePictureUrl || null, + let contact = await this.findContact(instance, chatId); + + if (contact) { + const waProfilePictureFile = picture_url.profilePictureUrl.split('#')[0].split('?')[0].split('/').pop(); + const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop(); + const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; + const nameNeedsUpdate = + !contact.name || + contact.name === chatId || + (`+${chatId}`.startsWith('+55') + ? this.getNumbers(`+${chatId}`).some( + (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), + ) + : false); + + const contactNeedsUpdate = pictureNeedsUpdate || nameNeedsUpdate; + if (contactNeedsUpdate) { + this.logger.verbose('update contact in chatwoot'); + contact = await this.updateContact(instance, contact.id, { + ...(nameNeedsUpdate && { name: nameContact }), + ...(pictureNeedsUpdate && { avatar_url: picture_url.profilePictureUrl || null }), }); - } else { - const jid = isGroup ? null : body.key.remoteJid; - contact = await this.createContact( - instance, - chatId, - filterInbox.id, - isGroup, - nameContact, - picture_url.profilePictureUrl || null, - jid, - ); } } else { - if (findContact) { - if (!findContact.name || findContact.name === chatId) { - contact = await this.updateContact(instance, findContact.id, { - name: nameContact, - avatar_url: picture_url.profilePictureUrl || null, - }); - } else { - contact = await this.updateContact(instance, findContact.id, { - avatar_url: picture_url.profilePictureUrl || null, - }); - } - if (!contact) { - contact = await this.findContact(instance, chatId); - } - } else { - const jid = isGroup ? null : body.key.remoteJid; - contact = await this.createContact( - instance, - chatId, - filterInbox.id, - isGroup, - nameContact, - picture_url.profilePictureUrl || null, - jid, - ); - } + const jid = isGroup ? null : body.key.remoteJid; + contact = await this.createContact( + instance, + chatId, + filterInbox.id, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + jid, + ); } if (!contact) { @@ -617,13 +604,6 @@ export class ChatwootService { const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; - if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) { - this.logger.verbose('update contact name in chatwoot'); - await this.updateContact(instance, contactId, { - name: nameContact, - }); - } - this.logger.verbose('get contact conversations in chatwoot'); const contactConversations = (await client.contacts.listConversations({ accountId: this.provider.account_id, From dcc32479ffe9aed8365ae5c249b4261bb81f911a Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Wed, 8 May 2024 15:03:37 -0300 Subject: [PATCH 10/18] feature(chatwoot): add merge_brazil_contacts function to solve nine digit in brazilian numbers --- src/api/controllers/instance.controller.ts | 2 + src/api/dto/instance.dto.ts | 1 + .../controllers/chatwoot.controller.ts | 1 + .../integrations/chatwoot/dto/chatwoot.dto.ts | 1 + .../chatwoot/models/chatwoot.model.ts | 2 + .../chatwoot/services/chatwoot.service.ts | 46 +++++++++++++++++-- .../chatwoot/validate/chatwoot.schema.ts | 1 + src/api/services/channel.service.ts | 2 + 8 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 648e549f1..3393e655f 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -66,6 +66,7 @@ export class InstanceController { chatwoot_conversation_pending, chatwoot_import_contacts, chatwoot_name_inbox, + chatwoot_merge_brazil_contacts, chatwoot_import_messages, chatwoot_days_limit_import_messages, reject_call, @@ -519,6 +520,7 @@ export class InstanceController { reopen_conversation: chatwoot_reopen_conversation || false, conversation_pending: chatwoot_conversation_pending || false, import_contacts: chatwoot_import_contacts ?? true, + merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false, import_messages: chatwoot_import_messages ?? true, days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60, auto_create: true, diff --git a/src/api/dto/instance.dto.ts b/src/api/dto/instance.dto.ts index f3329e3e1..1f2ff1c65 100644 --- a/src/api/dto/instance.dto.ts +++ b/src/api/dto/instance.dto.ts @@ -27,6 +27,7 @@ export class InstanceDto { chatwoot_sign_msg?: boolean; chatwoot_reopen_conversation?: boolean; chatwoot_conversation_pending?: boolean; + chatwoot_merge_brazil_contacts?: boolean; chatwoot_import_contacts?: boolean; chatwoot_import_messages?: boolean; chatwoot_days_limit_import_messages?: number; diff --git a/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts b/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts index a70e70ff1..c6799ea54 100644 --- a/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts +++ b/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts @@ -53,6 +53,7 @@ export class ChatwootController { data.conversation_pending = false; data.import_contacts = false; data.import_messages = false; + data.merge_brazil_contacts = false; data.days_limit_import_messages = 0; data.auto_create = false; data.name_inbox = ''; diff --git a/src/api/integrations/chatwoot/dto/chatwoot.dto.ts b/src/api/integrations/chatwoot/dto/chatwoot.dto.ts index 1ce4dbd1e..a6786db23 100644 --- a/src/api/integrations/chatwoot/dto/chatwoot.dto.ts +++ b/src/api/integrations/chatwoot/dto/chatwoot.dto.ts @@ -9,6 +9,7 @@ export class ChatwootDto { number?: string; reopen_conversation?: boolean; conversation_pending?: boolean; + merge_brazil_contacts?: boolean; import_contacts?: boolean; import_messages?: boolean; days_limit_import_messages?: number; diff --git a/src/api/integrations/chatwoot/models/chatwoot.model.ts b/src/api/integrations/chatwoot/models/chatwoot.model.ts index 659b847c4..423fbb10c 100644 --- a/src/api/integrations/chatwoot/models/chatwoot.model.ts +++ b/src/api/integrations/chatwoot/models/chatwoot.model.ts @@ -14,6 +14,7 @@ export class ChatwootRaw { number?: string; reopen_conversation?: boolean; conversation_pending?: boolean; + merge_brazil_contacts?: boolean; import_contacts?: boolean; import_messages?: boolean; days_limit_import_messages?: number; @@ -31,6 +32,7 @@ const chatwootSchema = new Schema({ number: { type: String, required: true }, reopen_conversation: { type: Boolean, required: true }, conversation_pending: { type: Boolean, required: true }, + merge_brazil_contacts: { type: Boolean, required: true }, import_contacts: { type: Boolean, required: true }, import_messages: { type: Boolean, required: true }, days_limit_import_messages: { type: Number, required: true }, diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index a80d64a26..0ddc951f4 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -86,13 +86,14 @@ export class ChatwootService { return client; } - public getClientCwConfig(): ChatwootAPIConfig & { name_inbox: string } { + public getClientCwConfig(): ChatwootAPIConfig & { name_inbox: string; merge_brazil_contacts: boolean } { return { basePath: this.provider.url, with_credentials: true, credentials: 'include', token: this.provider.token, name_inbox: this.provider.name_inbox, + merge_brazil_contacts: this.provider.merge_brazil_contacts, }; } @@ -418,10 +419,49 @@ export class ChatwootService { } } + private async mergeBrazilianContacts(contacts: any[]) { + try { + //sdk chatwoot não tem função merge + this.logger.verbose('merging contacts'); + const contact = await chatwootRequest(this.getClientCwConfig(), { + method: 'POST', + url: `/api/v1/accounts/${this.provider.account_id}/actions/contact_merge`, + body: { + base_contact_id: contacts.find((contact) => contact.phone_number.length === 14)?.id, + mergee_contact_id: contacts.find((contact) => contact.phone_number.length === 13)?.id, + }, + }); + + return contact; + } catch { + this.logger.error('Error merging contacts'); + return null; + } + } + private findContactInContactList(contacts: any[], query: string) { const phoneNumbers = this.getNumbers(query); const searchableFields = this.getSearchableFields(); + // eslint-disable-next-line prettier/prettier + if(contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')){ + + const contact = this.mergeBrazilianContacts(contacts); + if (contact) { + return contact; + } + } + + const phone = phoneNumbers.reduce( + (savedNumber, number) => (number.length > savedNumber.length ? number : savedNumber), + '', + ); + + const contact_with9 = contacts.find((contact) => contact.phone_number === phone); + if (contact_with9) { + return contact_with9; + } + for (const contact of contacts) { for (const field of searchableFields) { if (contact[field] && phoneNumbers.includes(contact[field])) { @@ -449,7 +489,7 @@ export class ChatwootService { } private getSearchableFields() { - return ['phone_number', 'identifier']; + return ['phone_number']; } private getFilterPayload(query: string) { @@ -463,7 +503,7 @@ export class ChatwootService { const queryOperator = fieldsToSearch.length - 1 === index1 && numbers.length - 1 === index2 ? null : 'OR'; filterPayload.push({ attribute_key: field, - filter_operator: ['phone_number', 'identifier'].includes(field) ? 'equal_to' : 'contains', + filter_operator: 'equal_to', values: [number.replace('+', '')], query_operator: queryOperator, }); diff --git a/src/api/integrations/chatwoot/validate/chatwoot.schema.ts b/src/api/integrations/chatwoot/validate/chatwoot.schema.ts index 33652ec33..05995a2c2 100644 --- a/src/api/integrations/chatwoot/validate/chatwoot.schema.ts +++ b/src/api/integrations/chatwoot/validate/chatwoot.schema.ts @@ -35,6 +35,7 @@ export const chatwootSchema: JSONSchema7 = { conversation_pending: { type: 'boolean', enum: [true, false] }, auto_create: { type: 'boolean', enum: [true, false] }, import_contacts: { type: 'boolean', enum: [true, false] }, + merge_brazil_contacts: { type: 'boolean', enum: [true, false] }, import_messages: { type: 'boolean', enum: [true, false] }, days_limit_import_messages: { type: 'number' }, }, diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index a6fef45bc..7f1ae12c6 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -356,6 +356,7 @@ export class ChannelStartupService { this.logger.verbose(`Chatwoot sign delimiter: ${data.sign_delimiter}`); this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`); this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`); + this.logger.verbose(`Chatwoot merge brazilian contacts: ${data.import_contacts}`); this.logger.verbose(`Chatwoot import contacts: ${data.import_contacts}`); this.logger.verbose(`Chatwoot import messages: ${data.import_messages}`); this.logger.verbose(`Chatwoot days limit import messages: ${data.days_limit_import_messages}`); @@ -370,6 +371,7 @@ export class ChannelStartupService { sign_delimiter: data.sign_delimiter || null, reopen_conversation: data.reopen_conversation, conversation_pending: data.conversation_pending, + merge_brazil_contacts: data.merge_brazil_contacts, import_contacts: data.import_contacts, import_messages: data.import_messages, days_limit_import_messages: data.days_limit_import_messages, From 7c207a50e1eac3c690426e7f6ad48b189706886b Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Wed, 8 May 2024 15:11:37 -0300 Subject: [PATCH 11/18] feat: add merge_brazil_contacts to swagger doc --- src/docs/swagger.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 55968502d..b742b9212 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -2244,6 +2244,9 @@ paths: conversation_pending: type: boolean description: "Indicates whether to mark conversations as pending." + merge_brazil_contacts: + type: boolean + description: "Indicates whether to merge Brazil numbers in case of numbers with and without ninth digit." import_contacts: type: boolean description: "Indicates whether to import contacts from phone to Chatwoot when connecting." From 8875ab1e931222ec5c8c5e1fbcfbebe87302ddcd Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Wed, 8 May 2024 15:26:56 -0300 Subject: [PATCH 12/18] feat: add merge_brazil_contacts flag to instance controller --- src/api/controllers/instance.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 3393e655f..4a1cf110a 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -576,6 +576,7 @@ export class InstanceController { sign_msg: chatwoot_sign_msg || false, reopen_conversation: chatwoot_reopen_conversation || false, conversation_pending: chatwoot_conversation_pending || false, + merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false, import_contacts: chatwoot_import_contacts ?? true, import_messages: chatwoot_import_messages ?? true, days_limit_import_messages: chatwoot_days_limit_import_messages || 60, From 8446be7646492a697e5ce6c5191b05d54bcb29ed Mon Sep 17 00:00:00 2001 From: Deivison Lincoln Date: Wed, 8 May 2024 16:47:21 -0300 Subject: [PATCH 13/18] fix(chatwoot): fix grupos --- .../chatwoot/services/chatwoot.service.ts | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 0cafed949..5b283fdbe 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -564,25 +564,29 @@ export class ChatwootService { let contact = await this.findContact(instance, chatId); if (contact) { - const waProfilePictureFile = picture_url.profilePictureUrl.split('#')[0].split('?')[0].split('/').pop(); - const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop(); - const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; - const nameNeedsUpdate = - !contact.name || - contact.name === chatId || - (`+${chatId}`.startsWith('+55') - ? this.getNumbers(`+${chatId}`).some( - (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), - ) - : false); - - const contactNeedsUpdate = pictureNeedsUpdate || nameNeedsUpdate; - if (contactNeedsUpdate) { - this.logger.verbose('update contact in chatwoot'); - contact = await this.updateContact(instance, contact.id, { - ...(nameNeedsUpdate && { name: nameContact }), - ...(pictureNeedsUpdate && { avatar_url: picture_url.profilePictureUrl || null }), - }); + if (!body.key.fromMe) { + const waProfilePictureFile = + picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || ''; + const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || ''; + const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; + const nameNeedsUpdate = + !contact.name || + contact.name === chatId || + (`+${chatId}`.startsWith('+55') + ? this.getNumbers(`+${chatId}`).some( + (v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1), + ) + : false); + + const contactNeedsUpdate = pictureNeedsUpdate || nameNeedsUpdate; + if (contactNeedsUpdate) { + this.logger.verbose('update contact in chatwoot'); + contact = await this.updateContact(instance, contact.id, { + ...(nameNeedsUpdate && { name: nameContact }), + ...(waProfilePictureFile === '' && { avatar: null }), + ...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }), + }); + } } } else { const jid = isGroup ? null : body.key.remoteJid; From 6144fbe8569887a2ae851cd3a90ddbfa24dcb436 Mon Sep 17 00:00:00 2001 From: Ckk3 Date: Thu, 9 May 2024 13:21:28 -0300 Subject: [PATCH 14/18] changing CLEAN_STORE_CLEANING_TERMINAL to CLEAN_STORE_CLEANING_INTERVAL --- 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 69b6771a8..de952863e 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -251,8 +251,8 @@ export class ConfigService { LABELS: process.env?.STORE_LABELS === 'true', }, CLEAN_STORE: { - CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL) - ? Number.parseInt(process.env.CLEAN_STORE_CLEANING_TERMINAL) + CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_INTERVAL) + ? Number.parseInt(process.env.CLEAN_STORE_CLEANING_INTERVAL) : 7200, MESSAGES: process.env?.CLEAN_STORE_MESSAGES === 'true', MESSAGE_UP: process.env?.CLEAN_STORE_MESSAGE_UP === 'true', From 283b497788efd5559cfe6185d6078a3f2f6ef601 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 20 May 2024 19:39:56 -0300 Subject: [PATCH 15/18] downgrade baileys version --- .github/workflows/publish_docker_image.yml | 4 ++-- package.json | 2 +- src/api/controllers/instance.controller.ts | 2 +- src/api/dto/chat.dto.ts | 2 +- src/api/dto/instance.dto.ts | 2 +- src/api/dto/sendMessage.dto.ts | 2 +- .../integrations/chatwoot/services/chatwoot.service.ts | 2 +- .../chatwoot/utils/chatwoot-import-helper.ts | 2 +- src/api/models/integration.model.ts | 4 ++-- src/api/services/cache.service.ts | 2 +- src/api/services/channel.service.ts | 2 +- src/api/services/channels/whatsapp.baileys.service.ts | 8 ++++---- src/api/types/wa.types.ts | 2 +- src/cache/rediscache.ts | 2 +- src/utils/use-multi-file-auth-state-db.ts | 9 ++++++++- src/utils/use-multi-file-auth-state-redis-db.ts | 8 +++++++- 16 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 7db95e607..7c9901bc3 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -29,12 +29,12 @@ jobs: - name: set docker tag run: | - echo "DOCKER_TAG=ghcr.io/stack-app-br/evolution-api:$GIT_REF" >> $GITHUB_ENV + echo "DOCKER_TAG=ghcr.io/atendai/evolution-api:$GIT_REF" >> $GITHUB_ENV - name: replace docker tag if main if: github.ref_name == 'main' run: | - echo "DOCKER_TAG=ghcr.io/stack-app-br/evolution-api:latest" >> $GITHUB_ENV + echo "DOCKER_TAG=ghcr.io/atendai/evolution-api:latest" >> $GITHUB_ENV - name: Login to GitHub Container Registry uses: docker/login-action@v3 diff --git a/package.json b/package.json index ebaf62511..9ce59c4b3 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "@aws-sdk/client-sqs": "^3.569.0", "axios": "^1.6.5", - "baileys": "^6.7.2", + "@whiskeysockets/baileys": "6.6.0", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 4a1cf110a..0ffa885d6 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -1,4 +1,4 @@ -import { delay } from 'baileys'; +import { delay } from '@whiskeysockets/baileys'; import { isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; import { v4 } from 'uuid'; diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 7952ddc63..3ed55d522 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -1,4 +1,4 @@ -import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from 'baileys'; +import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; export class OnWhatsAppDto { constructor( diff --git a/src/api/dto/instance.dto.ts b/src/api/dto/instance.dto.ts index 1f2ff1c65..d9a4d87aa 100644 --- a/src/api/dto/instance.dto.ts +++ b/src/api/dto/instance.dto.ts @@ -1,4 +1,4 @@ -import { WAPresence } from 'baileys'; +import { WAPresence } from '@whiskeysockets/baileys'; import { ProxyDto } from './proxy.dto'; diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index 5c197d446..7bb33074b 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -1,4 +1,4 @@ -import { proto, WAPresence } from 'baileys'; +import { proto, WAPresence } from '@whiskeysockets/baileys'; export class Quoted { key: proto.IMessageKey; diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 28982d226..4190267e4 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -8,8 +8,8 @@ import ChatwootClient, { inbox, } from '@figuro/chatwoot-sdk'; import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request'; +import { proto } from '@whiskeysockets/baileys'; import axios from 'axios'; -import { proto } from 'baileys'; import FormData from 'form-data'; import { createReadStream, unlinkSync, writeFileSync } from 'fs'; import Jimp from 'jimp'; diff --git a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts index e102aa57f..dd0bb23a4 100644 --- a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts +++ b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts @@ -1,5 +1,5 @@ import { inbox } from '@figuro/chatwoot-sdk'; -import { proto } from 'baileys'; +import { proto } from '@whiskeysockets/baileys'; import { InstanceDto } from '../../../../api/dto/instance.dto'; import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../../api/models'; diff --git a/src/api/models/integration.model.ts b/src/api/models/integration.model.ts index 9aa6e8c68..3bacd7ee1 100644 --- a/src/api/models/integration.model.ts +++ b/src/api/models/integration.model.ts @@ -9,12 +9,12 @@ export class IntegrationRaw { token?: string; } -const sqsSchema = new Schema({ +const integrationSchema = new Schema({ _id: { type: String, _id: true }, integration: { type: String, required: true }, number: { type: String, required: true }, token: { type: String, required: true }, }); -export const IntegrationModel = dbserver?.model(IntegrationRaw.name, sqsSchema, 'integration'); +export const IntegrationModel = dbserver?.model(IntegrationRaw.name, integrationSchema, 'integration'); export type IntegrationModel = typeof IntegrationModel; diff --git a/src/api/services/cache.service.ts b/src/api/services/cache.service.ts index e03b3eb56..caf3dbfae 100644 --- a/src/api/services/cache.service.ts +++ b/src/api/services/cache.service.ts @@ -1,4 +1,4 @@ -import { BufferJSON } from 'baileys'; +import { BufferJSON } from '@whiskeysockets/baileys'; import { Logger } from '../../config/logger.config'; import { ICache } from '../abstract/abstract.cache'; diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 7f1ae12c6..0a35154c8 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -1,5 +1,5 @@ +import { WASocket } from '@whiskeysockets/baileys'; import axios from 'axios'; -import { WASocket } from 'baileys'; import { execSync } from 'child_process'; import { isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index ebf6d7407..f6a4a4b84 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -1,6 +1,5 @@ import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { Boom } from '@hapi/boom'; -import axios from 'axios'; import makeWASocket, { AnyMessageContent, BufferedEventData, @@ -36,9 +35,10 @@ import makeWASocket, { WAMessageUpdate, WAPresence, WASocket, -} from 'baileys'; -import { Label } from 'baileys/lib/Types/Label'; -import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; +} from '@whiskeysockets/baileys'; +import { Label } from '@whiskeysockets/baileys/lib/Types/Label'; +import { LabelAssociation } from '@whiskeysockets/baileys/lib/Types/LabelAssociation'; +import axios from 'axios'; import { exec } from 'child_process'; import { isBase64, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; diff --git a/src/api/types/wa.types.ts b/src/api/types/wa.types.ts index 0549f05f0..066691e4a 100644 --- a/src/api/types/wa.types.ts +++ b/src/api/types/wa.types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { AuthenticationState, WAConnectionState } from 'baileys'; +import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; export enum Events { APPLICATION_STARTUP = 'application.startup', diff --git a/src/cache/rediscache.ts b/src/cache/rediscache.ts index c4e98968d..6e209ef11 100644 --- a/src/cache/rediscache.ts +++ b/src/cache/rediscache.ts @@ -1,4 +1,4 @@ -import { BufferJSON } from 'baileys'; +import { BufferJSON } from '@whiskeysockets/baileys'; import { RedisClientType } from 'redis'; import { ICache } from '../api/abstract/abstract.cache'; diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index f20db84f3..995ac92ad 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -1,4 +1,11 @@ -import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; +import { + AuthenticationCreds, + AuthenticationState, + BufferJSON, + initAuthCreds, + proto, + SignalDataTypeMap, +} from '@whiskeysockets/baileys'; import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index d077b894e..66bb89ea3 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -1,4 +1,10 @@ -import { AuthenticationCreds, AuthenticationState, initAuthCreds, proto, SignalDataTypeMap } from 'baileys'; +import { + AuthenticationCreds, + AuthenticationState, + initAuthCreds, + proto, + SignalDataTypeMap, +} from '@whiskeysockets/baileys'; import { CacheService } from '../api/services/cache.service'; import { Logger } from '../config/logger.config'; From 65bba23519614006187fcfa9361b7c9112967c86 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 20 May 2024 19:53:23 -0300 Subject: [PATCH 16/18] update baileys version --- package.json | 2 +- src/api/services/channels/whatsapp.baileys.service.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9ce59c4b3..8512f0efb 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "amqplib": "^0.10.3", "@aws-sdk/client-sqs": "^3.569.0", "axios": "^1.6.5", - "@whiskeysockets/baileys": "6.6.0", + "@whiskeysockets/baileys": "^6.7.2", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index f6a4a4b84..1369aa0a3 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -540,7 +540,7 @@ export class BaileysStartupService extends ChannelStartupService { printQRInTerminal: false, mobile, browser: number ? ['Chrome (Linux)', session.NAME, release()] : browser, - version, + version: [2, 2413, 1], markOnlineOnConnect: this.localSettings.always_online, retryRequestDelayMs: 10, connectTimeoutMs: 60_000, @@ -671,7 +671,7 @@ export class BaileysStartupService extends ChannelStartupService { try { this.instance.authState = await this.defineAuthState(); - const { version } = await fetchLatestBaileysVersion(); + // const { version } = await fetchLatestBaileysVersion(); const session = this.configService.get('CONFIG_SESSION_PHONE'); const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; @@ -711,7 +711,7 @@ export class BaileysStartupService extends ChannelStartupService { logger: P({ level: this.logBaileys }), printQRInTerminal: false, browser: this.phoneNumber ? ['Chrome (Linux)', session.NAME, release()] : browser, - version, + version: [2, 2413, 1], markOnlineOnConnect: this.localSettings.always_online, retryRequestDelayMs: 10, connectTimeoutMs: 60_000, From 8fce53b4af6a00f8a7a9b24e271f2afd4c064aa1 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 21 May 2024 08:38:08 -0300 Subject: [PATCH 17/18] fixed version whatsapp web for baileys --- .../channels/whatsapp.baileys.service.ts | 36 +++++++++++++++---- src/config/env.config.ts | 3 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 1369aa0a3..ebc568dcb 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -496,13 +496,24 @@ export class BaileysStartupService extends ChannelStartupService { this.mobile = mobile; } - const { version } = await fetchLatestBaileysVersion(); - - this.logger.verbose('Baileys version: ' + version); const session = this.configService.get('CONFIG_SESSION_PHONE'); const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; this.logger.verbose('Browser: ' + JSON.stringify(browser)); + let version; + let log; + + if (session.VERSION) { + version = session.VERSION.split(','); + log = `Baileys version env: ${version}`; + } else { + const baileysVersion = await fetchLatestBaileysVersion(); + version = baileysVersion.version; + log = `Baileys version: ${version}`; + } + + this.logger.info(log); + let options; if (this.localProxy.enabled) { @@ -540,7 +551,7 @@ export class BaileysStartupService extends ChannelStartupService { printQRInTerminal: false, mobile, browser: number ? ['Chrome (Linux)', session.NAME, release()] : browser, - version: [2, 2413, 1], + version, markOnlineOnConnect: this.localSettings.always_online, retryRequestDelayMs: 10, connectTimeoutMs: 60_000, @@ -671,10 +682,23 @@ export class BaileysStartupService extends ChannelStartupService { try { this.instance.authState = await this.defineAuthState(); - // const { version } = await fetchLatestBaileysVersion(); const session = this.configService.get('CONFIG_SESSION_PHONE'); const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; + let version; + let log; + + if (session.VERSION) { + version = session.VERSION.split(','); + log = `Baileys version env: ${version}`; + } else { + const baileysVersion = await fetchLatestBaileysVersion(); + version = baileysVersion.version; + log = `Baileys version: ${version}`; + } + + this.logger.info(log); + let options; if (this.localProxy.enabled) { @@ -711,7 +735,7 @@ export class BaileysStartupService extends ChannelStartupService { logger: P({ level: this.logBaileys }), printQRInTerminal: false, browser: this.phoneNumber ? ['Chrome (Linux)', session.NAME, release()] : browser, - version: [2, 2413, 1], + version, markOnlineOnConnect: this.localSettings.always_online, retryRequestDelayMs: 10, connectTimeoutMs: 60_000, diff --git a/src/config/env.config.ts b/src/config/env.config.ts index de952863e..4f37b0901 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -155,7 +155,7 @@ export type CacheConfLocal = { }; export type SslConf = { PRIVKEY: string; FULLCHAIN: string }; export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook }; -export type ConfigSessionPhone = { CLIENT: string; NAME: string }; +export type ConfigSessionPhone = { CLIENT: string; NAME: string; VERSION: string }; export type QrCode = { LIMIT: number; COLOR: string }; export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean }; export type Chatwoot = { @@ -360,6 +360,7 @@ export class ConfigService { CONFIG_SESSION_PHONE: { CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API', NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome', + VERSION: process.env?.CONFIG_SESSION_PHONE_VERSION || null, }, QRCODE: { LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30, From 67afbd6a77570378f770b64356cf998d034bda77 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Tue, 21 May 2024 08:53:35 -0300 Subject: [PATCH 18/18] v1.7.5 --- CHANGELOG.md | 11 +++++++++++ Dockerfile | 2 +- package.json | 2 +- src/docs/swagger.yaml | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc595045b..074fad8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.7.5 (2024-05-21 08:50) + +### Fixed +* Add merge_brazil_contacts function to solve nine digit in brazilian numbers +* Optimize ChatwootService method for updating contact +* Fix swagger auth +* Update aws sdk v3 +* Fix getOpenConversationByContact and init queries error +* Method to mark chat as unread +* Added environment variable to manually select the WhatsApp web version for the baileys lib (optional) + # 1.7.4 (2024-04-28 09:46) ### Fixed diff --git a/Dockerfile b/Dockerfile index 51955b71c..2dac29c02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:20.7.0-alpine AS builder -LABEL version="1.7.4" description="Api to control whatsapp features through http requests." +LABEL version="1.7.5" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL contact="contato@agenciadgcode.com" diff --git a/package.json b/package.json index 8512f0efb..af6198c7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "1.7.4", + "version": "1.7.5", "description": "Rest api for communication with WhatsApp", "main": "./dist/src/main.js", "scripts": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index b742b9212..0bcab52d2 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -25,7 +25,7 @@ info: [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442) - version: 1.7.4 + version: 1.7.5 contact: name: DavidsonGomes email: contato@agenciadgcode.com