From b921a4d324bfc22efd07500d960dbab224e225c7 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Fri, 16 Aug 2024 11:31:46 -0300 Subject: [PATCH] feat: IA send images and Sentry implemented --- .env.example | 4 +- .eslintrc.js | 66 ++-- CHANGELOG.md | 12 + Docker/swarm/evolution_api_v2.yaml | 3 +- package.json | 9 +- src/api/controllers/instance.controller.ts | 2 +- src/api/guards/auth.guard.ts | 2 +- src/api/guards/instance.guard.ts | 11 +- .../chatwoot/services/chatwoot.service.ts | 2 +- .../chatwoot/utils/chatwoot-import-helper.ts | 7 +- .../channels/whatsapp.baileys.service.ts | 321 ++++++++++-------- src/api/services/monitor.service.ts | 32 +- src/config/env.config.ts | 4 +- src/libs/prisma.connect.ts | 19 +- src/main.ts | 16 +- 15 files changed, 283 insertions(+), 227 deletions(-) diff --git a/.env.example b/.env.example index 2ad41b90..90649eeb 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ SERVER_PORT=8080 # Server URL - Set your application url SERVER_URL=http://localhost:8080 +SENTRY_DSN= + # Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com' CORS_ORIGIN=* CORS_METHODS=GET,POST,PUT,DELETE @@ -19,8 +21,6 @@ LOG_BAILEYS=error # If you don't even want an expiration, enter the value false DEL_INSTANCE=false -# Permanent data storage -DATABASE_ENABLED=true # Provider: postgresql | mysql DATABASE_PROVIDER=postgresql DATABASE_CONNECTION_URI='postgresql://user:pass@localhost:5432/evolution?schema=public' diff --git a/.eslintrc.js b/.eslintrc.js index f805da92..74a4c2ea 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,50 +1,42 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - sourceType: 'CommonJS', + sourceType: 'CommonJS', }, - plugins: [ - '@typescript-eslint', - 'simple-import-sort', - 'import' - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended' - ], + plugins: ['@typescript-eslint', 'simple-import-sort', 'import'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', }, root: true, env: { - node: true, - jest: true, + node: true, + jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unused-vars': 'error', - 'import/first': 'error', - 'import/no-duplicates': 'error', - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', - '@typescript-eslint/ban-types': [ - 'error', - { - extendDefaults: true, - types: { - '{}': false, - Object: false, - }, + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'import/first': 'error', + 'import/no-duplicates': 'error', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + '@typescript-eslint/ban-types': [ + 'error', + { + extendDefaults: true, + types: { + '{}': false, + Object: false, }, - ], - 'prettier/prettier': ['error', { endOfLine: 'auto' }], + }, + ], + 'prettier/prettier': ['error', { endOfLine: 'auto' }], }, -}; \ No newline at end of file +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index bbfb2230..611f4244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.1.0 (develop) + +### Features + +* OpenAI send images when markdown +* Dify send images when markdown +* Sentry implemented + +### Fixed + +* Fix on get profilePicture + # 2.0.9 (2024-08-15 12:31) ### Features diff --git a/Docker/swarm/evolution_api_v2.yaml b/Docker/swarm/evolution_api_v2.yaml index 8d63ef55..e46e40d9 100644 --- a/Docker/swarm/evolution_api_v2.yaml +++ b/Docker/swarm/evolution_api_v2.yaml @@ -2,7 +2,7 @@ version: "3.7" services: evolution_v2: - image: atendai/evolution-api:v2.0.9-rc + image: atendai/evolution-api:v2.0.9 volumes: - evolution_instances:/evolution/instances networks: @@ -10,7 +10,6 @@ services: environment: - SERVER_URL=https://evo2.site.com - DEL_INSTANCE=false - - DATABASE_ENABLED=true - DATABASE_PROVIDER=postgresql - DATABASE_CONNECTION_URI=postgresql://postgres:SENHA@postgres:5432/evolution - DATABASE_SAVE_DATA_INSTANCE=true diff --git a/package.json b/package.json index 273caa01..c8e42e0b 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "evolution-api", - "version": "2.0.9", + "version": "2.1.0", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs", "scripts": { - "build": "tsup", + "build": "tsc --noEmit && tsup", "start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts", "start:prod": "node dist/main", "dev:server": "clear && tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts", @@ -52,10 +52,11 @@ "@figuro/chatwoot-sdk": "^1.1.16", "@hapi/boom": "^10.0.1", "@prisma/client": "^5.15.0", - "@sentry/node": "^7.59.2", + "@sentry/node": "^7.119.0", + "@sentry/profiling-node": "^8.26.0", "amqplib": "^0.10.3", "axios": "^1.6.5", - "baileys": "6.7.6", + "baileys": "6.7.5", "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 e5e9a53b..45a5c998 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -570,7 +570,7 @@ export class InstanceController { if (state == 'close') { await instance.connectToWhatsapp(number); - await delay(5000); + await delay(2000); return instance.qrCode; } diff --git a/src/api/guards/auth.guard.ts b/src/api/guards/auth.guard.ts index 89b0874e..9ad20b61 100644 --- a/src/api/guards/auth.guard.ts +++ b/src/api/guards/auth.guard.ts @@ -34,7 +34,7 @@ async function apikey(req: Request, _: Response, next: NextFunction) { return next(); } } else { - if (req.originalUrl.includes('/instance/fetchInstances') && db.ENABLED) { + if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) { const instanceByKey = await prismaRepository.instance.findFirst({ where: { token: key }, }); diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index 9f8eb090..29c320ec 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -1,13 +1,12 @@ import { InstanceDto } from '@api/dto/instance.dto'; import { cache, waMonitor } from '@api/server.module'; -import { CacheConf, configService, Database } from '@config/env.config'; +import { CacheConf, configService } from '@config/env.config'; import { BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException } from '@exceptions'; import { prismaServer } from '@libs/prisma.connect'; import { NextFunction, Request, Response } from 'express'; async function getInstance(instanceName: string) { try { - const db = configService.get('DATABASE'); const cacheConf = configService.get('CACHE'); const exists = !!waMonitor.waInstances[instanceName]; @@ -18,13 +17,9 @@ async function getInstance(instanceName: string) { return exists || keyExists; } - if (db.ENABLED) { - const prisma = prismaServer; + const prisma = prismaServer; - return exists || (await prisma.instance.findMany({ where: { name: instanceName } })).length > 0; - } - - return false; + return exists || (await prisma.instance.findMany({ where: { name: instanceName } })).length > 0; } catch (error) { throw new InternalServerErrorException(error?.toString()); } diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 3df8795a..0904ac61 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -354,7 +354,7 @@ export class ChatwootService { return contact; } catch (error) { - this.logger.error(error); + return null; } } diff --git a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts index e5f0dbc9..765e9cf3 100644 --- a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts +++ b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts @@ -289,7 +289,12 @@ class ChatwootImport { this.deleteHistoryMessages(instance); this.deleteRepositoryMessagesCache(instance); - this.importHistoryContacts(instance, provider); + const providerData: ChatwootDto = { + ...provider, + ignoreJids: Array.isArray(provider.ignoreJids) ? provider.ignoreJids.map((event) => String(event)) : [], + }; + + this.importHistoryContacts(instance, providerData); return totalMessagesImported; } catch (error) { diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 9d5a4334..06c21481 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -68,7 +68,6 @@ import { S3, Typebot, } from '@config/env.config'; -import { INSTANCE_DIR } from '@config/path.config'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions'; import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { Boom } from '@hapi/boom'; @@ -98,6 +97,7 @@ import makeWASocket, { GroupParticipant, isJidBroadcast, isJidGroup, + // isJidNewsletter, isJidUser, makeCacheableSignalKeyStore, MessageUpsertType, @@ -118,10 +118,8 @@ import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; import { isBase64, isURL } from 'class-validator'; import { randomBytes } from 'crypto'; import EventEmitter2 from 'eventemitter2'; -// import { exec } from 'child_process'; import ffmpeg from 'fluent-ffmpeg'; -// import ffmpeg from 'fluent-ffmpeg'; -import { existsSync, readFileSync } from 'fs'; +import { readFileSync } from 'fs'; import Long from 'long'; import mime from 'mime'; import NodeCache from 'node-cache'; @@ -148,7 +146,7 @@ export class BaileysStartupService extends ChannelStartupService { ) { super(configService, eventEmitter, prismaRepository, chatwootCache); this.instance.qrcode = { count: 0 }; - this.recoveringMessages(); + // this.recoveringMessages(); this.authStateProvider = new AuthStateProvider(this.providerFiles); } @@ -163,57 +161,57 @@ export class BaileysStartupService extends ChannelStartupService { public phoneNumber: string; - private async recoveringMessages() { - const cacheConf = this.configService.get('CACHE'); + // private async recoveringMessages() { + // const cacheConf = this.configService.get('CACHE'); - if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { - this.logger.info('Recovering messages lost from cache'); - setInterval(async () => { - this.baileysCache.keys().then((keys) => { - keys.forEach(async (key) => { - const data = await this.baileysCache.get(key.split(':')[2]); + // if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + // this.logger.info('Recovering messages lost from cache'); + // setInterval(async () => { + // this.baileysCache.keys().then((keys) => { + // keys.forEach(async (key) => { + // const data = await this.baileysCache.get(key.split(':')[2]); - let message: any; - let retry: number; + // let message: any; + // let retry: number; - if (!data?.message) { - message = data; - retry = 0; - } else { - message = data.message; - retry = data.retry; - } + // if (!data?.message) { + // message = data; + // retry = 0; + // } else { + // message = data.message; + // retry = data.retry; + // } - if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { - retry = retry + 1; - this.logger.info(`Message absent from node, retrying to send, key: ${key.split(':')[2]} retry: ${retry}`); - if (message.messageStubParameters[1]) { - await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); - } + // if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { + // retry = retry + 1; + // this.logger.info(`Message absent from node, retrying to send, key: ${key.split(':')[2]} retry: ${retry}`); + // if (message.messageStubParameters[1]) { + // await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); + // } - this.baileysCache.set(key.split(':')[2], { message, retry }); + // this.baileysCache.set(key.split(':')[2], { message, retry }); - if (retry >= 100) { - this.logger.warn(`Message absent from node, retry limit reached, key: ${key.split(':')[2]}`); - this.baileysCache.delete(key.split(':')[2]); - return; - } - } - }); - }); - // 15 minutes - }, 15 * 60 * 1000); - } - } + // if (retry >= 100) { + // this.logger.warn(`Message absent from node, retry limit reached, key: ${key.split(':')[2]}`); + // this.baileysCache.delete(key.split(':')[2]); + // return; + // } + // } + // }); + // }); + // // 15 minutes + // }, 15 * 60 * 1000); + // } + // } - private async forceUpdateGroupMetadataCache() { - this.logger.verbose('Force update group metadata cache'); - const groups = await this.fetchAllGroups({ getParticipants: 'false' }); + // private async forceUpdateGroupMetadataCache() { + // this.logger.verbose('Force update group metadata cache'); + // const groups = await this.fetchAllGroups({ getParticipants: 'false' }); - for (const group of groups) { - await this.updateGroupMetadataCache(group.id); - } - } + // for (const group of groups) { + // await this.updateGroupMetadataCache(group.id); + // } + // } public get connectionStatus() { return this.stateConnection; @@ -239,21 +237,12 @@ export class BaileysStartupService extends ChannelStartupService { public async getProfileName() { let profileName = this.client.user?.name ?? this.client.user?.verifiedName; if (!profileName) { - if (this.configService.get('DATABASE').ENABLED) { - const data = await this.prismaRepository.session.findUnique({ - where: { sessionId: this.instanceId }, - }); + const data = await this.prismaRepository.session.findUnique({ + where: { sessionId: this.instanceId }, + }); - if (data) { - const creds = JSON.parse(JSON.stringify(data.creds), BufferJSON.reviver); - profileName = creds.me?.name || creds.me?.verifiedName; - } - } else if (existsSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'))) { - const creds = JSON.parse( - readFileSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'), { - encoding: 'utf-8', - }), - ); + if (data) { + const creds = JSON.parse(JSON.stringify(data.creds), BufferJSON.reviver); profileName = creds.me?.name || creds.me?.verifiedName; } } @@ -404,17 +393,15 @@ export class BaileysStartupService extends ChannelStartupService { disconnectionObject: JSON.stringify(lastDisconnect), }); - if (this.configService.get('DATABASE').ENABLED) { - await this.prismaRepository.instance.update({ - where: { id: this.instanceId }, - data: { - connectionStatus: 'close', - disconnectionAt: new Date(), - disconnectionReasonCode: statusCode, - disconnectionObject: JSON.stringify(lastDisconnect), - }, - }); - } + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { + connectionStatus: 'close', + disconnectionAt: new Date(), + disconnectionReasonCode: statusCode, + disconnectionObject: JSON.stringify(lastDisconnect), + }, + }); if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled) { this.chatwootService.eventWhatsapp( @@ -462,17 +449,15 @@ export class BaileysStartupService extends ChannelStartupService { `, ); - if (this.configService.get('DATABASE').ENABLED) { - await this.prismaRepository.instance.update({ - where: { id: this.instanceId }, - data: { - ownerJid: this.instance.wuid, - profileName: (await this.getProfileName()) as string, - profilePicUrl: this.instance.profilePictureUrl, - connectionStatus: 'open', - }, - }); - } + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { + ownerJid: this.instance.wuid, + profileName: (await this.getProfileName()) as string, + profilePicUrl: this.instance.profilePictureUrl, + connectionStatus: 'open', + }, + }); if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled) { this.chatwootService.eventWhatsapp( @@ -520,6 +505,7 @@ export class BaileysStartupService extends ChannelStartupService { return webMessageInfo[0].message; } catch (error) { + this.logger.error('line 508'); return { conversation: '' }; } } @@ -539,7 +525,7 @@ export class BaileysStartupService extends ChannelStartupService { return await useMultiFileAuthStateRedisDb(this.instance.id, this.cache); } - if (db.SAVE_DATA.INSTANCE && db.ENABLED) { + if (db.SAVE_DATA.INSTANCE) { return await useMultiFileAuthStatePrisma(this.instance.id, this.cache); } } @@ -619,56 +605,40 @@ export class BaileysStartupService extends ChannelStartupService { const socketConfig: UserFacingSocketConfig = { ...options, + version, + logger: P({ level: this.logBaileys }), + printQRInTerminal: false, auth: { creds: this.instance.authState.state.creds, keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, + msgRetryCounterCache: this.msgRetryCounterCache, + generateHighQualityLinkPreview: true, + getMessage: async (key) => (await this.getMessage(key)) as Promise, ...browserOptions, - version, markOnlineOnConnect: this.localSettings.alwaysOnline, retryRequestDelayMs: 350, maxMsgRetryCount: 4, fireInitQueries: true, - connectTimeoutMs: 20_000, + connectTimeoutMs: 30_000, keepAliveIntervalMs: 30_000, qrTimeout: 45_000, emitOwnEvents: false, shouldIgnoreJid: (jid) => { const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); - const isNewsletter = jid ? jid.includes('newsletter') : false; + // const isNewsletter = !isJidNewsletter(jid); + const isNewsletter = jid && jid.includes('newsletter'); return isGroupJid || isBroadcast || isNewsletter; }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, syncFullHistory: this.localSettings.syncFullHistory, shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { return this.historySyncNotification(msg); }, + // cachedGroupMetadata: this.getGroupMetadataCache, userDevicesCache: this.userDevicesCache, transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, - patchMessageBeforeSending(message) { - if ( - message.deviceSentMessage?.message?.listMessage?.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST - ) { - message = JSON.parse(JSON.stringify(message)); - - message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) { - message = JSON.parse(JSON.stringify(message)); - - message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - return message; - }, - forceGroupsPrekeys: false, }; this.endSession = false; @@ -694,6 +664,7 @@ export class BaileysStartupService extends ChannelStartupService { return await this.createClient(number); } catch (error) { + this.logger.error('line 667'); this.logger.error(error); throw new InternalServerErrorException(error?.toString()); } @@ -703,6 +674,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.createClient(this.phoneNumber); } catch (error) { + this.logger.error('line 677'); this.logger.error(error); throw new InternalServerErrorException(error?.toString()); } @@ -820,7 +792,7 @@ export class BaileysStartupService extends ChannelStartupService { if (updatedContacts.length > 0) { await Promise.all( - updatedContacts.map(async function (contact) { + updatedContacts.map(async (contact) => { const update = this.prismaRepository.contact.updateMany({ where: { remoteJid: contact.remoteJid, instanceId: this.instanceId }, data: { @@ -830,7 +802,7 @@ export class BaileysStartupService extends ChannelStartupService { const instance = { instanceName: this.instance.name, instanceId: this.instance.id }; - const findParticipant = await this.findContact(instance, contact.remoteJid.split('@')[0]); + const findParticipant = await this.chatwootService.findContact(instance, contact.remoteJid.split('@')[0]); this.chatwootService.updateContact(instance, findParticipant.id, { name: contact.pushName, @@ -842,6 +814,7 @@ export class BaileysStartupService extends ChannelStartupService { ); } } catch (error) { + this.logger.error('line 817'); this.logger.error(`Error: ${error.message}`); } }, @@ -880,13 +853,25 @@ export class BaileysStartupService extends ChannelStartupService { messages, chats, contacts, + isLatest, + progress, + syncType, }: { chats: Chat[]; contacts: Contact[]; messages: proto.IWebMessageInfo[]; - isLatest: boolean; + isLatest?: boolean; + progress?: number; + syncType?: proto.HistorySync.HistorySyncType; }) => { try { + if (syncType === proto.HistorySync.HistorySyncType.ON_DEMAND) { + console.log('received on-demand history sync, messages=', messages); + } + console.log( + `recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest}, progress: ${progress}%), type: ${syncType}`, + ); + const instance: InstanceDto = { instanceName: this.instance.name }; let timestampLimitToImport = null; @@ -1023,6 +1008,7 @@ export class BaileysStartupService extends ChannelStartupService { messages = undefined; chats = undefined; } catch (error) { + this.logger.error('line 1011'); this.logger.error(error); } }, @@ -1031,14 +1017,31 @@ export class BaileysStartupService extends ChannelStartupService { { messages, type, + requestId, }: { messages: proto.IWebMessageInfo[]; type: MessageUpsertType; + requestId?: string; }, settings: any, ) => { try { for (const received of messages) { + if (received.message?.conversation || received.message?.extendedTextMessage?.text) { + const text = received.message?.conversation || received.message?.extendedTextMessage?.text; + if (text == 'requestPlaceholder' && !requestId) { + // const messageId = await this.client.requestPlaceholderResend(received.key); + // console.log('requested placeholder resync, id=', messageId); + } else if (requestId) { + console.log('Message received from phone, id=', requestId, received); + } + + if (text == 'onDemandHistSync') { + // const messageId = await this.client.fetchMessageHistory(50, received.key, received.messageTimestamp!); + // console.log('requested on-demand sync, id=', messageId); + } + } + if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { const editedMessage = received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; @@ -1175,6 +1178,7 @@ export class BaileysStartupService extends ChannelStartupService { messageRaw.message.mediaUrl = mediaUrl; } catch (error) { + this.logger.error('line 1181'); this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); } } @@ -1311,6 +1315,7 @@ export class BaileysStartupService extends ChannelStartupService { }); } } catch (error) { + this.logger.error('line 1318'); this.logger.error(error); } }, @@ -1489,7 +1494,7 @@ export class BaileysStartupService extends ChannelStartupService { data: { association: LabelAssociation; type: 'remove' | 'add' }, database: Database, ) => { - if (database.ENABLED && database.SAVE_DATA.CHATS) { + if (database.SAVE_DATA.CHATS) { const chats = await this.prismaRepository.chat.findMany({ where: { instanceId: this.instanceId }, }); @@ -1512,7 +1517,6 @@ export class BaileysStartupService extends ChannelStartupService { } } - // Envia dados para o webhook this.sendDataWebhook(Events.LABELS_ASSOCIATION, { instance: this.instance.name, type: data.type, @@ -1559,7 +1563,7 @@ export class BaileysStartupService extends ChannelStartupService { if (events['messaging-history.set']) { const payload = events['messaging-history.set']; - this.messageHandle['messaging-history.set'](payload as any); + this.messageHandle['messaging-history.set'](payload); } if (events['messages.upsert']) { @@ -1671,9 +1675,9 @@ export class BaileysStartupService extends ChannelStartupService { public async profilePicture(number: string) { const jid = this.createJid(number); - const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image'); - try { + const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image'); + return { wuid: jid, profilePictureUrl, @@ -1772,11 +1776,11 @@ export class BaileysStartupService extends ChannelStartupService { }; if (isJidGroup(sender)) { + option.useCachedGroupMetadata = true; if (participants) option.cachedGroupMetadata = async () => { return { participants: participants as GroupParticipant[] }; }; - else option.cachedGroupMetadata = this.getGroupMetadataCache; } if (ephemeralExpiration) option.ephemeralExpiration = ephemeralExpiration; @@ -2003,7 +2007,7 @@ export class BaileysStartupService extends ChannelStartupService { quoted, null, group?.ephemeralDuration, - group.participants, + // group?.participants, ); } else { messageSent = await this.sendMessage(sender, message, mentions, linkPreview, quoted); @@ -2074,6 +2078,7 @@ export class BaileysStartupService extends ChannelStartupService { return messageSent; } catch (error) { + this.logger.error('line 2081'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2126,6 +2131,7 @@ export class BaileysStartupService extends ChannelStartupService { return { presence: data.presence }; } catch (error) { + this.logger.error('line 2134'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2138,6 +2144,7 @@ export class BaileysStartupService extends ChannelStartupService { return { presence: data.presence }; } catch (error) { + this.logger.error('line 2147'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2368,6 +2375,7 @@ export class BaileysStartupService extends ChannelStartupService { { userJid: this.instance.wuid }, ); } catch (error) { + this.logger.error('line 2378'); this.logger.error(error); throw new InternalServerErrorException(error?.toString() || error); } @@ -2409,6 +2417,7 @@ export class BaileysStartupService extends ChannelStartupService { return webpBuffer; } catch (error) { + this.logger.error('line 2420'); console.error('Erro ao converter a imagem para WebP:', error); throw error; } @@ -2806,6 +2815,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.readMessages(keys); return { message: 'Read messages', read: 'success' }; } catch (error) { + this.logger.error('line 2818'); throw new InternalServerErrorException('Read messages fail', error.toString()); } } @@ -2871,6 +2881,7 @@ export class BaileysStartupService extends ChannelStartupService { archived: true, }; } catch (error) { + this.logger.error('line 2884'); throw new InternalServerErrorException({ archived: false, message: ['An error occurred while archiving the chat. Open a calling.', error.toString()], @@ -2908,6 +2919,7 @@ export class BaileysStartupService extends ChannelStartupService { markedChatUnread: true, }; } catch (error) { + this.logger.error('line 2922'); throw new InternalServerErrorException({ markedChatUnread: false, message: ['An error occurred while marked unread the chat. Open a calling.', error.toString()], @@ -2919,6 +2931,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.client.sendMessage(del.remoteJid, { delete: del }); } catch (error) { + this.logger.error('line 2934'); throw new InternalServerErrorException('Error while deleting message for everyone', error?.toString()); } } @@ -3010,6 +3023,7 @@ export class BaileysStartupService extends ChannelStartupService { buffer: getBuffer ? buffer : null, }; } catch (error) { + this.logger.error('line 3026'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3051,6 +3065,7 @@ export class BaileysStartupService extends ChannelStartupService { }, }; } catch (error) { + this.logger.error('line 3068'); throw new InternalServerErrorException('Error updating privacy settings', error.toString()); } } @@ -3076,6 +3091,7 @@ export class BaileysStartupService extends ChannelStartupService { ...profile, }; } catch (error) { + this.logger.error('line 3094'); throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -3086,6 +3102,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3105'); throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -3096,6 +3113,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3116'); throw new InternalServerErrorException('Error updating profile status', error.toString()); } } @@ -3137,6 +3155,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3158'); throw new InternalServerErrorException('Error updating profile picture', error.toString()); } } @@ -3149,6 +3168,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3171'); throw new InternalServerErrorException('Error removing profile picture', error.toString()); } } @@ -3169,6 +3189,7 @@ export class BaileysStartupService extends ChannelStartupService { return { block: 'success' }; } catch (error) { + this.logger.error('line 3192'); throw new InternalServerErrorException('Error blocking user', error.toString()); } } @@ -3199,6 +3220,7 @@ export class BaileysStartupService extends ChannelStartupService { return null; } catch (error) { + this.logger.error('line 3223'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3220,6 +3242,7 @@ export class BaileysStartupService extends ChannelStartupService { edit: data.key, }); } catch (error) { + this.logger.error('line 3245'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3262,6 +3285,7 @@ export class BaileysStartupService extends ChannelStartupService { return { numberJid: contact.jid, labelId: data.labelId, remove: true }; } } catch (error) { + this.logger.error('line 3288'); throw new BadRequestException(`Unable to ${data.action} label to chat`, error.toString()); } } @@ -3271,14 +3295,19 @@ export class BaileysStartupService extends ChannelStartupService { try { const meta = await this.client.groupMetadata(groupJid); - this.logger.verbose(`Updating cache for group: ${groupJid}`); - await groupMetadataCache.set(groupJid, { - timestamp: Date.now(), - data: meta, - }); + const cacheConf = this.configService.get('CACHE'); + + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + this.logger.verbose(`Updating cache for group: ${groupJid}`); + await groupMetadataCache.set(groupJid, { + timestamp: Date.now(), + data: meta, + }); + } return meta; } catch (error) { + this.logger.error('line 3310'); this.logger.error(error); return null; } @@ -3287,19 +3316,25 @@ export class BaileysStartupService extends ChannelStartupService { private async getGroupMetadataCache(groupJid: string) { if (!isJidGroup(groupJid)) return null; - if (await groupMetadataCache.has(groupJid)) { - console.log(`Cache request for group: ${groupJid}`); - const meta = await groupMetadataCache.get(groupJid); + const cacheConf = this.configService.get('CACHE'); - if (Date.now() - meta.timestamp > 3600000) { - await this.updateGroupMetadataCache(groupJid); + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + if (await groupMetadataCache.has(groupJid)) { + console.log(`Cache request for group: ${groupJid}`); + const meta = await groupMetadataCache.get(groupJid); + + if (Date.now() - meta.timestamp > 3600000) { + await this.updateGroupMetadataCache(groupJid); + } + + return meta.data; } - return meta.data; + console.log(`Cache request for group: ${groupJid} - not found`); + return await this.updateGroupMetadataCache(groupJid); } - console.log(`Cache request for group: ${groupJid} - not found`); - return await this.updateGroupMetadataCache(groupJid); + return await this.findGroup({ groupJid }, 'inner'); } public async createGroup(create: CreateGroupDto) { @@ -3325,6 +3360,7 @@ export class BaileysStartupService extends ChannelStartupService { return group; } catch (error) { + this.logger.error('line 3363'); this.logger.error(error); throw new InternalServerErrorException('Error creating group', error.toString()); } @@ -3364,6 +3400,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3403'); throw new InternalServerErrorException('Error update group picture', error.toString()); } } @@ -3374,6 +3411,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3414'); throw new InternalServerErrorException('Error updating group subject', error.toString()); } } @@ -3384,6 +3422,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3425'); throw new InternalServerErrorException('Error updating group description', error.toString()); } } @@ -3418,6 +3457,7 @@ export class BaileysStartupService extends ChannelStartupService { if (reply === 'inner') { return; } + this.logger.error('line 3460'); throw new NotFoundException('Error fetching group', error.toString()); } } @@ -3427,8 +3467,7 @@ export class BaileysStartupService extends ChannelStartupService { let groups = []; for (const group of fetch) { - const picture = null; - // const picture = await this.profilePicture(group.id); + const picture = await this.profilePicture(group.id); const result = { id: group.id, @@ -3460,6 +3499,7 @@ export class BaileysStartupService extends ChannelStartupService { const code = await this.client.groupInviteCode(id.groupJid); return { inviteUrl: `https://chat.whatsapp.com/${code}`, inviteCode: code }; } catch (error) { + this.logger.error('line 3502'); throw new NotFoundException('No invite code', error.toString()); } } @@ -3468,6 +3508,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.client.groupGetInviteInfo(id.inviteCode); } catch (error) { + this.logger.error('line 3511'); throw new NotFoundException('No invite info', id.inviteCode); } } @@ -3493,6 +3534,7 @@ export class BaileysStartupService extends ChannelStartupService { return { send: true, inviteUrl }; } catch (error) { + this.logger.error('line 3537'); throw new NotFoundException('No send invite'); } } @@ -3502,6 +3544,7 @@ export class BaileysStartupService extends ChannelStartupService { const groupJid = await this.client.groupAcceptInvite(id.inviteCode); return { accepted: true, groupJid: groupJid }; } catch (error) { + this.logger.error('line 3547'); throw new NotFoundException('Accept invite error', error.toString()); } } @@ -3511,6 +3554,7 @@ export class BaileysStartupService extends ChannelStartupService { const inviteCode = await this.client.groupRevokeInvite(id.groupJid); return { revoked: true, inviteCode }; } catch (error) { + this.logger.error('line 3557'); throw new NotFoundException('Revoke error', error.toString()); } } @@ -3536,6 +3580,7 @@ export class BaileysStartupService extends ChannelStartupService { }); return { participants: parsedParticipants }; } catch (error) { + this.logger.error('line 3583'); throw new NotFoundException('No participants', error.toString()); } } @@ -3550,6 +3595,7 @@ export class BaileysStartupService extends ChannelStartupService { ); return { updateParticipants: updateParticipants }; } catch (error) { + this.logger.error('line 3598'); throw new BadRequestException('Error updating participants', error.toString()); } } @@ -3559,6 +3605,7 @@ export class BaileysStartupService extends ChannelStartupService { const updateSetting = await this.client.groupSettingUpdate(update.groupJid, update.action); return { updateSetting: updateSetting }; } catch (error) { + this.logger.error('line 3608'); throw new BadRequestException('Error updating setting', error.toString()); } } @@ -3568,6 +3615,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.groupToggleEphemeral(update.groupJid, update.expiration); return { success: true }; } catch (error) { + this.logger.error('line 3618'); throw new BadRequestException('Error updating setting', error.toString()); } } @@ -3577,6 +3625,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.groupLeave(id.groupJid); return { groupJid: id.groupJid, leave: true }; } catch (error) { + this.logger.error('line 3628'); throw new BadRequestException('Unable to leave the group', error.toString()); } } diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index a5f20332..2e18229a 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -121,7 +121,7 @@ export class WAMonitoringService { public async cleaningUp(instanceName: string) { let instanceDbId: string; - if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + if (this.db.SAVE_DATA.INSTANCE) { const instance = await this.prismaRepository.instance.update({ where: { name: instanceName }, data: { connectionStatus: 'close' }, @@ -181,7 +181,7 @@ export class WAMonitoringService { try { if (this.providerSession?.ENABLED) { await this.loadInstancesFromProvider(); - } else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + } else if (this.db.SAVE_DATA.INSTANCE) { await this.loadInstancesFromDatabasePostgres(); } else if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.loadInstancesFromRedis(); @@ -193,21 +193,19 @@ export class WAMonitoringService { public async saveInstance(data: any) { try { - if (this.db.ENABLED) { - const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; - await this.prismaRepository.instance.create({ - data: { - id: data.instanceId, - name: data.instanceName, - connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BUSINESS ? 'open' : 'close', - number: data.number, - integration: data.integration || Integration.WHATSAPP_BAILEYS, - token: data.hash, - clientName: clientName, - businessId: data.businessId, - }, - }); - } + const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; + await this.prismaRepository.instance.create({ + data: { + id: data.instanceId, + name: data.instanceName, + connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BUSINESS ? 'open' : 'close', + number: data.number, + integration: data.integration || Integration.WHATSAPP_BAILEYS, + token: data.hash, + clientName: clientName, + businessId: data.businessId, + }, + }); } catch (error) { this.logger.error(error); } diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 7df8dd3a..bc232e89 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -51,7 +51,6 @@ export type DBConnection = { }; export type Database = { CONNECTION: DBConnection; - ENABLED: boolean; PROVIDER: string; SAVE_DATA: SaveData; }; @@ -286,7 +285,6 @@ export class ConfigService { URI: process.env.DATABASE_CONNECTION_URI || '', CLIENT_NAME: process.env.DATABASE_CONNECTION_CLIENT_NAME || 'evolution', }, - ENABLED: process.env?.DATABASE_ENABLED === 'true', PROVIDER: process.env.DATABASE_PROVIDER || 'postgresql', SAVE_DATA: { INSTANCE: process.env?.DATABASE_SAVE_DATA_INSTANCE === 'true', @@ -464,7 +462,7 @@ export class ConfigService { ENABLE: process.env?.S3_ENABLED === 'true', PORT: Number.parseInt(process.env?.S3_PORT || '9000'), USE_SSL: process.env?.S3_USE_SSL === 'true', - REGION: process.env?.S3_REGION + REGION: process.env?.S3_REGION, }, AUTHENTICATION: { API_KEY: { diff --git a/src/libs/prisma.connect.ts b/src/libs/prisma.connect.ts index 849d26e2..fa8d6600 100644 --- a/src/libs/prisma.connect.ts +++ b/src/libs/prisma.connect.ts @@ -1,21 +1,16 @@ -import { configService, Database } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { PrismaClient } from '@prisma/client'; const logger = new Logger('Prisma'); -const db = configService.get('DATABASE'); - export const prismaServer = (() => { - if (db.ENABLED) { - logger.verbose('connecting'); - const db = new PrismaClient(); + logger.verbose('connecting'); + const db = new PrismaClient(); - process.on('beforeExit', () => { - logger.verbose('instance destroyed'); - db.$disconnect(); - }); + process.on('beforeExit', () => { + logger.verbose('instance destroyed'); + db.$disconnect(); + }); - return db; - } + return db; })(); diff --git a/src/main.ts b/src/main.ts index d96291e4..0598ae75 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,3 @@ -import 'express-async-errors'; - import { initAMQP, initGlobalQueues } from '@api/integrations/rabbitmq/libs/amqp.server'; import { initSQS } from '@api/integrations/sqs/libs/sqs.server'; import { initIO } from '@api/integrations/websocket/libs/socket.server'; @@ -11,6 +9,7 @@ import { Auth, configService, Cors, HttpServer, ProviderSession, Rabbitmq, Sqs, import { onUnexpectedError } from '@config/error.config'; import { Logger } from '@config/logger.config'; import { ROOT_DIR } from '@config/path.config'; +import * as Sentry from '@sentry/node'; import { ServerUP } from '@utils/server-up'; import axios from 'axios'; import compression from 'compression'; @@ -25,6 +24,19 @@ function initWA() { async function bootstrap() { const logger = new Logger('SERVER'); const app = express(); + const dsn = process.env.SENTRY_DSN; + + if (dsn) { + logger.info('Sentry - ON'); + Sentry.init({ + dsn: dsn, + environment: process.env.NODE_ENV || 'development', + tracesSampleRate: 1.0, + }); + app.use(Sentry.Handlers.requestHandler()); + app.use(Sentry.Handlers.tracingHandler()); + app.use(Sentry.Handlers.errorHandler()); + } let providerFiles: ProviderFiles = null; if (configService.get('PROVIDER').ENABLED) {