Merge pull request #2158 from KokeroO/develop

fix: Integration Chatwoot and Baileys services
This commit is contained in:
Davidson Gomes 2025-11-07 14:26:32 -03:00 committed by GitHub
commit 400b6291a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 628 additions and 600 deletions

View File

@ -59,7 +59,7 @@ body:
value: | value: |
- OS: [e.g. Ubuntu 20.04, Windows 10, macOS 12.0] - OS: [e.g. Ubuntu 20.04, Windows 10, macOS 12.0]
- Node.js version: [e.g. 18.17.0] - Node.js version: [e.g. 18.17.0]
- Evolution API version: [e.g. 2.3.6] - Evolution API version: [e.g. 2.3.7]
- Database: [e.g. PostgreSQL 14, MySQL 8.0] - Database: [e.g. PostgreSQL 14, MySQL 8.0]
- Connection type: [e.g. Baileys, WhatsApp Business API] - Connection type: [e.g. Baileys, WhatsApp Business API]
validations: validations:

View File

@ -2,7 +2,7 @@ version: "3.7"
services: services:
evolution_v2: evolution_v2:
image: evoapicloud/evolution-api:v2.3.6 image: evoapicloud/evolution-api:v2.3.7
volumes: volumes:
- evolution_instances:/evolution/instances - evolution_instances:/evolution/instances
networks: networks:

893
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "evolution-api", "name": "evolution-api",
"version": "2.3.6", "version": "2.3.7",
"description": "Rest api for communication with WhatsApp", "description": "Rest api for communication with WhatsApp",
"main": "./dist/main.js", "main": "./dist/main.js",
"type": "commonjs", "type": "commonjs",
@ -96,6 +96,7 @@
"jsonschema": "^1.4.1", "jsonschema": "^1.4.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"kafkajs": "^2.2.4", "kafkajs": "^2.2.4",
"libphonenumber-js": "^1.12.25",
"link-preview-js": "^3.0.13", "link-preview-js": "^3.0.13",
"long": "^5.2.3", "long": "^5.2.3",
"mediainfo.js": "^0.3.4", "mediainfo.js": "^0.3.4",

View File

@ -92,6 +92,15 @@ export class InstanceController {
instanceId: instanceId, instanceId: instanceId,
}); });
const instanceDto: InstanceDto = {
instanceName: instance.instanceName,
instanceId: instance.instanceId,
connectionStatus:
typeof instance.connectionStatus === 'string'
? instance.connectionStatus
: instance.connectionStatus?.state || 'unknown',
};
if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) { if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) {
const testProxy = await this.proxyService.testProxy({ const testProxy = await this.proxyService.testProxy({
host: instanceData.proxyHost, host: instanceData.proxyHost,
@ -103,8 +112,7 @@ export class InstanceController {
if (!testProxy) { if (!testProxy) {
throw new BadRequestException('Invalid proxy'); throw new BadRequestException('Invalid proxy');
} }
await this.proxyService.createProxy(instanceDto, {
await this.proxyService.createProxy(instance, {
enabled: true, enabled: true,
host: instanceData.proxyHost, host: instanceData.proxyHost,
port: instanceData.proxyPort, port: instanceData.proxyPort,
@ -125,7 +133,7 @@ export class InstanceController {
wavoipToken: instanceData.wavoipToken || '', wavoipToken: instanceData.wavoipToken || '',
}; };
await this.settingsService.create(instance, settings); await this.settingsService.create(instanceDto, settings);
let webhookWaBusiness = null, let webhookWaBusiness = null,
accessTokenWaBusiness = ''; accessTokenWaBusiness = '';
@ -155,7 +163,10 @@ export class InstanceController {
integration: instanceData.integration, integration: instanceData.integration,
webhookWaBusiness, webhookWaBusiness,
accessTokenWaBusiness, accessTokenWaBusiness,
status: instance.connectionStatus.state, status:
typeof instance.connectionStatus === 'string'
? instance.connectionStatus
: instance.connectionStatus?.state || 'unknown',
}, },
hash, hash,
webhook: { webhook: {
@ -217,7 +228,7 @@ export class InstanceController {
const urlServer = this.configService.get<HttpServer>('SERVER').URL; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
try { try {
this.chatwootService.create(instance, { this.chatwootService.create(instanceDto, {
enabled: true, enabled: true,
accountId: instanceData.chatwootAccountId, accountId: instanceData.chatwootAccountId,
token: instanceData.chatwootToken, token: instanceData.chatwootToken,
@ -246,7 +257,10 @@ export class InstanceController {
integration: instanceData.integration, integration: instanceData.integration,
webhookWaBusiness, webhookWaBusiness,
accessTokenWaBusiness, accessTokenWaBusiness,
status: instance.connectionStatus.state, status:
typeof instance.connectionStatus === 'string'
? instance.connectionStatus
: instance.connectionStatus?.state || 'unknown',
}, },
hash, hash,
webhook: { webhook: {
@ -338,20 +352,38 @@ export class InstanceController {
throw new BadRequestException('The "' + instanceName + '" instance does not exist'); throw new BadRequestException('The "' + instanceName + '" instance does not exist');
} }
if (state == 'close') { if (state === 'close') {
throw new BadRequestException('The "' + instanceName + '" instance is not connected'); throw new BadRequestException('The "' + instanceName + '" instance is not connected');
} else if (state == 'open') { }
this.logger.info(`Restarting instance: ${instanceName}`);
if (typeof instance.restart === 'function') {
await instance.restart();
// Wait a bit for the reconnection to be established
await new Promise((r) => setTimeout(r, 2000));
return {
instance: {
instanceName: instanceName,
status: instance.connectionStatus?.state || 'connecting',
},
};
}
// Fallback for Baileys (uses different mechanism)
if (state === 'open' || state === 'connecting') {
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) instance.clearCacheChatwoot(); if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) instance.clearCacheChatwoot();
this.logger.info('restarting instance' + instanceName);
instance.client?.ws?.close();
instance.client?.end(new Error('restart'));
return await this.connectToWhatsapp({ instanceName });
} else if (state == 'connecting') {
instance.client?.ws?.close(); instance.client?.ws?.close();
instance.client?.end(new Error('restart')); instance.client?.end(new Error('restart'));
return await this.connectToWhatsapp({ instanceName }); return await this.connectToWhatsapp({ instanceName });
} }
return {
instance: {
instanceName: instanceName,
status: state,
},
};
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
return { error: true, message: error.toString() }; return { error: true, message: error.toString() };
@ -409,7 +441,7 @@ export class InstanceController {
} }
try { try {
this.waMonitor.waInstances[instanceName]?.logoutInstance(); await this.waMonitor.waInstances[instanceName]?.logoutInstance();
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
} catch (error) { } catch (error) {

View File

@ -12,6 +12,7 @@ export class InstanceDto extends IntegrationDto {
token?: string; token?: string;
status?: string; status?: string;
ownerJid?: string; ownerJid?: string;
connectionStatus?: string;
profileName?: string; profileName?: string;
profilePicUrl?: string; profilePicUrl?: string;
// settings // settings

View File

@ -82,7 +82,7 @@ import { createId as cuid } from '@paralleldrive/cuid2';
import { Instance, Message } from '@prisma/client'; import { Instance, Message } from '@prisma/client';
import { createJid } from '@utils/createJid'; import { createJid } from '@utils/createJid';
import { fetchLatestWaWebVersion } from '@utils/fetchLatestWaWebVersion'; import { fetchLatestWaWebVersion } from '@utils/fetchLatestWaWebVersion';
import {makeProxyAgent, makeProxyAgentUndici} from '@utils/makeProxyAgent'; import { makeProxyAgent, makeProxyAgentUndici } from '@utils/makeProxyAgent';
import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache'; import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache';
import { status } from '@utils/renderStatus'; import { status } from '@utils/renderStatus';
import { sendTelemetry } from '@utils/sendTelemetry'; import { sendTelemetry } from '@utils/sendTelemetry';
@ -569,15 +569,6 @@ export class BaileysStartupService extends ChannelStartupService {
const version = baileysVersion.version; const version = baileysVersion.version;
const log = `Baileys version: ${version.join('.')}`; const log = `Baileys version: ${version.join('.')}`;
// if (session.VERSION) {
// version = session.VERSION.split('.');
// log = `Baileys version env: ${version}`;
// } else {
// const baileysVersion = await fetchLatestWaWebVersion({});
// version = baileysVersion.version;
// log = `Baileys version: ${version}`;
// }
this.logger.info(log); this.logger.info(log);
this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`); this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`);
@ -1130,16 +1121,6 @@ export class BaileysStartupService extends ChannelStartupService {
} }
} }
const messageKey = `${this.instance.id}_${received.key.id}`;
const cached = await this.baileysCache.get(messageKey);
if (cached && !editedMessage && !requestId) {
this.logger.info(`Message duplicated ignored: ${received.key.id}`);
continue;
}
await this.baileysCache.set(messageKey, true, this.MESSAGE_CACHE_TTL_SECONDS);
if ( if (
(type !== 'notify' && type !== 'append') || (type !== 'notify' && type !== 'append') ||
editedMessage || editedMessage ||
@ -1442,7 +1423,7 @@ export class BaileysStartupService extends ChannelStartupService {
const cached = await this.baileysCache.get(updateKey); const cached = await this.baileysCache.get(updateKey);
if (cached) { if (cached) {
this.logger.info(`Message duplicated ignored [avoid deadlock]: ${updateKey}`); this.logger.info(`Update Message duplicated ignored [avoid deadlock]: ${updateKey}`);
continue; continue;
} }

View File

@ -1,10 +1,6 @@
import { InstanceDto } from '@api/dto/instance.dto'; import { InstanceDto } from '@api/dto/instance.dto';
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto'; import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service'; import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
import { PrismaRepository } from '@api/repository/repository.service';
import { waMonitor } from '@api/server.module';
import { CacheService } from '@api/services/cache.service';
import { CacheEngine } from '@cache/cacheengine';
import { Chatwoot, ConfigService, HttpServer } from '@config/env.config'; import { Chatwoot, ConfigService, HttpServer } from '@config/env.config';
import { BadRequestException } from '@exceptions'; import { BadRequestException } from '@exceptions';
import { isURL } from 'class-validator'; import { isURL } from 'class-validator';
@ -13,7 +9,6 @@ export class ChatwootController {
constructor( constructor(
private readonly chatwootService: ChatwootService, private readonly chatwootService: ChatwootService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly prismaRepository: PrismaRepository,
) {} ) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) { public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
@ -84,9 +79,6 @@ export class ChatwootController {
public async receiveWebhook(instance: InstanceDto, data: any) { public async receiveWebhook(instance: InstanceDto, data: any) {
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled'); if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine()); return this.chatwootService.receiveWebhook(instance, data);
const chatwootService = new ChatwootService(waMonitor, this.configService, this.prismaRepository, chatwootCache);
return chatwootService.receiveWebhook(instance, data);
} }
} }

View File

@ -27,6 +27,7 @@ import { WAMessageContent, WAMessageKey } from 'baileys';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import FormData from 'form-data'; import FormData from 'form-data';
import { Jimp, JimpMime } from 'jimp'; import { Jimp, JimpMime } from 'jimp';
import { parsePhoneNumberFromString } from 'libphonenumber-js';
import Long from 'long'; import Long from 'long';
import mimeTypes from 'mime-types'; import mimeTypes from 'mime-types';
import path from 'path'; import path from 'path';
@ -589,7 +590,7 @@ export class ChatwootService {
`Identifier needs update: (contact.identifier: ${contact.identifier}, phoneNumber: ${phoneNumber}, body.key.remoteJidAlt: ${remoteJid}`, `Identifier needs update: (contact.identifier: ${contact.identifier}, phoneNumber: ${phoneNumber}, body.key.remoteJidAlt: ${remoteJid}`,
); );
const updateContact = await this.updateContact(instance, contact.id, { const updateContact = await this.updateContact(instance, contact.id, {
identifier: remoteJid, identifier: phoneNumber,
phone_number: `+${phoneNumber.split('@')[0]}`, phone_number: `+${phoneNumber.split('@')[0]}`,
}); });
@ -611,13 +612,15 @@ export class ChatwootService {
if (await this.cache.has(cacheKey)) { if (await this.cache.has(cacheKey)) {
const conversationId = (await this.cache.get(cacheKey)) as number; const conversationId = (await this.cache.get(cacheKey)) as number;
this.logger.verbose(`Found conversation to: ${phoneNumber}, conversation ID: ${conversationId}`); this.logger.verbose(`Found conversation to: ${phoneNumber}, conversation ID: ${conversationId}`);
let conversationExists: conversation | boolean; let conversationExists: any;
try { try {
conversationExists = await client.conversations.get({ conversationExists = await client.conversations.get({
accountId: this.provider.accountId, accountId: this.provider.accountId,
conversationId: conversationId, conversationId: conversationId,
}); });
this.logger.verbose(`Conversation exists: ${JSON.stringify(conversationExists)}`); this.logger.verbose(
`Conversation exists: ID: ${conversationExists.id} - Name: ${conversationExists.meta.sender.name} - Identifier: ${conversationExists.meta.sender.identifier}`,
);
} catch (error) { } catch (error) {
this.logger.error(`Error getting conversation: ${error}`); this.logger.error(`Error getting conversation: ${error}`);
conversationExists = false; conversationExists = false;
@ -669,7 +672,7 @@ export class ChatwootService {
if (isGroup) { if (isGroup) {
this.logger.verbose(`Processing group conversation`); this.logger.verbose(`Processing group conversation`);
const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId); const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId);
this.logger.verbose(`Group metadata: ${JSON.stringify(group)}`); this.logger.verbose(`Group metadata: JID:${group.JID} - Subject:${group?.subject || group?.Name}`);
const participantJid = isLid && !body.key.fromMe ? body.key.participantAlt : body.key.participant; const participantJid = isLid && !body.key.fromMe ? body.key.participantAlt : body.key.participant;
nameContact = `${group.subject} (GROUP)`; nameContact = `${group.subject} (GROUP)`;
@ -680,9 +683,11 @@ export class ChatwootService {
this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`); this.logger.verbose(`Participant profile picture URL: ${JSON.stringify(picture_url)}`);
const findParticipant = await this.findContact(instance, participantJid.split('@')[0]); const findParticipant = await this.findContact(instance, participantJid.split('@')[0]);
this.logger.verbose(`Found participant: ${JSON.stringify(findParticipant)}`);
if (findParticipant) { if (findParticipant) {
this.logger.verbose(
`Found participant: ID:${findParticipant.id} - Name: ${findParticipant.name} - identifier: ${findParticipant.identifier}`,
);
if (!findParticipant.name || findParticipant.name === chatId) { if (!findParticipant.name || findParticipant.name === chatId) {
await this.updateContact(instance, findParticipant.id, { await this.updateContact(instance, findParticipant.id, {
name: body.pushName, name: body.pushName,
@ -692,7 +697,7 @@ export class ChatwootService {
} else { } else {
await this.createContact( await this.createContact(
instance, instance,
participantJid.split('@')[0], participantJid.split('@')[0].split(':')[0],
filterInbox.id, filterInbox.id,
false, false,
body.pushName, body.pushName,
@ -709,20 +714,13 @@ export class ChatwootService {
let contact = await this.findContact(instance, chatId); let contact = await this.findContact(instance, chatId);
if (contact) { if (contact) {
this.logger.verbose(`Found contact: ${JSON.stringify(contact)}`); this.logger.verbose(`Found contact: ID:${contact.id} - Name:${contact.name}`);
if (!body.key.fromMe) { if (!body.key.fromMe) {
const waProfilePictureFile = const waProfilePictureFile =
picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || ''; picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || '';
const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || ''; const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || '';
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile; const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
const nameNeedsUpdate = const nameNeedsUpdate = !contact.name || contact.name === chatId;
!contact.name ||
contact.name === chatId ||
(`+${chatId}`.startsWith('+55')
? this.getNumbers(`+${chatId}`).some(
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
)
: false);
this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`); this.logger.verbose(`Picture needs update: ${pictureNeedsUpdate}`);
this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`); this.logger.verbose(`Name needs update: ${nameNeedsUpdate}`);
if (pictureNeedsUpdate || nameNeedsUpdate) { if (pictureNeedsUpdate || nameNeedsUpdate) {
@ -741,7 +739,7 @@ export class ChatwootService {
isGroup, isGroup,
nameContact, nameContact,
picture_url.profilePictureUrl || null, picture_url.profilePictureUrl || null,
remoteJid, phoneNumber,
); );
} }
@ -757,7 +755,6 @@ export class ChatwootService {
accountId: this.provider.accountId, accountId: this.provider.accountId,
id: contactId, id: contactId,
})) as any; })) as any;
this.logger.verbose(`Contact conversations: ${JSON.stringify(contactConversations)}`);
if (!contactConversations || !contactConversations.payload) { if (!contactConversations || !contactConversations.payload) {
this.logger.error(`No conversations found or payload is undefined`); this.logger.error(`No conversations found or payload is undefined`);
@ -769,7 +766,9 @@ export class ChatwootService {
); );
if (inboxConversation) { if (inboxConversation) {
if (this.provider.reopenConversation) { if (this.provider.reopenConversation) {
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`); this.logger.verbose(
`Found conversation in reopenConversation mode: ID: ${inboxConversation.id} - Name: ${inboxConversation.meta.sender.name} - Identifier: ${inboxConversation.meta.sender.identifier}`,
);
if (inboxConversation && this.provider.conversationPending && inboxConversation.status !== 'open') { if (inboxConversation && this.provider.conversationPending && inboxConversation.status !== 'open') {
await client.conversations.toggleStatus({ await client.conversations.toggleStatus({
accountId: this.provider.accountId, accountId: this.provider.accountId,
@ -789,7 +788,7 @@ export class ChatwootService {
if (inboxConversation) { if (inboxConversation) {
this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`); this.logger.verbose(`Returning existing conversation ID: ${inboxConversation.id}`);
this.cache.set(cacheKey, inboxConversation.id, 8 * 3600); this.cache.set(cacheKey, inboxConversation.id, 1800);
return inboxConversation.id; return inboxConversation.id;
} }
} }
@ -803,14 +802,6 @@ export class ChatwootService {
data['status'] = 'pending'; data['status'] = 'pending';
} }
/*
Triple check after lock
Utilizei uma nova verificação para evitar que outra thread execute entre o terminio do while e o set lock
*/
if (await this.cache.has(cacheKey)) {
return (await this.cache.get(cacheKey)) as number;
}
const conversation = await client.conversations.create({ const conversation = await client.conversations.create({
accountId: this.provider.accountId, accountId: this.provider.accountId,
data, data,
@ -822,7 +813,7 @@ export class ChatwootService {
} }
this.logger.verbose(`New conversation created of ${remoteJid} with ID: ${conversation.id}`); this.logger.verbose(`New conversation created of ${remoteJid} with ID: ${conversation.id}`);
this.cache.set(cacheKey, conversation.id, 8 * 3600); this.cache.set(cacheKey, conversation.id, 1800);
return conversation.id; return conversation.id;
} finally { } finally {
await this.cache.delete(lockKey); await this.cache.delete(lockKey);
@ -1392,10 +1383,7 @@ export class ChatwootService {
} }
if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') { if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') {
if ( if (body?.conversation?.messages[0]?.source_id?.substring(0, 5) === 'WAID:') {
body?.conversation?.messages[0]?.source_id?.substring(0, 5) === 'WAID:' &&
body?.conversation?.messages[0]?.id === body?.id
) {
return { message: 'bot' }; return { message: 'bot' };
} }
@ -2032,17 +2020,9 @@ export class ChatwootService {
const participantName = body.pushName; const participantName = body.pushName;
const rawPhoneNumber = const rawPhoneNumber =
body.key.addressingMode === 'lid' && !body.key.fromMe body.key.addressingMode === 'lid' && !body.key.fromMe
? body.key.participantAlt.split('@')[0] ? body.key.participantAlt.split('@')[0].split(':')[0]
: body.key.participant.split('@')[0]; : body.key.participant.split('@')[0].split(':')[0];
const phoneMatch = rawPhoneNumber.match(/^(\d{2})(\d{2})(\d{4})(\d{4})$/); const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
let formattedPhoneNumber: string;
if (phoneMatch) {
formattedPhoneNumber = `+${phoneMatch[1]} (${phoneMatch[2]}) ${phoneMatch[3]}-${phoneMatch[4]}`;
} else {
formattedPhoneNumber = `+${rawPhoneNumber}`;
}
let content: string; let content: string;
@ -2178,17 +2158,9 @@ export class ChatwootService {
const participantName = body.pushName; const participantName = body.pushName;
const rawPhoneNumber = const rawPhoneNumber =
body.key.addressingMode === 'lid' && !body.key.fromMe body.key.addressingMode === 'lid' && !body.key.fromMe
? body.key.participantAlt.split('@')[0] ? body.key.participantAlt.split('@')[0].split(':')[0]
: body.key.participant.split('@')[0]; : body.key.participant.split('@')[0].split(':')[0];
const phoneMatch = rawPhoneNumber.match(/^(\d{2})(\d{2})(\d{4})(\d{4})$/); const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
let formattedPhoneNumber: string;
if (phoneMatch) {
formattedPhoneNumber = `+${phoneMatch[1]} (${phoneMatch[2]}) ${phoneMatch[3]}-${phoneMatch[4]}`;
} else {
formattedPhoneNumber = `+${rawPhoneNumber}`;
}
let content: string; let content: string;
@ -2270,8 +2242,21 @@ export class ChatwootService {
} }
if (event === 'messages.edit' || event === 'send.message.update') { if (event === 'messages.edit' || event === 'send.message.update') {
const editedMessageContent = const editedMessageContentRaw =
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text; body?.editedMessage?.conversation ??
body?.editedMessage?.extendedTextMessage?.text ??
body?.editedMessage?.imageMessage?.caption ??
body?.editedMessage?.videoMessage?.caption ??
body?.editedMessage?.documentMessage?.caption ??
(typeof body?.text === 'string' ? body.text : undefined);
const editedMessageContent = (editedMessageContentRaw ?? '').trim();
if (!editedMessageContent) {
this.logger.info('[CW.EDIT] Conteúdo vazio — ignorando (DELETE tratará se for revoke).');
return;
}
const message = await this.getMessageByKeyId(instance, body?.key?.id); const message = await this.getMessageByKeyId(instance, body?.key?.id);
if (!message) { if (!message) {

View File

@ -82,7 +82,7 @@ const proxyService = new ProxyService(waMonitor);
export const proxyController = new ProxyController(proxyService, waMonitor); export const proxyController = new ProxyController(proxyService, waMonitor);
const chatwootService = new ChatwootService(waMonitor, configService, prismaRepository, chatwootCache); const chatwootService = new ChatwootService(waMonitor, configService, prismaRepository, chatwootCache);
export const chatwootController = new ChatwootController(chatwootService, configService, prismaRepository); export const chatwootController = new ChatwootController(chatwootService, configService);
const settingsService = new SettingsService(waMonitor); const settingsService = new SettingsService(waMonitor);
export const settingsController = new SettingsController(settingsService); export const settingsController = new SettingsController(settingsService);

View File

@ -60,6 +60,7 @@ export class ChannelStartupService {
this.instance.number = instance.number; this.instance.number = instance.number;
this.instance.token = instance.token; this.instance.token = instance.token;
this.instance.businessId = instance.businessId; this.instance.businessId = instance.businessId;
this.instance.ownerJid = instance.ownerJid;
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
this.chatwootService.eventWhatsapp( this.chatwootService.eventWhatsapp(

View File

@ -38,25 +38,37 @@ export class WAMonitoringService {
private readonly logger = new Logger('WAMonitoringService'); private readonly logger = new Logger('WAMonitoringService');
public readonly waInstances: Record<string, any> = {}; public readonly waInstances: Record<string, any> = {};
private readonly delInstanceTimeouts: Record<string, NodeJS.Timeout> = {};
private readonly providerSession: ProviderSession; private readonly providerSession: ProviderSession;
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( // Clear previous timeout if exists
if (this.delInstanceTimeouts[instance]) {
clearTimeout(this.delInstanceTimeouts[instance]);
}
// Set new timeout and store reference
this.delInstanceTimeouts[instance] = setTimeout(
async () => { async () => {
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { try {
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');
} }
} finally {
// Clean up timeout reference
delete this.delInstanceTimeouts[instance];
} }
}, },
1000 * 60 * time, 1000 * 60 * time,
@ -64,6 +76,13 @@ export class WAMonitoringService {
} }
} }
public clearDelInstanceTime(instance: string) {
if (this.delInstanceTimeouts[instance]) {
clearTimeout(this.delInstanceTimeouts[instance]);
delete this.delInstanceTimeouts[instance];
}
}
public async instanceInfo(instanceNames?: string[]): Promise<any> { public async instanceInfo(instanceNames?: string[]): Promise<any> {
if (instanceNames && instanceNames.length > 0) { if (instanceNames && instanceNames.length > 0) {
const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : []; const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : [];
@ -271,9 +290,19 @@ export class WAMonitoringService {
token: instanceData.token, token: instanceData.token,
number: instanceData.number, number: instanceData.number,
businessId: instanceData.businessId, businessId: instanceData.businessId,
ownerJid: instanceData.ownerJid,
}); });
await instance.connectToWhatsapp(); if (instanceData.connectionStatus === 'open' || instanceData.connectionStatus === 'connecting') {
this.logger.info(
`Auto-connecting instance "${instanceData.instanceName}" (status: ${instanceData.connectionStatus})`,
);
await instance.connectToWhatsapp();
} else {
this.logger.info(
`Skipping auto-connect for instance "${instanceData.instanceName}" (status: ${instanceData.connectionStatus || 'close'})`,
);
}
this.waInstances[instanceData.instanceName] = instance; this.waInstances[instanceData.instanceName] = instance;
} }
@ -299,6 +328,7 @@ export class WAMonitoringService {
token: instanceData.token, token: instanceData.token,
number: instanceData.number, number: instanceData.number,
businessId: instanceData.businessId, businessId: instanceData.businessId,
connectionStatus: instanceData.connectionStatus as any, // Pass connection status
}; };
this.setInstance(instance); this.setInstance(instance);
@ -327,6 +357,8 @@ export class WAMonitoringService {
token: instance.token, token: instance.token,
number: instance.number, number: instance.number,
businessId: instance.businessId, businessId: instance.businessId,
ownerJid: instance.ownerJid,
connectionStatus: instance.connectionStatus as any, // Pass connection status
}); });
}), }),
); );
@ -351,6 +383,7 @@ export class WAMonitoringService {
integration: instance.integration, integration: instance.integration,
token: instance.token, token: instance.token,
businessId: instance.businessId, businessId: instance.businessId,
connectionStatus: instance.connectionStatus as any, // Pass connection status
}); });
}), }),
); );
@ -361,6 +394,8 @@ export class WAMonitoringService {
try { try {
await this.waInstances[instanceName]?.sendDataWebhook(Events.REMOVE_INSTANCE, null); await this.waInstances[instanceName]?.sendDataWebhook(Events.REMOVE_INSTANCE, null);
this.clearDelInstanceTime(instanceName);
this.cleaningUp(instanceName); this.cleaningUp(instanceName);
this.cleaningStoreData(instanceName); this.cleaningStoreData(instanceName);
} finally { } finally {
@ -377,6 +412,8 @@ export class WAMonitoringService {
try { try {
await this.waInstances[instanceName]?.sendDataWebhook(Events.LOGOUT_INSTANCE, null); await this.waInstances[instanceName]?.sendDataWebhook(Events.LOGOUT_INSTANCE, null);
this.clearDelInstanceTime(instanceName);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) { if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
this.waInstances[instanceName]?.clearCacheChatwoot(); this.waInstances[instanceName]?.clearCacheChatwoot();
} }

View File

@ -52,6 +52,7 @@ export declare namespace wa {
pairingCode?: string; pairingCode?: string;
authState?: { state: AuthenticationState; saveCreds: () => void }; authState?: { state: AuthenticationState; saveCreds: () => void };
name?: string; name?: string;
ownerJid?: string;
wuid?: string; wuid?: string;
profileName?: string; profileName?: string;
profilePictureUrl?: string; profilePictureUrl?: string;

View File

@ -26,8 +26,8 @@ import cors from 'cors';
import express, { json, NextFunction, Request, Response, urlencoded } from 'express'; import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
import { join } from 'path'; import { join } from 'path';
function initWA() { async function initWA() {
waMonitor.loadInstance(); await waMonitor.loadInstance();
} }
async function bootstrap() { async function bootstrap() {
@ -159,7 +159,9 @@ async function bootstrap() {
server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT)); server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT));
initWA(); initWA().catch((error) => {
logger.error('Error loading instances: ' + error);
});
onUnexpectedError(); onUnexpectedError();
} }

View File

@ -1,7 +1,6 @@
import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent';
import { SocksProxyAgent } from 'socks-proxy-agent'; import { SocksProxyAgent } from 'socks-proxy-agent';
import { ProxyAgent } from 'undici';
import { ProxyAgent } from 'undici'
type Proxy = { type Proxy = {
host: string; host: string;
@ -46,38 +45,38 @@ export function makeProxyAgent(proxy: Proxy | string): HttpsProxyAgent<string> |
} }
export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent { export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
let proxyUrl: string let proxyUrl: string;
let protocol: string let protocol: string;
if (typeof proxy === 'string') { if (typeof proxy === 'string') {
const url = new URL(proxy) const url = new URL(proxy);
protocol = url.protocol.replace(':', '') protocol = url.protocol.replace(':', '');
proxyUrl = proxy proxyUrl = proxy;
} else { } else {
const { host, password, port, protocol: proto, username } = proxy const { host, password, port, protocol: proto, username } = proxy;
protocol = (proto || 'http').replace(':', '') protocol = (proto || 'http').replace(':', '');
if (protocol === 'socks') { if (protocol === 'socks') {
protocol = 'socks5' protocol = 'socks5';
} }
const auth = username && password ? `${username}:${password}@` : '' const auth = username && password ? `${username}:${password}@` : '';
proxyUrl = `${protocol}://${auth}${host}:${port}` proxyUrl = `${protocol}://${auth}${host}:${port}`;
} }
const PROXY_HTTP_PROTOCOL = 'http' const PROXY_HTTP_PROTOCOL = 'http';
const PROXY_HTTPS_PROTOCOL = 'https' const PROXY_HTTPS_PROTOCOL = 'https';
const PROXY_SOCKS4_PROTOCOL = 'socks4' const PROXY_SOCKS4_PROTOCOL = 'socks4';
const PROXY_SOCKS5_PROTOCOL = 'socks5' const PROXY_SOCKS5_PROTOCOL = 'socks5';
switch (protocol) { switch (protocol) {
case PROXY_HTTP_PROTOCOL: case PROXY_HTTP_PROTOCOL:
case PROXY_HTTPS_PROTOCOL: case PROXY_HTTPS_PROTOCOL:
case PROXY_SOCKS4_PROTOCOL: case PROXY_SOCKS4_PROTOCOL:
case PROXY_SOCKS5_PROTOCOL: case PROXY_SOCKS5_PROTOCOL:
return new ProxyAgent(proxyUrl) return new ProxyAgent(proxyUrl);
default: default:
throw new Error(`Unsupported proxy protocol: ${protocol}`) throw new Error(`Unsupported proxy protocol: ${protocol}`);
} }
} }

View File

@ -117,24 +117,19 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
`Saving: remoteJid=${remoteJid}, jidOptions=${uniqueNumbers.join(',')}, lid=${item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null}`, `Saving: remoteJid=${remoteJid}, jidOptions=${uniqueNumbers.join(',')}, lid=${item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null}`,
); );
if (existingRecord) { await prismaRepository.isOnWhatsapp.upsert({
await prismaRepository.isOnWhatsapp.update({ where: { remoteJid: remoteJid },
where: { id: existingRecord.id },
data: { update: {
remoteJid: remoteJid, jidOptions: uniqueNumbers.join(','),
jidOptions: uniqueNumbers.join(','), lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null, },
}, create: {
}); remoteJid: remoteJid,
} else { jidOptions: uniqueNumbers.join(','),
await prismaRepository.isOnWhatsapp.create({ lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
data: { },
remoteJid: remoteJid, });
jidOptions: uniqueNumbers.join(','),
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
},
});
}
} }
} }
} }