Merge pull request #1126 from jesus-chacon/update_chats

Fix: cuid security deprecation, update libs, lint and improve chat DB update
This commit is contained in:
Davidson Gomes 2025-01-07 13:06:23 -03:00 committed by GitHub
commit ca451bfacc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 12668 additions and 126 deletions

1
.gitignore vendored
View File

@ -21,7 +21,6 @@ lerna-debug.log*
# Package # Package
/yarn.lock /yarn.lock
/pnpm-lock.yaml /pnpm-lock.yaml
/package-lock.json
# IDEs # IDEs
.vscode/* .vscode/*

12517
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -49,73 +49,72 @@
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme", "homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
"dependencies": { "dependencies": {
"@adiwajshing/keyed-db": "^0.2.4", "@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-sqs": "^3.569.0", "@aws-sdk/client-sqs": "^3.723.0",
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.16", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
"@sentry/node": "^8.28.0", "@sentry/node": "^8.47.0",
"amqplib": "^0.10.3", "amqplib": "^0.10.5",
"axios": "^1.6.5", "axios": "^1.7.9",
"baileys": "github:EvolutionAPI/Baileys", "baileys": "github:EvolutionAPI/Baileys",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"compression": "^1.7.4", "compression": "^1.7.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"cuid": "^3.0.0", "dayjs": "^1.11.13",
"dayjs": "^1.11.7", "dotenv": "^16.4.7",
"dotenv": "^16.4.5",
"eventemitter2": "^6.4.9", "eventemitter2": "^6.4.9",
"express": "^4.18.2", "express": "^4.21.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.3",
"form-data": "^4.0.0", "form-data": "^4.0.1",
"https-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6",
"i18next": "^23.7.19", "i18next": "^23.7.19",
"jimp": "^0.16.13", "jimp": "^0.16.13",
"json-schema": "^0.4.0", "json-schema": "^0.4.0",
"jsonschema": "^1.4.1", "jsonschema": "^1.4.1",
"link-preview-js": "^3.0.4", "link-preview-js": "^3.0.13",
"long": "^5.2.3", "long": "^5.2.3",
"mediainfo.js": "^0.3.2", "mediainfo.js": "^0.3.4",
"mime": "^3.0.0", "mime": "^4.0.6",
"minio": "^8.0.1", "minio": "^8.0.3",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"node-cron": "^3.0.3", "node-cron": "^3.0.3",
"openai": "^4.52.7", "openai": "^4.77.3",
"pg": "^8.11.3", "pg": "^8.13.1",
"pino": "^8.11.0", "pino": "^8.11.0",
"prisma": "^6.1.0", "prisma": "^6.1.0",
"pusher": "^5.2.0", "pusher": "^5.2.0",
"qrcode": "^1.5.1", "qrcode": "^1.5.4",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"redis": "^4.6.5", "redis": "^4.7.0",
"sharp": "^0.32.2", "sharp": "^0.32.6",
"socket.io": "^4.7.1", "socket.io": "^4.8.1",
"tsup": "^8.2.4", "tsup": "^8.3.5"
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.5",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.17",
"@types/express": "^4.17.17", "@types/express": "^4.17.18",
"@types/json-schema": "^7.0.15", "@types/json-schema": "^7.0.15",
"@types/mime": "3.0.0", "@types/mime": "4.0.0",
"@types/node": "^18.15.11", "@types/node": "^22.10.5",
"@types/node-cron": "^3.0.11", "@types/node-cron": "^3.0.11",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.5",
"@types/qrcode-terminal": "^0.12.0", "@types/qrcode-terminal": "^0.12.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.45.0", "eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^2.8.8", "prettier": "^3.4.2",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.5.4" "typescript": "^5.7.2"
} }
} }

View File

@ -10,7 +10,10 @@ import axios from 'axios';
const logger = new Logger('ProxyController'); const logger = new Logger('ProxyController');
export class ProxyController { export class ProxyController {
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {} constructor(
private readonly proxyService: ProxyService,
private readonly waMonitor: WAMonitoringService,
) {}
public async createProxy(instance: InstanceDto, data: ProxyDto) { public async createProxy(instance: InstanceDto, data: ProxyDto) {
if (!this.waMonitor.waInstances[instance.instanceName]) { if (!this.waMonitor.waInstances[instance.instanceName]) {

View File

@ -71,10 +71,7 @@ export class BusinessStartupService extends ChannelStartupService {
} }
private isMediaMessage(message: any) { private isMediaMessage(message: any) {
return message.document || return message.document || message.image || message.audio || message.video;
message.image ||
message.audio ||
message.video
} }
private async post(message: any, params: string) { private async post(message: any, params: string) {
@ -333,13 +330,17 @@ export class BusinessStartupService extends ChannelStartupService {
const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' }); const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
const mediaType = message.messages[0].document let mediaType;
? 'document'
: message.messages[0].image if (message.messages[0].document) {
? 'image' mediaType = 'document';
: message.messages[0].audio } else if (message.messages[0].image) {
? 'audio' mediaType = 'image';
: 'video'; } else if (message.messages[0].audio) {
mediaType = 'audio';
} else {
mediaType = 'video';
}
const mimetype = result.data?.mime_type || result.headers['content-type']; const mimetype = result.data?.mime_type || result.headers['content-type'];
@ -479,7 +480,7 @@ export class BusinessStartupService extends ChannelStartupService {
message: { message: {
mediaUrl: messageRaw.message.mediaUrl, mediaUrl: messageRaw.message.mediaUrl,
...messageRaw, ...messageRaw,
} },
}, },
() => {}, () => {},
); );
@ -800,7 +801,7 @@ export class BusinessStartupService extends ChannelStartupService {
} }
if (message['media']) { if (message['media']) {
const isImage = message['mimetype']?.startsWith('image/'); const isImage = message['mimetype']?.startsWith('image/');
content = { content = {
messaging_product: 'whatsapp', messaging_product: 'whatsapp',
recipient_type: 'individual', recipient_type: 'individual',
@ -815,7 +816,7 @@ export class BusinessStartupService extends ChannelStartupService {
}; };
quoted ? (content.context = { message_id: quoted.id }) : content; quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages'); return await this.post(content, 'messages');
} }
if (message['audio']) { if (message['audio']) {
content = { content = {
messaging_product: 'whatsapp', messaging_product: 'whatsapp',
@ -1103,11 +1104,10 @@ export class BusinessStartupService extends ChannelStartupService {
if (file?.buffer) { if (file?.buffer) {
mediaData.audio = file.buffer.toString('base64'); mediaData.audio = file.buffer.toString('base64');
} } else if (isURL(mediaData.audio)) {
else if(isURL(mediaData.audio)){ // DO NOTHING
mediaData.audio = mediaData.audio // mediaData.audio = mediaData.audio;
} } else {
else {
console.error('El archivo no tiene buffer o file es undefined'); console.error('El archivo no tiene buffer o file es undefined');
throw new Error('File or buffer is undefined'); throw new Error('File or buffer is undefined');
} }

View File

@ -76,6 +76,7 @@ import {
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions';
import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import ffmpegPath from '@ffmpeg-installer/ffmpeg';
import { Boom } from '@hapi/boom'; import { Boom } from '@hapi/boom';
import { createId as cuid } from '@paralleldrive/cuid2';
import { Instance } from '@prisma/client'; import { Instance } from '@prisma/client';
import { makeProxyAgent } from '@utils/makeProxyAgent'; import { makeProxyAgent } from '@utils/makeProxyAgent';
import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache'; import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache';
@ -125,7 +126,6 @@ import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { isArray, isBase64, isURL } from 'class-validator'; import { isArray, isBase64, isURL } from 'class-validator';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import cuid from 'cuid';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import ffmpeg from 'fluent-ffmpeg'; import ffmpeg from 'fluent-ffmpeg';
import FormData from 'form-data'; import FormData from 'form-data';
@ -1136,29 +1136,25 @@ export class BaileysStartupService extends ChannelStartupService {
} }
const existingChat = await this.prismaRepository.chat.findFirst({ const existingChat = await this.prismaRepository.chat.findFirst({
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid }, where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
select: { id: true, name: true },
}); });
if (existingChat) { if (
const chatToInsert = { existingChat &&
remoteJid: received.key.remoteJid, received.pushName &&
instanceId: this.instanceId, existingChat.name !== received.pushName &&
name: received.pushName || '', received.pushName.trim().length > 0
unreadMessages: 0, ) {
}; this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]);
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) { if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
try { try {
await this.prismaRepository.chat.update({ await this.prismaRepository.chat.update({
where: { where: { id: existingChat.id },
id: existingChat.id, data: { name: received.pushName },
}, });
data: chatToInsert, } catch (error) {
}); console.log(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`);
} }
catch(error){
console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`);
}
} }
} }
@ -1494,13 +1490,12 @@ export class BaileysStartupService extends ChannelStartupService {
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) { if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
try { try {
await this.prismaRepository.chat.update({ await this.prismaRepository.chat.update({
where: { where: {
id: existingChat.id, id: existingChat.id,
}, },
data: chatToInsert, data: chatToInsert,
}); });
} } catch (error) {
catch(error){
console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`); console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`);
} }
} }

View File

@ -66,7 +66,6 @@ export class WebhookController extends EventController implements EventControlle
local, local,
}: EmitData): Promise<void> { }: EmitData): Promise<void> {
const instance = (await this.get(instanceName)) as wa.LocalWebHook; const instance = (await this.get(instanceName)) as wa.LocalWebHook;
const webhookConfig = configService.get<Webhook>('WEBHOOK'); const webhookConfig = configService.get<Webhook>('WEBHOOK');
const webhookLocal = instance?.events; const webhookLocal = instance?.events;
@ -86,7 +85,7 @@ export class WebhookController extends EventController implements EventControlle
apikey: apiKey, apikey: apiKey,
}; };
if (local && !instance || !instance?.enabled) { if ((local && !instance) || !instance?.enabled) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) { if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
let baseURL: string; let baseURL: string;

View File

@ -8,7 +8,10 @@ import { instanceSchema, webhookSchema } from '@validate/validate.schema';
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
export class WebhookRouter extends RouterBroker { export class WebhookRouter extends RouterBroker {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { constructor(
readonly configService: ConfigService,
...guards: RequestHandler[]
) {
super(); super();
this.router this.router
.post(this.routerPath('set'), ...guards, async (req, res) => { .post(this.routerPath('set'), ...guards, async (req, res) => {

View File

@ -8,7 +8,10 @@ import { RequestHandler, Router } from 'express';
import { HttpStatus } from './index.router'; import { HttpStatus } from './index.router';
export class InstanceRouter extends RouterBroker { export class InstanceRouter extends RouterBroker {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { constructor(
readonly configService: ConfigService,
...guards: RequestHandler[]
) {
super(); super();
this.router this.router
.post('/create', ...guards, async (req, res) => { .post('/create', ...guards, async (req, res) => {

View File

@ -9,7 +9,10 @@ import { RequestHandler, Router } from 'express';
import { HttpStatus } from './index.router'; import { HttpStatus } from './index.router';
export class TemplateRouter extends RouterBroker { export class TemplateRouter extends RouterBroker {
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { constructor(
readonly configService: ConfigService,
...guards: RequestHandler[]
) {
super(); super();
this.router this.router
.post(this.routerPath('create'), ...guards, async (req, res) => { .post(this.routerPath('create'), ...guards, async (req, res) => {

View File

@ -42,20 +42,23 @@ export class WAMonitoringService {
public delInstanceTime(instance: string) { public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE'); const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) { if (typeof time === 'number' && time > 0) {
setTimeout(async () => { setTimeout(
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { async () => {
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') { if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) { if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) {
this.waInstances[instance]?.client?.ws?.close(); await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.end(undefined); this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined);
}
this.eventEmitter.emit('remove.instance', instance, 'inner');
} else {
this.eventEmitter.emit('remove.instance', instance, 'inner');
} }
this.eventEmitter.emit('remove.instance', instance, 'inner');
} else {
this.eventEmitter.emit('remove.instance', instance, 'inner');
} }
} },
}, 1000 * 60 * time); 1000 * 60 * time,
);
} }
} }
@ -72,14 +75,15 @@ export class WAMonitoringService {
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME; const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
const where = instanceNames && instanceNames.length > 0 const where =
? { instanceNames && instanceNames.length > 0
name: { ? {
in: instanceNames, name: {
}, in: instanceNames,
clientName, },
} clientName,
: { clientName }; }
: { clientName };
const instances = await this.prismaRepository.instance.findMany({ const instances = await this.prismaRepository.instance.findMany({
where, where,
@ -218,7 +222,7 @@ export class WAMonitoringService {
id: data.instanceId, id: data.instanceId,
name: data.instanceName, name: data.instanceName,
connectionStatus: connectionStatus:
data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : data.status ?? 'open', data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : (data.status ?? 'open'),
number: data.number, number: data.number,
integration: data.integration || Integration.WHATSAPP_BAILEYS, integration: data.integration || Integration.WHATSAPP_BAILEYS,
token: data.hash, token: data.hash,

View File

@ -131,7 +131,14 @@ export declare namespace wa {
export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';
} }
export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage', 'ptvMessage']; export const TypeMediaMessage = [
'imageMessage',
'documentMessage',
'audioMessage',
'videoMessage',
'stickerMessage',
'ptvMessage',
];
export const MessageSubtype = [ export const MessageSubtype = [
'ephemeralMessage', 'ephemeralMessage',

View File

@ -10,7 +10,10 @@ const logger = new Logger('CacheEngine');
export class CacheEngine { export class CacheEngine {
private engine: ICache; private engine: ICache;
constructor(private readonly configService: ConfigService, module: string) { constructor(
private readonly configService: ConfigService,
module: string,
) {
const cacheConf = configService.get<CacheConf>('CACHE'); const cacheConf = configService.get<CacheConf>('CACHE');
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') { if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {

View File

@ -9,7 +9,10 @@ export class LocalCache implements ICache {
private conf: CacheConfLocal; private conf: CacheConfLocal;
static localCache = new NodeCache(); static localCache = new NodeCache();
constructor(private readonly configService: ConfigService, private readonly module: string) { constructor(
private readonly configService: ConfigService,
private readonly module: string,
) {
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL; this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
} }

View File

@ -11,7 +11,10 @@ export class RedisCache implements ICache {
private client: RedisClientType; private client: RedisClientType;
private conf: CacheConfRedis; private conf: CacheConfRedis;
constructor(private readonly configService: ConfigService, private readonly module: string) { constructor(
private readonly configService: ConfigService,
private readonly module: string,
) {
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS; this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
this.client = redisClient.getConnection(); this.client = redisClient.getConnection();
} }

View File

@ -17,13 +17,14 @@ const getTypeMessage = (msg: any) => {
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url, msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
listResponseMessage: msg?.message?.listResponseMessage?.title, listResponseMessage: msg?.message?.listResponseMessage?.title,
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
templateButtonReplyMessage: msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId, templateButtonReplyMessage:
msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId,
// Medias // Medias
audioMessage: msg?.message?.speechToText audioMessage: msg?.message?.speechToText
? msg?.message?.speechToText ? msg?.message?.speechToText
: msg?.message?.audioMessage : msg?.message?.audioMessage
? `audioMessage|${mediaId}` ? `audioMessage|${mediaId}`
: undefined, : undefined,
imageMessage: msg?.message?.imageMessage imageMessage: msg?.message?.imageMessage
? `imageMessage|${mediaId}${msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : ''}` ? `imageMessage|${mediaId}${msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : ''}`
: undefined, : undefined,

View File

@ -1,12 +1,12 @@
import * as Sentry from "@sentry/node"; import * as Sentry from '@sentry/node';
const dsn = process.env.SENTRY_DSN; const dsn = process.env.SENTRY_DSN;
if (dsn) { if (dsn) {
Sentry.init({ Sentry.init({
dsn: dsn, dsn: dsn,
environment: process.env.NODE_ENV || 'development', environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
profilesSampleRate: 1.0, profilesSampleRate: 1.0,
}); });
} }