Spaces:
Runtime error
Runtime error
| 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<HttpServer>('SERVER').URL; | |
| webhookWaBusiness = `${urlServer}/webhook/meta`; | |
| accessTokenWaBusiness = this.configService.get<WaBusiness>('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, | |
| }, | |
| nats: { | |
| enabled: instanceData?.nats?.enabled, | |
| }, | |
| sqs: { | |
| enabled: instanceData?.sqs?.enabled, | |
| }, | |
| settings, | |
| qrcode: getQrcode, | |
| }; | |
| return result; | |
| } | |
| if (!this.configService.get<Chatwoot>('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<HttpServer>('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, | |
| }, | |
| nats: { | |
| enabled: instanceData?.nats?.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>('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<Auth>('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>('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()); | |
| } | |
| } | |
| } | |