import { InstanceDto, SetPresenceDto } from '@api/dto/instance.dto'; import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service'; import { ProviderFiles } from '@api/provider/sessions'; import { PrismaRepository } from '@api/repository/repository.service'; import { channelController, eventManager } from '@api/server.module'; import { CacheService } from '@api/services/cache.service'; import { WAMonitoringService } from '@api/services/monitor.service'; import { SettingsService } from '@api/services/settings.service'; import { Events, Integration, wa } from '@api/types/wa.types'; import { Auth, Chatwoot, ConfigService, HttpServer, WaBusiness } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { BadRequestException, InternalServerErrorException, UnauthorizedException } from '@exceptions'; import { delay } from 'baileys'; import { isArray, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; import { v4 } from 'uuid'; import { ProxyController } from './proxy.controller'; export class InstanceController { constructor( private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService, private readonly prismaRepository: PrismaRepository, private readonly eventEmitter: EventEmitter2, private readonly chatwootService: ChatwootService, private readonly settingsService: SettingsService, private readonly proxyService: ProxyController, private readonly cache: CacheService, private readonly chatwootCache: CacheService, private readonly baileysCache: CacheService, private readonly providerFiles: ProviderFiles, ) {} private readonly logger = new Logger('InstanceController'); public async createInstance(instanceData: InstanceDto) { try { const instance = channelController.init(instanceData, { configService: this.configService, eventEmitter: this.eventEmitter, prismaRepository: this.prismaRepository, cache: this.cache, chatwootCache: this.chatwootCache, baileysCache: this.baileysCache, providerFiles: this.providerFiles, }); if (!instance) { throw new BadRequestException('Invalid integration'); } const instanceId = v4(); instanceData.instanceId = instanceId; let hash: string; if (!instanceData.token) hash = v4().toUpperCase(); else hash = instanceData.token; await this.waMonitor.saveInstance({ instanceId, integration: instanceData.integration, instanceName: instanceData.instanceName, ownerJid: instanceData.ownerJid, profileName: instanceData.profileName, profilePicUrl: instanceData.profilePicUrl, hash, number: instanceData.number, businessId: instanceData.businessId, status: instanceData.status, }); instance.setInstance({ instanceName: instanceData.instanceName, instanceId, integration: instanceData.integration, token: hash, number: instanceData.number, businessId: instanceData.businessId, }); this.waMonitor.waInstances[instance.instanceName] = instance; this.waMonitor.delInstanceTime(instance.instanceName); // set events await eventManager.setInstance(instance.instanceName, instanceData); instance.sendDataWebhook(Events.INSTANCE_CREATE, { instanceName: instanceData.instanceName, instanceId: instanceId, }); if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) { const testProxy = await this.proxyService.testProxy({ host: instanceData.proxyHost, port: instanceData.proxyPort, protocol: instanceData.proxyProtocol, username: instanceData.proxyUsername, password: instanceData.proxyPassword, }); if (!testProxy) { throw new BadRequestException('Invalid proxy'); } await this.proxyService.createProxy(instance, { enabled: true, host: instanceData.proxyHost, port: instanceData.proxyPort, protocol: instanceData.proxyProtocol, username: instanceData.proxyUsername, password: instanceData.proxyPassword, }); } const settings: wa.LocalSettings = { rejectCall: instanceData.rejectCall === true, msgCall: instanceData.msgCall || '', groupsIgnore: instanceData.groupsIgnore === true, alwaysOnline: instanceData.alwaysOnline === true, readMessages: instanceData.readMessages === true, readStatus: instanceData.readStatus === true, syncFullHistory: instanceData.syncFullHistory === true, wavoipToken: instanceData.wavoipToken || '', }; await this.settingsService.create(instance, settings); let webhookWaBusiness = null, accessTokenWaBusiness = ''; if (instanceData.integration === Integration.WHATSAPP_BUSINESS) { if (!instanceData.number) { throw new BadRequestException('number is required'); } const urlServer = this.configService.get('SERVER').URL; webhookWaBusiness = `${urlServer}/webhook/meta`; accessTokenWaBusiness = this.configService.get('WA_BUSINESS').TOKEN_WEBHOOK; } if (!instanceData.chatwootAccountId || !instanceData.chatwootToken || !instanceData.chatwootUrl) { let getQrcode: wa.QrCode; if (instanceData.qrcode && instanceData.integration === Integration.WHATSAPP_BAILEYS) { await instance.connectToWhatsapp(instanceData.number); await delay(5000); getQrcode = instance.qrCode; } const result = { instance: { instanceName: instance.instanceName, instanceId: instanceId, integration: instanceData.integration, webhookWaBusiness, accessTokenWaBusiness, status: instance.connectionStatus.state, }, hash, webhook: { webhookUrl: instanceData?.webhook?.url, webhookHeaders: instanceData?.webhook?.headers, webhookByEvents: instanceData?.webhook?.byEvents, webhookBase64: instanceData?.webhook?.base64, }, websocket: { enabled: instanceData?.websocket?.enabled, }, rabbitmq: { enabled: instanceData?.rabbitmq?.enabled, }, sqs: { enabled: instanceData?.sqs?.enabled, }, settings, qrcode: getQrcode, }; return result; } if (!this.configService.get('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is not enabled'); if (!instanceData.chatwootAccountId) { throw new BadRequestException('accountId is required'); } if (!instanceData.chatwootToken) { throw new BadRequestException('token is required'); } if (!instanceData.chatwootUrl) { throw new BadRequestException('url is required'); } if (!isURL(instanceData.chatwootUrl, { require_tld: false })) { throw new BadRequestException('Invalid "url" property in chatwoot'); } if (instanceData.chatwootSignMsg !== true && instanceData.chatwootSignMsg !== false) { throw new BadRequestException('signMsg is required'); } if (instanceData.chatwootReopenConversation !== true && instanceData.chatwootReopenConversation !== false) { throw new BadRequestException('reopenConversation is required'); } if (instanceData.chatwootConversationPending !== true && instanceData.chatwootConversationPending !== false) { throw new BadRequestException('conversationPending is required'); } const urlServer = this.configService.get('SERVER').URL; try { this.chatwootService.create(instance, { enabled: true, accountId: instanceData.chatwootAccountId, token: instanceData.chatwootToken, url: instanceData.chatwootUrl, signMsg: instanceData.chatwootSignMsg || false, nameInbox: instanceData.chatwootNameInbox ?? instance.instanceName.split('-cwId-')[0], number: instanceData.number, reopenConversation: instanceData.chatwootReopenConversation || false, conversationPending: instanceData.chatwootConversationPending || false, importContacts: instanceData.chatwootImportContacts ?? true, mergeBrazilContacts: instanceData.chatwootMergeBrazilContacts ?? false, importMessages: instanceData.chatwootImportMessages ?? true, daysLimitImportMessages: instanceData.chatwootDaysLimitImportMessages ?? 60, organization: instanceData.chatwootOrganization, logo: instanceData.chatwootLogo, autoCreate: instanceData.chatwootAutoCreate !== false, }); } catch (error) { this.logger.log(error); } return { instance: { instanceName: instance.instanceName, instanceId: instanceId, integration: instanceData.integration, webhookWaBusiness, accessTokenWaBusiness, status: instance.connectionStatus.state, }, hash, webhook: { webhookUrl: instanceData?.webhook?.url, webhookHeaders: instanceData?.webhook?.headers, webhookByEvents: instanceData?.webhook?.byEvents, webhookBase64: instanceData?.webhook?.base64, }, websocket: { enabled: instanceData?.websocket?.enabled, }, rabbitmq: { enabled: instanceData?.rabbitmq?.enabled, }, sqs: { enabled: instanceData?.sqs?.enabled, }, settings, chatwoot: { enabled: true, accountId: instanceData.chatwootAccountId, token: instanceData.chatwootToken, url: instanceData.chatwootUrl, signMsg: instanceData.chatwootSignMsg || false, reopenConversation: instanceData.chatwootReopenConversation || false, conversationPending: instanceData.chatwootConversationPending || false, mergeBrazilContacts: instanceData.chatwootMergeBrazilContacts ?? false, importContacts: instanceData.chatwootImportContacts ?? true, importMessages: instanceData.chatwootImportMessages ?? true, daysLimitImportMessages: instanceData.chatwootDaysLimitImportMessages || 60, number: instanceData.number, nameInbox: instanceData.chatwootNameInbox ?? instance.instanceName, webhookUrl: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, }, }; } catch (error) { this.waMonitor.deleteInstance(instanceData.instanceName); this.logger.error(isArray(error.message) ? error.message[0] : error.message); throw new BadRequestException(isArray(error.message) ? error.message[0] : error.message); } } public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { try { const instance = this.waMonitor.waInstances[instanceName]; const state = instance?.connectionStatus?.state; if (!state) { throw new BadRequestException('The "' + instanceName + '" instance does not exist'); } if (state == 'open') { return await this.connectionState({ instanceName }); } if (state == 'connecting') { return instance.qrCode; } if (state == 'close') { await instance.connectToWhatsapp(number); await delay(2000); return instance.qrCode; } return { instance: { instanceName: instanceName, status: state, }, qrcode: instance?.qrCode, }; } catch (error) { this.logger.error(error); return { error: true, message: error.toString() }; } } public async restartInstance({ instanceName }: InstanceDto) { try { const instance = this.waMonitor.waInstances[instanceName]; const state = instance?.connectionStatus?.state; if (!state) { throw new BadRequestException('The "' + instanceName + '" instance does not exist'); } if (state == 'close') { throw new BadRequestException('The "' + instanceName + '" instance is not connected'); } else if (state == 'open') { if (this.configService.get('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?.end(new Error('restart')); return await this.connectToWhatsapp({ instanceName }); } } catch (error) { this.logger.error(error); return { error: true, message: error.toString() }; } } public async connectionState({ instanceName }: InstanceDto) { return { instance: { instanceName: instanceName, state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state, }, }; } public async fetchInstances({ instanceName, instanceId, number }: InstanceDto, key: string) { const env = this.configService.get('AUTHENTICATION').API_KEY; if (env.KEY !== key) { const instancesByKey = await this.prismaRepository.instance.findMany({ where: { token: key, name: instanceName || undefined, id: instanceId || undefined, }, }); if (instancesByKey.length > 0) { const names = instancesByKey.map((instance) => instance.name); return this.waMonitor.instanceInfo(names); } else { throw new UnauthorizedException(); } } if (instanceId || number) { return this.waMonitor.instanceInfoById(instanceId, number); } const instanceNames = instanceName ? [instanceName] : null; return this.waMonitor.instanceInfo(instanceNames); } public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) { return await this.waMonitor.waInstances[instanceName].setPresence(data); } public async logout({ instanceName }: InstanceDto) { const { instance } = await this.connectionState({ instanceName }); if (instance.state === 'close') { throw new BadRequestException('The "' + instanceName + '" instance is not connected'); } try { this.waMonitor.waInstances[instanceName]?.logoutInstance(); return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; } catch (error) { throw new InternalServerErrorException(error.toString()); } } public async deleteInstance({ instanceName }: InstanceDto) { const { instance } = await this.connectionState({ instanceName }); try { const waInstances = this.waMonitor.waInstances[instanceName]; if (this.configService.get('CHATWOOT').ENABLED) waInstances?.clearCacheChatwoot(); if (instance.state === 'connecting' || instance.state === 'open') { await this.logout({ instanceName }); } try { waInstances?.sendDataWebhook(Events.INSTANCE_DELETE, { instanceName, instanceId: waInstances.instanceId, }); } catch (error) { this.logger.error(error); } this.eventEmitter.emit('remove.instance', instanceName, 'inner'); return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; } catch (error) { throw new BadRequestException(error.toString()); } } }