diff --git a/.DS_Store b/.DS_Store index 0ae7ca6cd..90ea33c61 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 8419d7dc9..7c9901bc3 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/atendai/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/atendai/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 }} 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 03ce55e2b..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": { @@ -47,9 +47,9 @@ "@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.0", + "@whiskeysockets/baileys": "^6.7.2", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", 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/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 648e549f1..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'; @@ -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, @@ -574,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, diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 0178de2a1..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( @@ -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/dto/instance.dto.ts b/src/api/dto/instance.dto.ts index f3329e3e1..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'; @@ -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/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/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 cf122fb10..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'; @@ -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, }); @@ -561,53 +601,44 @@ 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, - }); - } 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, + let contact = await this.findContact(instance, chatId); + + if (contact) { + 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 }), }); } - 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, - ); } + } else { + 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 +648,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, @@ -789,26 +813,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 ); } 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/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/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, }); 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/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/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 a6fef45bc..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'; @@ -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, diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 7e2f8d881..ebc568dcb 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'; @@ -68,6 +68,7 @@ import { DeleteMessage, getBase64FromMediaMessageDto, LastMessage, + MarkChatUnreadDto, NumberBusiness, OnWhatsAppDto, PrivacySettingDto, @@ -495,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) { @@ -670,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) { @@ -2714,6 +2739,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/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/config/env.config.ts b/src/config/env.config.ts index 69b6771a8..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 = { @@ -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', @@ -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, diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index b4aa646cc..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 @@ -194,6 +194,8 @@ paths: type: string description: Check the connection state of your instance. example: "evolution" + security: + - apikeyAuth: [] responses: "200": description: Successful response @@ -2242,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." 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'; 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',