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').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').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').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 { if (!this.configService.get('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(event: Events, data: T, local = true) { const serverUrl = this.configService.get('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('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) { 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) { 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; } }