diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c4363a9..c8bf1cedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.7.3 (2024-04-18 12:07) + +### Fixed +* Revert fix audio encoding +* Recovering messages lost with redis cache +* Adjusts in redis for save instances +* Adjusts in proxy +* Revert pull request #523 +* Added instance name on logs +* Added support for Spanish +* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to in chatwoot + # 1.7.2 (2024-04-12 17:31) ### Feature diff --git a/Docker/.env.example b/Docker/.env.example index 865ef8778..04dd08057 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true # Permanent data storage DATABASE_ENABLED=false -DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true +DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin & +readPreference=primary & +ssl=false & +directConnection=true DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker # Choose the data you want to save in the application's database or store @@ -43,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CHATS=false -REDIS_ENABLED=false -REDIS_URI=redis://redis:6379 -REDIS_PREFIX_KEY=evdocker - RABBITMQ_ENABLED=false RABBITMQ_RABBITMQ_MODE=global RABBITMQ_EXCHANGE_NAME=evolution_exchange @@ -73,7 +72,7 @@ WEBHOOK_GLOBAL_URL='' WEBHOOK_GLOBAL_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_GLOBAL_WEBHOOK_BY_EVENTS=false -## Set the events you want to hear +## Set the events you want to hear WEBHOOK_EVENTS_APPLICATION_STARTUP=false WEBHOOK_EVENTS_QRCODE_UPDATED=true WEBHOOK_EVENTS_MESSAGES_SET=true @@ -129,6 +128,14 @@ CHATWOOT_MESSAGE_READ=false # false | true CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true +CACHE_REDIS_ENABLED=false +CACHE_REDIS_URI=redis://redis:6379 +CACHE_REDIS_PREFIX_KEY=evolution +CACHE_REDIS_TTL=604800 +CACHE_REDIS_SAVE_INSTANCES=false +CACHE_LOCAL_ENABLED=false +CACHE_LOCAL_TTL=604800 + # 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 @@ -143,4 +150,4 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true AUTHENTICATION_JWT_EXPIRIN_IN=0 AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`' -LANGUAGE=en # pt-BR, en \ No newline at end of file +LANGUAGE=en # pt-BR, en diff --git a/Docker/evolution-api-all-services/.env.example b/Docker/evolution-api-all-services/.env.example index a28ad50fd..d08fee493 100644 --- a/Docker/evolution-api-all-services/.env.example +++ b/Docker/evolution-api-all-services/.env.example @@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true # Permanent data storage DATABASE_ENABLED=true -DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true +DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin & +readPreference=primary & +ssl=false & +directConnection=true DATABASE_CONNECTION_DB_PREFIX_NAME=evolution # Choose the data you want to save in the application's database or store @@ -43,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CHATS=false -REDIS_ENABLED=true -REDIS_URI=redis://redis:6379 -REDIS_PREFIX_KEY=evolution - # Global Webhook Settings # Each instance's Webhook URL and events will be requested at the time it is created ## Define a global webhook that will listen for enabled events from all instances @@ -54,7 +53,7 @@ WEBHOOK_GLOBAL_URL='' WEBHOOK_GLOBAL_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_GLOBAL_WEBHOOK_BY_EVENTS=false -## Set the events you want to hear +## Set the events you want to hear WEBHOOK_EVENTS_APPLICATION_STARTUP=false WEBHOOK_EVENTS_QRCODE_UPDATED=true WEBHOOK_EVENTS_MESSAGES_SET=true @@ -87,6 +86,14 @@ CONFIG_SESSION_PHONE_NAME=chrome # Set qrcode display limit QRCODE_LIMIT=30 +CACHE_REDIS_ENABLED=false +CACHE_REDIS_URI=redis://redis:6379 +CACHE_REDIS_PREFIX_KEY=evolution +CACHE_REDIS_TTL=604800 +CACHE_REDIS_SAVE_INSTANCES=false +CACHE_LOCAL_ENABLED=false +CACHE_LOCAL_TTL=604800 + # 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 @@ -109,4 +116,4 @@ AUTHENTICATION_INSTANCE_NAME=evolution AUTHENTICATION_INSTANCE_WEBHOOK_URL='' AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1 AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456 -AUTHENTICATION_INSTANCE_CHATWOOT_URL='' \ No newline at end of file +AUTHENTICATION_INSTANCE_CHATWOOT_URL='' diff --git a/Dockerfile b/Dockerfile index b4f20c803..a5e5c8b20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:20.7.0-alpine AS builder -LABEL version="1.7.2" description="Api to control whatsapp features through http requests." +LABEL version="1.7.3" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL contact="contato@agenciadgcode.com" @@ -58,10 +58,6 @@ ENV DATABASE_SAVE_MESSAGE_UPDATE=false ENV DATABASE_SAVE_DATA_CONTACTS=false ENV DATABASE_SAVE_DATA_CHATS=false -ENV REDIS_ENABLED=false -ENV REDIS_URI=redis://redis:6379 -ENV REDIS_PREFIX_KEY=evolution - ENV RABBITMQ_ENABLED=false ENV RABBITMQ_MODE=global ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange @@ -129,6 +125,14 @@ ENV QRCODE_COLOR=#198754 ENV TYPEBOT_API_VERSION=latest +ENV CACHE_REDIS_ENABLED=false +ENV CACHE_REDIS_URI=redis://redis:6379 +ENV CACHE_REDIS_PREFIX_KEY=evolution +ENV CACHE_REDIS_TTL=604800 +ENV CACHE_REDIS_SAVE_INSTANCES=false +ENV CACHE_LOCAL_ENABLED=false +ENV CACHE_LOCAL_TTL=604800 + ENV AUTHENTICATION_TYPE=apikey ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976 diff --git a/package.json b/package.json index 8b31f9025..bf5832ad2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "1.7.2", + "version": "1.7.3", "description": "Rest api for communication with WhatsApp", "main": "./dist/src/main.js", "scripts": { diff --git a/src/api/abstract/abstract.cache.ts b/src/api/abstract/abstract.cache.ts index caad26914..2d93f3232 100644 --- a/src/api/abstract/abstract.cache.ts +++ b/src/api/abstract/abstract.cache.ts @@ -1,13 +1,19 @@ export interface ICache { get(key: string): Promise; + hGet(key: string, field: string): Promise; + set(key: string, value: any, ttl?: number): void; + hSet(key: string, field: string, value: any): Promise; + has(key: string): Promise; keys(appendCriteria?: string): Promise; delete(key: string | string[]): Promise; + hDelete(key: string, field: string): Promise; + deleteAll(appendCriteria?: string): Promise; } diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 563bc5fdb..abe1dd3b3 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -6,7 +6,6 @@ import { v4 } from 'uuid'; import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; import { BadRequestException, InternalServerErrorException } from '../../exceptions'; -import { RedisCache } from '../../libs/redis.client'; import { InstanceDto, SetPresenceDto } from '../dto/instance.dto'; import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service'; import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service'; @@ -41,8 +40,9 @@ export class InstanceController { private readonly typebotService: TypebotService, private readonly integrationService: IntegrationService, private readonly proxyService: ProxyController, - private readonly cache: RedisCache, + private readonly cache: CacheService, private readonly chatwootCache: CacheService, + private readonly messagesLostCache: CacheService, ) {} private readonly logger = new Logger(InstanceController.name); @@ -108,6 +108,7 @@ export class InstanceController { this.repository, this.cache, this.chatwootCache, + this.messagesLostCache, ); } else { instance = new BaileysStartupService( @@ -116,6 +117,7 @@ export class InstanceController { this.repository, this.cache, this.chatwootCache, + this.messagesLostCache, ); } diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index 2214bd43d..84cabb122 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express'; import { existsSync } from 'fs'; import { join } from 'path'; -import { configService, Database, Redis } from '../../config/env.config'; +import { CacheConf, configService, Database } from '../../config/env.config'; import { INSTANCE_DIR } from '../../config/path.config'; import { BadRequestException, @@ -17,12 +17,13 @@ import { cache, waMonitor } from '../server.module'; async function getInstance(instanceName: string) { try { const db = configService.get('DATABASE'); - const redisConf = configService.get('REDIS'); + const cacheConf = configService.get('CACHE'); const exists = !!waMonitor.waInstances[instanceName]; - if (redisConf.ENABLED) { - const keyExists = await cache.keyExists(); + if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) { + const keyExists = await cache.has(instanceName); + return exists || keyExists; } diff --git a/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts b/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts index 03282ce54..2621465e0 100644 --- a/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts +++ b/src/api/integrations/chatwoot/controllers/chatwoot.controller.ts @@ -1,5 +1,6 @@ import { isURL } from 'class-validator'; +import { CacheEngine } from '../../../../cache/cacheengine'; import { ConfigService, HttpServer } from '../../../../config/env.config'; import { Logger } from '../../../../config/logger.config'; import { BadRequestException } from '../../../../exceptions'; @@ -7,7 +8,6 @@ import { InstanceDto } from '../../../dto/instance.dto'; import { RepositoryBroker } from '../../../repository/repository.manager'; import { waMonitor } from '../../../server.module'; import { CacheService } from '../../../services/cache.service'; -import { CacheEngine } from '../cache/cacheengine'; import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootService } from '../services/chatwoot.service'; diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 35fbb7af4..e484ec662 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -461,7 +461,7 @@ export class ChatwootService { const queryOperator = fieldsToSearch.length - 1 === index1 && numbers.length - 1 === index2 ? null : 'OR'; filterPayload.push({ attribute_key: field, - filter_operator: field == 'phone_number' ? 'equal_to' : 'contains', + filter_operator: ['phone_number', 'identifier'].includes(field) ? 'equal_to' : 'contains', values: [number.replace('+', '')], query_operator: queryOperator, }); diff --git a/src/api/integrations/typebot/services/typebot.service.ts b/src/api/integrations/typebot/services/typebot.service.ts index 6c3225520..eae67117d 100644 --- a/src/api/integrations/typebot/services/typebot.service.ts +++ b/src/api/integrations/typebot/services/typebot.service.ts @@ -57,7 +57,7 @@ export class TypebotService { if (session) { if (status === 'closed') { - const found_session: Session[] = findData.sessions.splice(findData.sessions.indexOf(session), 1); + findData.sessions.splice(findData.sessions.indexOf(session), 1); const typebotData = { enabled: findData.enabled, @@ -68,7 +68,7 @@ export class TypebotService { delay_message: findData.delay_message, unknown_message: findData.unknown_message, listening_from_me: findData.listening_from_me, - sessions: found_session, + sessions: findData.sessions, }; this.create(instance, typebotData); @@ -106,7 +106,7 @@ export class TypebotService { delay_message: findData.delay_message, unknown_message: findData.unknown_message, listening_from_me: findData.listening_from_me, - sessions: findData.sessions.splice(findData.sessions.indexOf(session), 1), + sessions: findData.sessions, }; this.create(instance, typebotData); diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 5c78e1c31..97df81a39 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -1,8 +1,8 @@ +import { CacheEngine } from '../cache/cacheengine'; import { configService } from '../config/env.config'; import { eventEmitter } from '../config/event.config'; import { Logger } from '../config/logger.config'; import { dbserver } from '../libs/db.connect'; -import { RedisCache } from '../libs/redis.client'; import { ChatController } from './controllers/chat.controller'; import { GroupController } from './controllers/group.controller'; import { InstanceController } from './controllers/instance.controller'; @@ -14,7 +14,6 @@ import { WebhookController } from './controllers/webhook.controller'; import { ChamaaiController } from './integrations/chamaai/controllers/chamaai.controller'; import { ChamaaiRepository } from './integrations/chamaai/repository/chamaai.repository'; import { ChamaaiService } from './integrations/chamaai/services/chamaai.service'; -import { CacheEngine } from './integrations/chatwoot/cache/cacheengine'; import { ChatwootController } from './integrations/chatwoot/controllers/chatwoot.controller'; import { ChatwootRepository } from './integrations/chatwoot/repository/chatwoot.repository'; import { ChatwootService } from './integrations/chatwoot/services/chatwoot.service'; @@ -107,10 +106,18 @@ export const repository = new RepositoryBroker( dbserver?.getClient(), ); -export const cache = new RedisCache(); +export const cache = new CacheService(new CacheEngine(configService, 'instance').getEngine()); const chatwootCache = new CacheService(new CacheEngine(configService, ChatwootService.name).getEngine()); +const messagesLostCache = new CacheService(new CacheEngine(configService, 'baileys').getEngine()); -export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache, chatwootCache); +export const waMonitor = new WAMonitoringService( + eventEmitter, + configService, + repository, + cache, + chatwootCache, + messagesLostCache, +); const authService = new AuthService(configService, waMonitor, repository); @@ -160,6 +167,7 @@ export const instanceController = new InstanceController( proxyController, cache, chatwootCache, + messagesLostCache, ); export const sendMessageController = new SendMessageController(waMonitor); export const chatController = new ChatController(waMonitor); diff --git a/src/api/services/cache.service.ts b/src/api/services/cache.service.ts index 0db39a445..caf3dbfae 100644 --- a/src/api/services/cache.service.ts +++ b/src/api/services/cache.service.ts @@ -1,3 +1,5 @@ +import { BufferJSON } from '@whiskeysockets/baileys'; + import { Logger } from '../../config/logger.config'; import { ICache } from '../abstract/abstract.cache'; @@ -20,6 +22,21 @@ export class CacheService { return this.cache.get(key); } + public async hGet(key: string, field: string) { + try { + const data = await this.cache.hGet(key, field); + + if (data) { + return JSON.parse(data, BufferJSON.reviver); + } + + return null; + } catch (error) { + this.logger.error(error); + return null; + } + } + async set(key: string, value: any) { if (!this.cache) { return; @@ -28,6 +45,16 @@ export class CacheService { this.cache.set(key, value); } + public async hSet(key: string, field: string, value: any) { + try { + const json = JSON.stringify(value, BufferJSON.replacer); + + await this.cache.hSet(key, field, json); + } catch (error) { + this.logger.error(error); + } + } + async has(key: string) { if (!this.cache) { return; @@ -44,6 +71,16 @@ export class CacheService { return this.cache.delete(key); } + async hDelete(key: string, field: string) { + try { + await this.cache.hDelete(key, field); + return true; + } catch (error) { + this.logger.error(error); + return false; + } + } + async deleteAll(appendCriteria?: string) { if (!this.cache) { return; diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index e8644e99e..df6b333ad 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -5,11 +5,10 @@ import { Db } from 'mongodb'; import { Collection } from 'mongoose'; import { join } from 'path'; -import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config'; +import { Auth, CacheConf, ConfigService, Database, DelInstance, HttpServer } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; import { NotFoundException } from '../../exceptions'; -import { RedisCache } from '../../libs/redis.client'; import { AuthModel, ChamaaiModel, @@ -34,8 +33,9 @@ export class WAMonitoringService { private readonly eventEmitter: EventEmitter2, private readonly configService: ConfigService, private readonly repository: RepositoryBroker, - private readonly cache: RedisCache, + private readonly cache: CacheService, private readonly chatwootCache: CacheService, + private readonly messagesLostCache: CacheService, ) { this.logger.verbose('instance created'); @@ -43,7 +43,7 @@ export class WAMonitoringService { this.noConnection(); Object.assign(this.db, configService.get('DATABASE')); - Object.assign(this.redis, configService.get('REDIS')); + Object.assign(this.redis, configService.get('CACHE')); this.dbInstance = this.db.ENABLED ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') @@ -51,7 +51,7 @@ export class WAMonitoringService { } private readonly db: Partial = {}; - private readonly redis: Partial = {}; + private readonly redis: Partial = {}; private dbInstance: Db; @@ -212,7 +212,7 @@ export class WAMonitoringService { }); this.logger.verbose('instance files deleted: ' + name); }); - } else if (!this.redis.ENABLED) { + } else if (!this.redis.REDIS.ENABLED && !this.redis.REDIS.SAVE_INSTANCES) { const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); for await (const dirent of dir) { if (dirent.isDirectory()) { @@ -247,10 +247,9 @@ export class WAMonitoringService { return; } - if (this.redis.ENABLED) { + if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { this.logger.verbose('cleaning up instance in redis: ' + instanceName); - this.cache.reference = instanceName; - await this.cache.delAll(); + await this.cache.delete(instanceName); return; } @@ -303,7 +302,7 @@ export class WAMonitoringService { this.logger.verbose('Loading instances'); try { - if (this.redis.ENABLED) { + if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.loadInstancesFromRedis(); } else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { await this.loadInstancesFromDatabase(); @@ -346,6 +345,7 @@ export class WAMonitoringService { this.repository, this.cache, this.chatwootCache, + this.messagesLostCache, ); instance.instanceName = name; @@ -356,6 +356,7 @@ export class WAMonitoringService { this.repository, this.cache, this.chatwootCache, + this.messagesLostCache, ); instance.instanceName = name; @@ -374,12 +375,11 @@ export class WAMonitoringService { private async loadInstancesFromRedis() { this.logger.verbose('Redis enabled'); - await this.cache.connect(this.redis as Redis); - const keys = await this.cache.getInstanceKeys(); + const keys = await this.cache.keys(); if (keys?.length > 0) { this.logger.verbose('Reading instance keys and setting instances'); - await Promise.all(keys.map((k) => this.setInstance(k.split(':')[1]))); + await Promise.all(keys.map((k) => this.setInstance(k.split(':')[2]))); } else { this.logger.verbose('No instance keys found'); } diff --git a/src/api/services/whatsapp.service.ts b/src/api/services/whatsapp.service.ts index 5c6df8d3e..ade84c4fd 100644 --- a/src/api/services/whatsapp.service.ts +++ b/src/api/services/whatsapp.service.ts @@ -71,6 +71,8 @@ export class WAStartupService { public chamaaiService = new ChamaaiService(waMonitor, this.configService); public set instanceName(name: string) { + this.logger.setInstance(name); + this.logger.verbose(`Initializing instance '${name}'`); if (!name) { this.logger.verbose('Instance name not found, generating random name with uuid'); @@ -583,7 +585,7 @@ export class WAStartupService { this.logger.verbose(`Proxy enabled: ${this.localProxy.enabled}`); this.localProxy.proxy = data?.proxy; - this.logger.verbose(`Proxy proxy: ${this.localProxy.proxy.host}`); + this.logger.verbose(`Proxy proxy: ${this.localProxy.proxy?.host}`); this.logger.verbose('Proxy loaded'); } diff --git a/src/api/services/whatsapp/whatsapp.baileys.service.ts b/src/api/services/whatsapp/whatsapp.baileys.service.ts index 6285250bb..732f82a08 100644 --- a/src/api/services/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/services/whatsapp/whatsapp.baileys.service.ts @@ -39,9 +39,10 @@ import makeWASocket, { 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 { arrayUnique, isBase64, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; -import ffmpeg from 'fluent-ffmpeg'; +// import ffmpeg from 'fluent-ffmpeg'; import fs, { existsSync, readFileSync } from 'fs'; import { parsePhoneNumber } from 'libphonenumber-js'; import Long from 'long'; @@ -54,11 +55,10 @@ import qrcode, { QRCodeToDataURLOptions } from 'qrcode'; import qrcodeTerminal from 'qrcode-terminal'; import sharp from 'sharp'; -import { ConfigService, ConfigSessionPhone, Database, Log, QrCode, Redis } from '../../../config/env.config'; +import { CacheConf, ConfigService, ConfigSessionPhone, Database, Log, QrCode } from '../../../config/env.config'; import { INSTANCE_DIR } from '../../../config/path.config'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../../exceptions'; import { dbserver } from '../../../libs/db.connect'; -import { RedisCache } from '../../../libs/redis.client'; import { makeProxyAgent } from '../../../utils/makeProxyAgent'; import { useMultiFileAuthStateDb } from '../../../utils/use-multi-file-auth-state-db'; import { useMultiFileAuthStateRedisDb } from '../../../utils/use-multi-file-auth-state-redis-db'; @@ -120,21 +120,21 @@ import { Events, MessageSubtype, TypeMediaMessage, wa } from '../../types/wa.typ import { CacheService } from './../cache.service'; import { WAStartupService } from './../whatsapp.service'; -// const retryCache = {}; - export class BaileysStartupService extends WAStartupService { constructor( public readonly configService: ConfigService, public readonly eventEmitter: EventEmitter2, public readonly repository: RepositoryBroker, - public readonly cache: RedisCache, + public readonly cache: CacheService, public readonly chatwootCache: CacheService, + public readonly messagesLostCache: CacheService, ) { super(configService, eventEmitter, repository, chatwootCache); this.logger.verbose('BaileysStartupService initialized'); this.cleanStore(); this.instance.qrcode = { count: 0 }; this.mobile = false; + this.recoveringMessages(); } private readonly msgRetryCounterCache: CacheStore = new NodeCache(); @@ -147,6 +147,26 @@ export class BaileysStartupService extends WAStartupService { public phoneNumber: string; public mobile: boolean; + private async recoveringMessages() { + const cacheConf = this.configService.get('CACHE'); + + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + setTimeout(async () => { + this.logger.info('Recovering messages'); + this.messagesLostCache.keys().then((keys) => { + keys.forEach(async (key) => { + const message = await this.messagesLostCache.get(key.split(':')[2]); + + if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { + this.logger.verbose('Message absent from node, retrying to send, key: ' + key.split(':')[2]); + await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); + } + }); + }); + }, 30000); + } + } + public get connectionStatus() { this.logger.verbose('Getting connection status'); return this.stateConnection; @@ -377,10 +397,12 @@ export class BaileysStartupService extends WAStartupService { │ CONNECTED TO WHATSAPP │ └──────────────────────────────┘`.replace(/^ +/gm, ' '), ); - this.logger.info(` + this.logger.info( + ` wuid: ${formattedWuid} name: ${formattedName} - `); + `, + ); if (this.localChatwoot.enabled) { this.chatwootService.eventWhatsapp( @@ -437,12 +459,11 @@ export class BaileysStartupService extends WAStartupService { private async defineAuthState() { this.logger.verbose('Defining auth state'); const db = this.configService.get('DATABASE'); - const redis = this.configService.get('REDIS'); + const cache = this.configService.get('CACHE'); - if (redis?.ENABLED) { - this.logger.verbose('Redis enabled'); - this.cache.reference = this.instance.name; - return await useMultiFileAuthStateRedisDb(this.cache); + if (cache?.REDIS.ENABLED && cache?.REDIS.SAVE_INSTANCES) { + this.logger.info('Redis enabled'); + return await useMultiFileAuthStateRedisDb(this.instance.name, this.cache); } if (db.SAVE_DATA.INSTANCE && db.ENABLED) { @@ -485,11 +506,11 @@ export class BaileysStartupService extends WAStartupService { let options; if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy.proxy.host); + this.logger.info('Proxy enabled: ' + this.localProxy.proxy?.host); if (this.localProxy?.proxy?.host?.includes('proxyscrape')) { try { - const response = await axios.get(this.localProxy.proxy.host); + const response = await axios.get(this.localProxy.proxy?.host); const text = response.data; const proxyUrls = text.split('\r\n'); const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); @@ -655,11 +676,11 @@ export class BaileysStartupService extends WAStartupService { let options; if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy.proxy.host); + this.logger.info('Proxy enabled: ' + this.localProxy.proxy?.host); if (this.localProxy?.proxy?.host?.includes('proxyscrape')) { try { - const response = await axios.get(this.localProxy.proxy.host); + const response = await axios.get(this.localProxy.proxy?.host); const text = response.data; const proxyUrls = text.split('\r\n'); const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); @@ -1043,6 +1064,20 @@ export class BaileysStartupService extends WAStartupService { } } + if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') { + this.logger.info('Recovering message lost'); + + await this.messagesLostCache.set(received.key.id, received); + continue; + } + + const retryCache = (await this.messagesLostCache.get(received.key.id)) || null; + + if (retryCache) { + this.logger.info('Recovered message lost'); + await this.messagesLostCache.delete(received.key.id); + } + if ( (type !== 'notify' && type !== 'append') || received.message?.protocolMessage || @@ -1241,7 +1276,6 @@ export class BaileysStartupService extends WAStartupService { } } - // if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { if (key.remoteJid !== 'status@broadcast') { this.logger.verbose('Message update is valid'); @@ -1472,27 +1506,12 @@ export class BaileysStartupService extends WAStartupService { if (events['messages.upsert']) { this.logger.verbose('Listening event: messages.upsert'); const payload = events['messages.upsert']; - // if (payload.messages.find((a) => a?.messageStubType === 2)) { - // const msg = payload.messages[0]; - // retryCache[msg.key.id] = msg; - // return; - // } this.messageHandle['messages.upsert'](payload, database, settings); } if (events['messages.update']) { this.logger.verbose('Listening event: messages.update'); const payload = events['messages.update']; - // payload.forEach((message) => { - // if (retryCache[message.key.id]) { - // this.client.ev.emit('messages.upsert', { - // messages: [message], - // type: 'notify', - // }); - // delete retryCache[message.key.id]; - // return; - // } - // }); this.messageHandle['messages.update'](payload, database, settings); } @@ -2297,6 +2316,82 @@ export class BaileysStartupService extends WAStartupService { return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot); } + // public async processAudio(audio: string, number: string) { + // this.logger.verbose('Processing audio'); + // let tempAudioPath: string; + // let outputAudio: string; + + // number = number.replace(/\D/g, ''); + // const hash = `${number}-${new Date().getTime()}`; + // this.logger.verbose('Hash to audio name: ' + hash); + + // if (isURL(audio)) { + // this.logger.verbose('Audio is url'); + + // outputAudio = `${join(this.storePath, 'temp', `${hash}.ogg`)}`; + // tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; + + // this.logger.verbose('Output audio path: ' + outputAudio); + // this.logger.verbose('Temp audio path: ' + tempAudioPath); + + // const timestamp = new Date().getTime(); + // const url = `${audio}?timestamp=${timestamp}`; + + // this.logger.verbose('Including timestamp in url: ' + url); + + // let config: any = { + // responseType: 'arraybuffer', + // }; + + // if (this.localProxy.enabled) { + // config = { + // ...config, + // httpsAgent: makeProxyAgent(this.localProxy.proxy), + // }; + // } + + // const response = await axios.get(url, config); + // this.logger.verbose('Getting audio from url'); + + // fs.writeFileSync(tempAudioPath, response.data); + // } else { + // this.logger.verbose('Audio is base64'); + + // outputAudio = `${join(this.storePath, 'temp', `${hash}.ogg`)}`; + // tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; + + // this.logger.verbose('Output audio path: ' + outputAudio); + // this.logger.verbose('Temp audio path: ' + tempAudioPath); + + // const audioBuffer = Buffer.from(audio, 'base64'); + // fs.writeFileSync(tempAudioPath, audioBuffer); + // this.logger.verbose('Temp audio created'); + // } + + // this.logger.verbose('Converting audio to mp4'); + // return new Promise((resolve, reject) => { + // // This fix was suggested by @PurpShell + // ffmpeg.setFfmpegPath(ffmpegPath.path); + + // ffmpeg() + // .input(tempAudioPath) + // .outputFormat('ogg') + // .noVideo() + // .audioCodec('libopus') + // .save(outputAudio) + // .on('error', function (error) { + // console.log('error', error); + // fs.unlinkSync(tempAudioPath); + // if (error) reject(error); + // }) + // .on('end', async function () { + // fs.unlinkSync(tempAudioPath); + // resolve(outputAudio); + // }) + // .run(); + // }); + // } + public async processAudio(audio: string, number: string) { this.logger.verbose('Processing audio'); let tempAudioPath: string; @@ -2309,7 +2404,7 @@ export class BaileysStartupService extends WAStartupService { if (isURL(audio)) { this.logger.verbose('Audio is url'); - outputAudio = `${join(this.storePath, 'temp', `${hash}.ogg`)}`; + outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; this.logger.verbose('Output audio path: ' + outputAudio); @@ -2320,25 +2415,14 @@ export class BaileysStartupService extends WAStartupService { this.logger.verbose('Including timestamp in url: ' + url); - let config: any = { - responseType: 'arraybuffer', - }; - - if (this.localProxy.enabled) { - config = { - ...config, - httpsAgent: makeProxyAgent(this.localProxy.proxy), - }; - } - - const response = await axios.get(url, config); + const response = await axios.get(url, { responseType: 'arraybuffer' }); this.logger.verbose('Getting audio from url'); fs.writeFileSync(tempAudioPath, response.data); } else { this.logger.verbose('Audio is base64'); - outputAudio = `${join(this.storePath, 'temp', `${hash}.ogg`)}`; + outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; this.logger.verbose('Output audio path: ' + outputAudio); @@ -2351,25 +2435,15 @@ export class BaileysStartupService extends WAStartupService { this.logger.verbose('Converting audio to mp4'); return new Promise((resolve, reject) => { - // This fix was suggested by @PurpShell - ffmpeg.setFfmpegPath(ffmpegPath.path); - - ffmpeg() - .input(tempAudioPath) - .outputFormat('ogg') - .noVideo() - .audioCodec('libopus') - .save(outputAudio) - .on('error', function (error) { - console.log('error', error); - fs.unlinkSync(tempAudioPath); - if (error) reject(error); - }) - .on('end', async function () { - fs.unlinkSync(tempAudioPath); - resolve(outputAudio); - }) - .run(); + exec(`${ffmpegPath.path} -i ${tempAudioPath} -vn -ab 128k -ar 44100 -f ipod ${outputAudio} -y`, (error) => { + fs.unlinkSync(tempAudioPath); + this.logger.verbose('Temp audio deleted'); + + if (error) reject(error); + + this.logger.verbose('Audio converted to mp4'); + resolve(outputAudio); + }); }); } @@ -2389,7 +2463,7 @@ export class BaileysStartupService extends WAStartupService { { audio: Buffer.from(audio, 'base64'), ptt: true, - mimetype: 'audio/ogg; codecs=opus', + mimetype: 'audio/mp4', }, { presence: 'recording', delay: data?.options?.delay }, isChatwoot, diff --git a/src/api/services/whatsapp/whatsapp.business.service.ts b/src/api/services/whatsapp/whatsapp.business.service.ts index 89cc20895..1ed2ddcdb 100644 --- a/src/api/services/whatsapp/whatsapp.business.service.ts +++ b/src/api/services/whatsapp/whatsapp.business.service.ts @@ -7,7 +7,6 @@ import { getMIMEType } from 'node-mime-types'; import { ConfigService, Database, WaBusiness } from '../../../config/env.config'; import { BadRequestException, InternalServerErrorException } from '../../../exceptions'; -import { RedisCache } from '../../../libs/redis.client'; import { NumberBusiness } from '../../dto/chat.dto'; import { ContactMessage, @@ -34,8 +33,9 @@ export class BusinessStartupService extends WAStartupService { public readonly configService: ConfigService, public readonly eventEmitter: EventEmitter2, public readonly repository: RepositoryBroker, - public readonly cache: RedisCache, + public readonly cache: CacheService, public readonly chatwootCache: CacheService, + public readonly messagesLostCache: CacheService, ) { super(configService, eventEmitter, repository, chatwootCache); this.logger.verbose('BusinessStartupService initialized'); diff --git a/src/api/integrations/chatwoot/cache/cacheengine.ts b/src/cache/cacheengine.ts similarity index 81% rename from src/api/integrations/chatwoot/cache/cacheengine.ts rename to src/cache/cacheengine.ts index a05e3dae0..258a98c2b 100644 --- a/src/api/integrations/chatwoot/cache/cacheengine.ts +++ b/src/cache/cacheengine.ts @@ -1,5 +1,5 @@ -import { CacheConf, ConfigService } from '../../../../config/env.config'; -import { ICache } from '../../../abstract/abstract.cache'; +import { ICache } from '../api/abstract/abstract.cache'; +import { CacheConf, ConfigService } from '../config/env.config'; import { LocalCache } from './localcache'; import { RedisCache } from './rediscache'; diff --git a/src/api/integrations/chatwoot/cache/localcache.ts b/src/cache/localcache.ts similarity index 78% rename from src/api/integrations/chatwoot/cache/localcache.ts rename to src/cache/localcache.ts index 7bf53e71b..54a51d904 100644 --- a/src/api/integrations/chatwoot/cache/localcache.ts +++ b/src/cache/localcache.ts @@ -1,7 +1,7 @@ import NodeCache from 'node-cache'; -import { CacheConf, CacheConfLocal, ConfigService } from '../../../../config/env.config'; -import { ICache } from '../../../abstract/abstract.cache'; +import { ICache } from '../api/abstract/abstract.cache'; +import { CacheConf, CacheConfLocal, ConfigService } from '../config/env.config'; export class LocalCache implements ICache { private conf: CacheConfLocal; @@ -45,4 +45,17 @@ export class LocalCache implements ICache { buildKey(key: string) { return `${this.module}:${key}`; } + + async hGet() { + console.log('hGet not implemented'); + } + + async hSet() { + console.log('hSet not implemented'); + } + + async hDelete() { + console.log('hDelete not implemented'); + return 0; + } } diff --git a/src/api/integrations/chatwoot/cache/rediscache.client.ts b/src/cache/rediscache.client.ts similarity index 90% rename from src/api/integrations/chatwoot/cache/rediscache.client.ts rename to src/cache/rediscache.client.ts index 0a3ef4fc8..b3f8deadd 100644 --- a/src/api/integrations/chatwoot/cache/rediscache.client.ts +++ b/src/cache/rediscache.client.ts @@ -1,7 +1,7 @@ import { createClient, RedisClientType } from 'redis'; -import { CacheConf, CacheConfRedis, configService } from '../../../../config/env.config'; -import { Logger } from '../../../../config/logger.config'; +import { CacheConf, CacheConfRedis, configService } from '../config/env.config'; +import { Logger } from '../config/logger.config'; class Redis { private logger = new Logger(Redis.name); diff --git a/src/api/integrations/chatwoot/cache/rediscache.ts b/src/cache/rediscache.ts similarity index 67% rename from src/api/integrations/chatwoot/cache/rediscache.ts rename to src/cache/rediscache.ts index 2a08fb3af..6e209ef11 100644 --- a/src/api/integrations/chatwoot/cache/rediscache.ts +++ b/src/cache/rediscache.ts @@ -1,8 +1,9 @@ +import { BufferJSON } from '@whiskeysockets/baileys'; import { RedisClientType } from 'redis'; -import { CacheConf, CacheConfRedis, ConfigService } from '../../../../config/env.config'; -import { Logger } from '../../../../config/logger.config'; -import { ICache } from '../../../abstract/abstract.cache'; +import { ICache } from '../api/abstract/abstract.cache'; +import { CacheConf, CacheConfRedis, ConfigService } from '../config/env.config'; +import { Logger } from '../config/logger.config'; import { redisClient } from './rediscache.client'; export class RedisCache implements ICache { @@ -14,7 +15,6 @@ export class RedisCache implements ICache { this.conf = this.configService.get('CACHE')?.REDIS; this.client = redisClient.getConnection(); } - async get(key: string): Promise { try { return JSON.parse(await this.client.get(this.buildKey(key))); @@ -23,6 +23,20 @@ export class RedisCache implements ICache { } } + async hGet(key: string, field: string) { + try { + const data = await this.client.hGet(this.buildKey(key), field); + + if (data) { + return JSON.parse(data, BufferJSON.reviver); + } + + return null; + } catch (error) { + this.logger.error(error); + } + } + async set(key: string, value: any, ttl?: number) { try { await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value)); @@ -31,6 +45,16 @@ export class RedisCache implements ICache { } } + async hSet(key: string, field: string, value: any) { + try { + const json = JSON.stringify(value, BufferJSON.replacer); + + await this.client.hSet(this.buildKey(key), field, json); + } catch (error) { + this.logger.error(error); + } + } + async has(key: string) { try { return (await this.client.exists(this.buildKey(key))) > 0; @@ -47,6 +71,14 @@ export class RedisCache implements ICache { } } + async hDelete(key: string, field: string) { + try { + return await this.client.hDel(this.buildKey(key), field); + } catch (error) { + this.logger.error(error); + } + } + async deleteAll(appendCriteria?: string) { try { const keys = await this.keys(appendCriteria); diff --git a/src/config/env.config.ts b/src/config/env.config.ts index b3991d084..69b6771a8 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -63,12 +63,6 @@ export type Database = { SAVE_DATA: SaveData; }; -export type Redis = { - ENABLED: boolean; - URI: string; - PREFIX_KEY: string; -}; - export type Rabbitmq = { ENABLED: boolean; MODE: string; // global, single, isolated @@ -153,6 +147,7 @@ export type CacheConfRedis = { URI: string; PREFIX_KEY: string; TTL: number; + SAVE_INSTANCES: boolean; }; export type CacheConfLocal = { ENABLED: boolean; @@ -186,7 +181,6 @@ export interface Env { STORE: StoreConf; CLEAN_STORE: CleanStoreConf; DATABASE: Database; - REDIS: Redis; RABBITMQ: Rabbitmq; SQS: Sqs; WEBSOCKET: Websocket; @@ -280,11 +274,6 @@ export class ConfigService { LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true', }, }, - REDIS: { - ENABLED: process.env?.REDIS_ENABLED === 'true', - URI: process.env.REDIS_URI || '', - PREFIX_KEY: process.env.REDIS_PREFIX_KEY || 'evolution', - }, RABBITMQ: { ENABLED: process.env?.RABBITMQ_ENABLED === 'true', MODE: process.env?.RABBITMQ_MODE || 'isolated', @@ -398,6 +387,7 @@ export class ConfigService { URI: process.env?.CACHE_REDIS_URI || '', PREFIX_KEY: process.env?.CACHE_REDIS_PREFIX_KEY || 'evolution-cache', TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 604800, + SAVE_INSTANCES: process.env?.CACHE_REDIS_SAVE_INSTANCES === 'true', }, LOCAL: { ENABLED: process.env?.CACHE_LOCAL_ENABLED === 'true', diff --git a/src/config/logger.config.ts b/src/config/logger.config.ts index 221ee0983..c52d3ccc6 100644 --- a/src/config/logger.config.ts +++ b/src/config/logger.config.ts @@ -60,10 +60,16 @@ export class Logger { private readonly configService = configService; constructor(private context = 'Logger') {} + private instance = null; + public setContext(value: string) { this.context = value; } + public setInstance(value: string) { + this.instance = value; + } + private console(value: any, type: Type) { const types: Type[] = []; @@ -76,6 +82,8 @@ export class Logger { /*Command.UNDERSCORE +*/ Command.BRIGHT + Level[type], '[Evolution API]', Command.BRIGHT + Color[type], + this.instance ? `[${this.instance}]` : '', + Command.BRIGHT + Color[type], `v${packageJson.version}`, Command.BRIGHT + Color[type], process.pid.toString(), @@ -99,6 +107,7 @@ export class Logger { } else { console.log( '[Evolution API]', + this.instance ? `[${this.instance}]` : '', process.pid.toString(), '-', `${formatDateLog(Date.now())} `, diff --git a/src/dev-env.yml b/src/dev-env.yml index f791b7211..e7f0fae34 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -77,11 +77,6 @@ DATABASE: CONTACTS: false CHATS: false -REDIS: - ENABLED: false - URI: "redis://localhost:6379" - PREFIX_KEY: "evolution" - RABBITMQ: ENABLED: false MODE: "global" @@ -181,8 +176,9 @@ CACHE: REDIS: ENABLED: false URI: "redis://localhost:6379" - PREFIX_KEY: "evolution-cache" + PREFIX_KEY: "evolution" TTL: 604800 + SAVE_INSTANCES: false LOCAL: ENABLED: false TTL: 86400 diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 1b307f5fe..3a7a2d15a 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.2 + version: 1.7.3 contact: name: DavidsonGomes email: contato@agenciadgcode.com diff --git a/src/libs/redis.client.ts b/src/libs/redis.client.ts deleted file mode 100644 index 4b3e19918..000000000 --- a/src/libs/redis.client.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { createClient, RedisClientType } from '@redis/client'; -import { BufferJSON } from '@whiskeysockets/baileys'; - -import { Redis } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -export class RedisCache { - private readonly logger = new Logger(RedisCache.name); - private client: RedisClientType; - private statusConnection = false; - private instanceName: string; - private redisEnv: Redis; - - constructor() { - this.logger.verbose('RedisCache instance created'); - process.on('beforeExit', () => { - this.logger.verbose('RedisCache instance destroyed'); - this.disconnect(); - }); - } - - public set reference(reference: string) { - this.logger.verbose('set reference: ' + reference); - this.instanceName = reference; - } - - public async connect(redisEnv: Redis) { - this.logger.verbose('Connecting to Redis...'); - this.client = createClient({ url: redisEnv.URI }); - this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err)); - - await this.client.connect(); - this.statusConnection = true; - this.redisEnv = redisEnv; - this.logger.verbose(`Connected to ${redisEnv.URI}`); - } - - public async disconnect() { - if (this.statusConnection) { - await this.client.disconnect(); - this.statusConnection = false; - this.logger.verbose('Redis client disconnected'); - } - } - - public async getInstanceKeys(): Promise { - const keys: string[] = []; - try { - this.logger.verbose('Fetching instance keys'); - for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) { - keys.push(key); - } - return keys; - } catch (error) { - this.logger.error('Error fetching instance keys ' + error); - throw error; - } - } - - public async keyExists(key?: string) { - try { - const keys = await this.getInstanceKeys(); - const targetKey = key || this.instanceName; - this.logger.verbose('keyExists: ' + targetKey); - return keys.includes(targetKey); - } catch (error) { - return false; - } - } - - public async setData(field: string, data: any) { - try { - this.logger.verbose('setData: ' + field); - const json = JSON.stringify(data, BufferJSON.replacer); - await this.client.hSet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field, json); - return true; - } catch (error) { - this.logger.error(error); - return false; - } - } - - public async getData(field: string): Promise { - try { - this.logger.verbose('getData: ' + field); - const data = await this.client.hGet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - - if (data) { - this.logger.verbose('getData: ' + field + ' success'); - return JSON.parse(data, BufferJSON.reviver); - } - - this.logger.verbose('getData: ' + field + ' not found'); - return null; - } catch (error) { - this.logger.error(error); - return null; - } - } - - public async removeData(field: string): Promise { - try { - this.logger.verbose('removeData: ' + field); - await this.client.hDel(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - return true; - } catch (error) { - this.logger.error(error); - return false; - } - } - - public async delAll(hash?: string): Promise { - try { - const targetHash = hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName; - this.logger.verbose('instance delAll: ' + targetHash); - const result = await this.client.del(targetHash); - return !!result; - } catch (error) { - this.logger.error(error); - return false; - } - } -} diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index 74a76724f..8e23181dd 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -4,7 +4,7 @@ import path from 'path'; import { ConfigService, Language } from '../config/env.config'; -const languages = ['en', 'pt-BR']; +const languages = ['en', 'pt-BR', 'es']; const translationsPath = path.join(__dirname, 'translations'); const configService: ConfigService = new ConfigService(); diff --git a/src/utils/translations/es.json b/src/utils/translations/es.json new file mode 100644 index 000000000..287840639 --- /dev/null +++ b/src/utils/translations/es.json @@ -0,0 +1,26 @@ +{ + "qrgeneratedsuccesfully": "Código QR generado exitosamente!", + "scanqr": "Escanea este código QR en los próximos 40 segundos.", + "qrlimitreached": "🚨 Se alcanzó el límite de generación de QRCode. Para generar un nuevo QRCode, envíe el mensaje 'init' nuevamente.", + "numbernotinwhatsapp": "⚠️ El mensaje no fue enviado porque el contacto no es un número de Whatsapp válido..", + "cw.inbox.connected": "🚀 ¡Conexión establecida exitosamente!", + "cw.inbox.disconnect": "🚨 Instancia *{{inboxName}}* desconectado de Whatsapp.", + "cw.inbox.alreadyConnected": "🚨 La instancia {{inboxName}} está conectada.", + "cw.inbox.clearCache": "✅ Caché de la instancia {{inboxName}} borrada.", + "cw.inbox.notFound": "⚠️ Instancia {{inboxName}} no encontrada.", + "cw.inbox.status": "⚠️ Estado de la instancia {{inboxName}}: *{{state}}*.", + "cw.import.startImport": "💬 Empezando a importar mensajes. Espere por favor...", + "cw.import.importingMessages": "💬 Importando mensajes. mas un momento...", + "cw.import.messagesImported": "💬 {{totalMessagesImported}} mensajes importados. Actualiza la página para ver los nuevos mensajes..", + "cw.import.messagesException": "⚠️ Algo salió mal al importar mensajes..", + "cw.locationMessage.location": "Ubicación", + "cw.locationMessage.latitude": "Latitude", + "cw.locationMessage.longitude": "Longitude", + "cw.locationMessage.locationName": "Nombre", + "cw.locationMessage.locationAddress": "Direccion", + "cw.locationMessage.locationUrl": "URL", + "cw.contactMessage.contact": "Contacto", + "cw.contactMessage.name": "Nombre", + "cw.contactMessage.number": "Numero", + "cw.message.edited": "Mensaje editado" +} \ No newline at end of file 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 eb88d678f..66bb89ea3 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -6,10 +6,13 @@ import { SignalDataTypeMap, } from '@whiskeysockets/baileys'; +import { CacheService } from '../api/services/cache.service'; import { Logger } from '../config/logger.config'; -import { RedisCache } from '../libs/redis.client'; -export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ +export async function useMultiFileAuthStateRedisDb( + instanceName: string, + cache: CacheService, +): Promise<{ state: AuthenticationState; saveCreds: () => Promise; }> { @@ -17,7 +20,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const writeData = async (data: any, key: string): Promise => { try { - return await cache.setData(key, data); + return await cache.hSet(instanceName, key, data); } catch (error) { return logger.error({ localError: 'writeData', error }); } @@ -25,7 +28,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const readData = async (key: string): Promise => { try { - return await cache.getData(key); + return await cache.hGet(instanceName, key); } catch (error) { logger.error({ localError: 'readData', error }); return; @@ -34,7 +37,7 @@ export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ const removeData = async (key: string) => { try { - return await cache.removeData(key); + return await cache.hDelete(instanceName, key); } catch (error) { logger.error({ readData: 'removeData', error }); }