mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-10 02:19:39 -06:00
649 lines
19 KiB
TypeScript
649 lines
19 KiB
TypeScript
import { InstanceDto } from '@api/dto/instance.dto';
|
|
import { ProxyDto } from '@api/dto/proxy.dto';
|
|
import { SettingsDto } from '@api/dto/settings.dto';
|
|
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
|
|
import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
|
|
import { DifyService } from '@api/integrations/chatbot/dify/services/dify.service';
|
|
import { OpenaiService } from '@api/integrations/chatbot/openai/services/openai.service';
|
|
import { TypebotService } from '@api/integrations/chatbot/typebot/services/typebot.service';
|
|
import { PrismaRepository, Query } from '@api/repository/repository.service';
|
|
import { eventManager, waMonitor } from '@api/server.module';
|
|
import { Events, wa } from '@api/types/wa.types';
|
|
import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
|
import { Logger } from '@config/logger.config';
|
|
import { NotFoundException } from '@exceptions';
|
|
import { Contact, Message } from '@prisma/client';
|
|
import { WASocket } from 'baileys';
|
|
import EventEmitter2 from 'eventemitter2';
|
|
import { v4 } from 'uuid';
|
|
|
|
import { CacheService } from './cache.service';
|
|
|
|
export class ChannelStartupService {
|
|
constructor(
|
|
public readonly configService: ConfigService,
|
|
public readonly eventEmitter: EventEmitter2,
|
|
public readonly prismaRepository: PrismaRepository,
|
|
public readonly chatwootCache: CacheService,
|
|
) {}
|
|
|
|
public readonly logger = new Logger('ChannelStartupService');
|
|
|
|
public client: WASocket;
|
|
public readonly instance: wa.Instance = {};
|
|
public readonly localChatwoot: wa.LocalChatwoot = {};
|
|
public readonly localProxy: wa.LocalProxy = {};
|
|
public readonly localSettings: wa.LocalSettings = {};
|
|
|
|
public chatwootService = new ChatwootService(
|
|
waMonitor,
|
|
this.configService,
|
|
this.prismaRepository,
|
|
this.chatwootCache,
|
|
);
|
|
|
|
public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository);
|
|
|
|
public openaiService = new OpenaiService(waMonitor, this.configService, this.prismaRepository);
|
|
|
|
public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository);
|
|
|
|
public setInstance(instance: InstanceDto) {
|
|
this.logger.setInstance(instance.instanceName);
|
|
|
|
this.instance.name = instance.instanceName;
|
|
this.instance.id = instance.instanceId;
|
|
this.instance.integration = instance.integration;
|
|
this.instance.number = instance.number;
|
|
this.instance.token = instance.token;
|
|
this.instance.businessId = instance.businessId;
|
|
|
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
|
this.chatwootService.eventWhatsapp(
|
|
Events.STATUS_INSTANCE,
|
|
{ instanceName: this.instance.name },
|
|
{
|
|
instance: this.instance.name,
|
|
status: 'created',
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
public set instanceName(name: string) {
|
|
this.logger.setInstance(name);
|
|
|
|
if (!name) {
|
|
this.instance.name = v4();
|
|
return;
|
|
}
|
|
this.instance.name = name;
|
|
}
|
|
|
|
public get instanceName() {
|
|
return this.instance.name;
|
|
}
|
|
|
|
public set instanceId(id: string) {
|
|
if (!id) {
|
|
this.instance.id = v4();
|
|
return;
|
|
}
|
|
this.instance.id = id;
|
|
}
|
|
|
|
public get instanceId() {
|
|
return this.instance.id;
|
|
}
|
|
|
|
public set integration(integration: string) {
|
|
this.instance.integration = integration;
|
|
}
|
|
|
|
public get integration() {
|
|
return this.instance.integration;
|
|
}
|
|
|
|
public set number(number: string) {
|
|
this.instance.number = number;
|
|
}
|
|
|
|
public get number() {
|
|
return this.instance.number;
|
|
}
|
|
|
|
public set token(token: string) {
|
|
this.instance.token = token;
|
|
}
|
|
|
|
public get token() {
|
|
return this.instance.token;
|
|
}
|
|
|
|
public get wuid() {
|
|
return this.instance.wuid;
|
|
}
|
|
|
|
public async loadSettings() {
|
|
const data = await this.prismaRepository.setting.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
this.localSettings.rejectCall = data?.rejectCall;
|
|
this.localSettings.msgCall = data?.msgCall;
|
|
this.localSettings.groupsIgnore = data?.groupsIgnore;
|
|
this.localSettings.alwaysOnline = data?.alwaysOnline;
|
|
this.localSettings.readMessages = data?.readMessages;
|
|
this.localSettings.readStatus = data?.readStatus;
|
|
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
|
}
|
|
|
|
public async setSettings(data: SettingsDto) {
|
|
await this.prismaRepository.setting.upsert({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
update: {
|
|
rejectCall: data.rejectCall,
|
|
msgCall: data.msgCall,
|
|
groupsIgnore: data.groupsIgnore,
|
|
alwaysOnline: data.alwaysOnline,
|
|
readMessages: data.readMessages,
|
|
readStatus: data.readStatus,
|
|
syncFullHistory: data.syncFullHistory,
|
|
},
|
|
create: {
|
|
rejectCall: data.rejectCall,
|
|
msgCall: data.msgCall,
|
|
groupsIgnore: data.groupsIgnore,
|
|
alwaysOnline: data.alwaysOnline,
|
|
readMessages: data.readMessages,
|
|
readStatus: data.readStatus,
|
|
syncFullHistory: data.syncFullHistory,
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
this.localSettings.rejectCall = data?.rejectCall;
|
|
this.localSettings.msgCall = data?.msgCall;
|
|
this.localSettings.groupsIgnore = data?.groupsIgnore;
|
|
this.localSettings.alwaysOnline = data?.alwaysOnline;
|
|
this.localSettings.readMessages = data?.readMessages;
|
|
this.localSettings.readStatus = data?.readStatus;
|
|
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
|
}
|
|
|
|
public async findSettings() {
|
|
const data = await this.prismaRepository.setting.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
rejectCall: data.rejectCall,
|
|
msgCall: data.msgCall,
|
|
groupsIgnore: data.groupsIgnore,
|
|
alwaysOnline: data.alwaysOnline,
|
|
readMessages: data.readMessages,
|
|
readStatus: data.readStatus,
|
|
syncFullHistory: data.syncFullHistory,
|
|
};
|
|
}
|
|
|
|
public async loadChatwoot() {
|
|
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
|
return;
|
|
}
|
|
|
|
const data = await this.prismaRepository.chatwoot.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
this.localChatwoot.enabled = data?.enabled;
|
|
this.localChatwoot.accountId = data?.accountId;
|
|
this.localChatwoot.token = data?.token;
|
|
this.localChatwoot.url = data?.url;
|
|
this.localChatwoot.nameInbox = data?.nameInbox;
|
|
this.localChatwoot.signMsg = data?.signMsg;
|
|
this.localChatwoot.signDelimiter = data?.signDelimiter;
|
|
this.localChatwoot.number = data?.number;
|
|
this.localChatwoot.reopenConversation = data?.reopenConversation;
|
|
this.localChatwoot.conversationPending = data?.conversationPending;
|
|
this.localChatwoot.mergeBrazilContacts = data?.mergeBrazilContacts;
|
|
this.localChatwoot.importContacts = data?.importContacts;
|
|
this.localChatwoot.importMessages = data?.importMessages;
|
|
this.localChatwoot.daysLimitImportMessages = data?.daysLimitImportMessages;
|
|
}
|
|
|
|
public async setChatwoot(data: ChatwootDto) {
|
|
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
|
return;
|
|
}
|
|
|
|
const chatwoot = await this.prismaRepository.chatwoot.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
if (chatwoot) {
|
|
await this.prismaRepository.chatwoot.update({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
data: {
|
|
enabled: data?.enabled,
|
|
accountId: data.accountId,
|
|
token: data.token,
|
|
url: data.url,
|
|
nameInbox: data.nameInbox,
|
|
signMsg: data.signMsg,
|
|
signDelimiter: data.signMsg ? data.signDelimiter : null,
|
|
number: data.number,
|
|
reopenConversation: data.reopenConversation,
|
|
conversationPending: data.conversationPending,
|
|
mergeBrazilContacts: data.mergeBrazilContacts,
|
|
importContacts: data.importContacts,
|
|
importMessages: data.importMessages,
|
|
daysLimitImportMessages: data.daysLimitImportMessages,
|
|
organization: data.organization,
|
|
logo: data.logo,
|
|
ignoreJids: data.ignoreJids,
|
|
},
|
|
});
|
|
|
|
Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null });
|
|
|
|
this.clearCacheChatwoot();
|
|
return;
|
|
}
|
|
|
|
await this.prismaRepository.chatwoot.create({
|
|
data: {
|
|
enabled: data?.enabled,
|
|
accountId: data.accountId,
|
|
token: data.token,
|
|
url: data.url,
|
|
nameInbox: data.nameInbox,
|
|
signMsg: data.signMsg,
|
|
number: data.number,
|
|
reopenConversation: data.reopenConversation,
|
|
conversationPending: data.conversationPending,
|
|
mergeBrazilContacts: data.mergeBrazilContacts,
|
|
importContacts: data.importContacts,
|
|
importMessages: data.importMessages,
|
|
daysLimitImportMessages: data.daysLimitImportMessages,
|
|
organization: data.organization,
|
|
logo: data.logo,
|
|
ignoreJids: data.ignoreJids,
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
Object.assign(this.localChatwoot, { ...data, signDelimiter: data.signMsg ? data.signDelimiter : null });
|
|
|
|
this.clearCacheChatwoot();
|
|
}
|
|
|
|
public async findChatwoot(): Promise<ChatwootDto | null> {
|
|
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
|
return null;
|
|
}
|
|
|
|
const data = await this.prismaRepository.chatwoot.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
const ignoreJidsArray = Array.isArray(data.ignoreJids) ? data.ignoreJids.map((event) => String(event)) : [];
|
|
|
|
return {
|
|
enabled: data?.enabled,
|
|
accountId: data.accountId,
|
|
token: data.token,
|
|
url: data.url,
|
|
nameInbox: data.nameInbox,
|
|
signMsg: data.signMsg,
|
|
signDelimiter: data.signDelimiter || null,
|
|
reopenConversation: data.reopenConversation,
|
|
conversationPending: data.conversationPending,
|
|
mergeBrazilContacts: data.mergeBrazilContacts,
|
|
importContacts: data.importContacts,
|
|
importMessages: data.importMessages,
|
|
daysLimitImportMessages: data.daysLimitImportMessages,
|
|
organization: data.organization,
|
|
logo: data.logo,
|
|
ignoreJids: ignoreJidsArray,
|
|
};
|
|
}
|
|
|
|
public clearCacheChatwoot() {
|
|
if (this.localChatwoot?.enabled) {
|
|
this.chatwootService.getCache()?.deleteAll(this.instanceName);
|
|
}
|
|
}
|
|
|
|
public async loadProxy() {
|
|
this.localProxy.enabled = false;
|
|
|
|
if (process.env.PROXY_HOST) {
|
|
this.localProxy.enabled = true;
|
|
this.localProxy.host = process.env.PROXY_HOST;
|
|
this.localProxy.port = process.env.PROXY_PORT || '80';
|
|
this.localProxy.protocol = process.env.PROXY_PROTOCOL || 'http';
|
|
this.localProxy.username = process.env.PROXY_USERNAME;
|
|
this.localProxy.password = process.env.PROXY_PASSWORD;
|
|
}
|
|
|
|
const data = await this.prismaRepository.proxy.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
if (data?.enabled) {
|
|
this.localProxy.enabled = true;
|
|
this.localProxy.host = data?.host;
|
|
this.localProxy.port = data?.port;
|
|
this.localProxy.protocol = data?.protocol;
|
|
this.localProxy.username = data?.username;
|
|
this.localProxy.password = data?.password;
|
|
}
|
|
}
|
|
|
|
public async setProxy(data: ProxyDto) {
|
|
await this.prismaRepository.proxy.upsert({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
update: {
|
|
enabled: data?.enabled,
|
|
host: data.host,
|
|
port: data.port,
|
|
protocol: data.protocol,
|
|
username: data.username,
|
|
password: data.password,
|
|
},
|
|
create: {
|
|
enabled: data?.enabled,
|
|
host: data.host,
|
|
port: data.port,
|
|
protocol: data.protocol,
|
|
username: data.username,
|
|
password: data.password,
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
Object.assign(this.localProxy, data);
|
|
}
|
|
|
|
public async findProxy() {
|
|
const data = await this.prismaRepository.proxy.findUnique({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
throw new NotFoundException('Proxy not found');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
|
|
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
|
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
|
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
|
const now = localISOTime;
|
|
|
|
const expose = this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES;
|
|
|
|
const instanceApikey = this.token || 'Apikey not found';
|
|
|
|
await eventManager.emit({
|
|
instanceName: this.instance.name,
|
|
origin: ChannelStartupService.name,
|
|
event,
|
|
data,
|
|
serverUrl,
|
|
dateTime: now,
|
|
sender: this.wuid,
|
|
apiKey: expose && instanceApikey ? instanceApikey : null,
|
|
local,
|
|
});
|
|
}
|
|
|
|
// Check if the number is MX or AR
|
|
public formatMXOrARNumber(jid: string): string {
|
|
const countryCode = jid.substring(0, 2);
|
|
|
|
if (Number(countryCode) === 52 || Number(countryCode) === 54) {
|
|
if (jid.length === 13) {
|
|
const number = countryCode + jid.substring(3);
|
|
return number;
|
|
}
|
|
|
|
return jid;
|
|
}
|
|
return jid;
|
|
}
|
|
|
|
// Check if the number is br
|
|
public formatBRNumber(jid: string) {
|
|
const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/);
|
|
if (regexp.test(jid)) {
|
|
const match = regexp.exec(jid);
|
|
if (match && match[1] === '55') {
|
|
const joker = Number.parseInt(match[3][0]);
|
|
const ddd = Number.parseInt(match[2]);
|
|
if (joker < 7 || ddd < 31) {
|
|
return match[0];
|
|
}
|
|
return match[1] + match[2] + match[3];
|
|
}
|
|
return jid;
|
|
} else {
|
|
return jid;
|
|
}
|
|
}
|
|
|
|
public createJid(number: string): string {
|
|
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
|
|
return number;
|
|
}
|
|
|
|
if (number.includes('@broadcast')) {
|
|
return number;
|
|
}
|
|
|
|
number = number
|
|
?.replace(/\s/g, '')
|
|
.replace(/\+/g, '')
|
|
.replace(/\(/g, '')
|
|
.replace(/\)/g, '')
|
|
.split(':')[0]
|
|
.split('@')[0];
|
|
|
|
if (number.includes('-') && number.length >= 24) {
|
|
number = number.replace(/[^\d-]/g, '');
|
|
return `${number}@g.us`;
|
|
}
|
|
|
|
number = number.replace(/\D/g, '');
|
|
|
|
if (number.length >= 18) {
|
|
number = number.replace(/[^\d-]/g, '');
|
|
return `${number}@g.us`;
|
|
}
|
|
|
|
number = this.formatMXOrARNumber(number);
|
|
|
|
number = this.formatBRNumber(number);
|
|
|
|
return `${number}@s.whatsapp.net`;
|
|
}
|
|
|
|
public async fetchContacts(query: Query<Contact>) {
|
|
const remoteJid = query?.where?.remoteJid
|
|
? query?.where?.remoteJid.includes('@')
|
|
? query.where?.remoteJid
|
|
: this.createJid(query.where?.remoteJid)
|
|
: null;
|
|
|
|
const where = {
|
|
instanceId: this.instanceId,
|
|
};
|
|
|
|
if (remoteJid) {
|
|
where['remoteJid'] = remoteJid;
|
|
}
|
|
|
|
return await this.prismaRepository.contact.findMany({
|
|
where,
|
|
});
|
|
}
|
|
|
|
public async fetchMessages(query: Query<Message>) {
|
|
const keyFilters = query?.where?.key as {
|
|
id?: string;
|
|
fromMe?: boolean;
|
|
remoteJid?: string;
|
|
participants?: string;
|
|
};
|
|
|
|
const count = await this.prismaRepository.message.count({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
id: query?.where?.id,
|
|
source: query?.where?.source,
|
|
messageType: query?.where?.messageType,
|
|
AND: [
|
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
|
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
|
|
keyFilters?.participants ? { key: { path: ['participants'], equals: keyFilters?.participants } } : {},
|
|
],
|
|
},
|
|
});
|
|
|
|
if (!query?.offset) {
|
|
query.offset = 50;
|
|
}
|
|
|
|
if (!query?.page) {
|
|
query.page = 1;
|
|
}
|
|
|
|
const messages = await this.prismaRepository.message.findMany({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
id: query?.where?.id,
|
|
source: query?.where?.source,
|
|
messageType: query?.where?.messageType,
|
|
AND: [
|
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
|
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
|
|
keyFilters?.participants ? { key: { path: ['participants'], equals: keyFilters?.participants } } : {},
|
|
],
|
|
},
|
|
orderBy: {
|
|
messageTimestamp: 'desc',
|
|
},
|
|
skip: query.offset * (query?.page === 1 ? 0 : (query?.page as number) - 1),
|
|
take: query.offset,
|
|
select: {
|
|
id: true,
|
|
key: true,
|
|
pushName: true,
|
|
messageType: true,
|
|
message: true,
|
|
messageTimestamp: true,
|
|
instanceId: true,
|
|
source: true,
|
|
MessageUpdate: {
|
|
select: {
|
|
status: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
messages: {
|
|
total: count,
|
|
pages: Math.ceil(count / query.offset),
|
|
currentPage: query.page,
|
|
records: messages,
|
|
},
|
|
};
|
|
}
|
|
|
|
public async fetchStatusMessage(query: any) {
|
|
return await this.prismaRepository.messageUpdate.findMany({
|
|
where: {
|
|
instanceId: this.instanceId,
|
|
remoteJid: query.where?.remoteJid,
|
|
keyId: query.where?.id,
|
|
},
|
|
skip: query.offset * (query?.page === 1 ? 0 : (query?.page as number) - 1),
|
|
take: query.offset,
|
|
});
|
|
}
|
|
|
|
public async fetchChats(query: any) {
|
|
const remoteJid = query?.where?.remoteJid
|
|
? query?.where?.remoteJid.includes('@')
|
|
? query.where?.remoteJid
|
|
: this.createJid(query.where?.remoteJid)
|
|
: null;
|
|
|
|
const result = await this.prismaRepository.$queryRaw`
|
|
SELECT
|
|
"Chat"."id",
|
|
"Chat"."remoteJid",
|
|
"Chat"."name",
|
|
"Chat"."labels",
|
|
"Chat"."createdAt",
|
|
"Chat"."updatedAt",
|
|
"Contact"."pushName",
|
|
"Contact"."profilePicUrl",
|
|
"Contact"."unreadMessages"
|
|
FROM "Chat"
|
|
INNER JOIN "Message" ON "Chat"."remoteJid" = "Message"."key"->>'remoteJid'
|
|
LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid"
|
|
WHERE "Chat"."instanceId" = ${this.instanceId}
|
|
${remoteJid ? 'AND "Chat"."remoteJid" = ${remoteJid}' : ''}
|
|
GROUP BY
|
|
"Chat"."id",
|
|
"Chat"."remoteJid",
|
|
"Chat"."name",
|
|
"Chat"."labels",
|
|
"Chat"."createdAt",
|
|
"Chat"."updatedAt",
|
|
"Contact"."pushName",
|
|
"Contact"."profilePicUrl",
|
|
"Contact"."unreadMessages"
|
|
ORDER BY "Chat"."updatedAt" DESC;
|
|
`;
|
|
|
|
return result;
|
|
}
|
|
}
|