oex2003/evolution-api
0
1import { InstanceDto, SetPresenceDto } from '@api/dto/instance.dto';2import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';3import { ProviderFiles } from '@api/provider/sessions';4import { PrismaRepository } from '@api/repository/repository.service';5import { channelController, eventManager } from '@api/server.module';6import { CacheService } from '@api/services/cache.service';7import { WAMonitoringService } from '@api/services/monitor.service';8import { SettingsService } from '@api/services/settings.service';9import { Events, Integration, wa } from '@api/types/wa.types';10import { Auth, Chatwoot, ConfigService, HttpServer, WaBusiness } from '@config/env.config';11import { Logger } from '@config/logger.config';12import { BadRequestException, InternalServerErrorException, UnauthorizedException } from '@exceptions';13import { delay } from 'baileys';14import { isArray, isURL } from 'class-validator';15import EventEmitter2 from 'eventemitter2';16import { v4 } from 'uuid';17 18import { ProxyController } from './proxy.controller';19 20export class InstanceController {21 constructor(22 private readonly waMonitor: WAMonitoringService,23 private readonly configService: ConfigService,24 private readonly prismaRepository: PrismaRepository,25 private readonly eventEmitter: EventEmitter2,26 private readonly chatwootService: ChatwootService,27 private readonly settingsService: SettingsService,28 private readonly proxyService: ProxyController,29 private readonly cache: CacheService,30 private readonly chatwootCache: CacheService,31 private readonly baileysCache: CacheService,32 private readonly providerFiles: ProviderFiles,33 ) {}34 35 private readonly logger = new Logger('InstanceController');36 37 public async createInstance(instanceData: InstanceDto) {38 try {39 const instance = channelController.init(instanceData, {40 configService: this.configService,41 eventEmitter: this.eventEmitter,42 prismaRepository: this.prismaRepository,43 cache: this.cache,44 chatwootCache: this.chatwootCache,45 baileysCache: this.baileysCache,46 providerFiles: this.providerFiles,47 });48 49 if (!instance) {50 throw new BadRequestException('Invalid integration');51 }52 53 const instanceId = v4();54 55 instanceData.instanceId = instanceId;56 57 let hash: string;58 59 if (!instanceData.token) hash = v4().toUpperCase();60 else hash = instanceData.token;61 62 await this.waMonitor.saveInstance({63 instanceId,64 integration: instanceData.integration,65 instanceName: instanceData.instanceName,66 ownerJid: instanceData.ownerJid,67 profileName: instanceData.profileName,68 profilePicUrl: instanceData.profilePicUrl,69 hash,70 number: instanceData.number,71 businessId: instanceData.businessId,72 status: instanceData.status,73 });74 75 instance.setInstance({76 instanceName: instanceData.instanceName,77 instanceId,78 integration: instanceData.integration,79 token: hash,80 number: instanceData.number,81 businessId: instanceData.businessId,82 });83 84 this.waMonitor.waInstances[instance.instanceName] = instance;85 this.waMonitor.delInstanceTime(instance.instanceName);86 87 // set events88 await eventManager.setInstance(instance.instanceName, instanceData);89 90 instance.sendDataWebhook(Events.INSTANCE_CREATE, {91 instanceName: instanceData.instanceName,92 instanceId: instanceId,93 });94 95 if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) {96 const testProxy = await this.proxyService.testProxy({97 host: instanceData.proxyHost,98 port: instanceData.proxyPort,99 protocol: instanceData.proxyProtocol,100 username: instanceData.proxyUsername,101 password: instanceData.proxyPassword,102 });103 if (!testProxy) {104 throw new BadRequestException('Invalid proxy');105 }106 107 await this.proxyService.createProxy(instance, {108 enabled: true,109 host: instanceData.proxyHost,110 port: instanceData.proxyPort,111 protocol: instanceData.proxyProtocol,112 username: instanceData.proxyUsername,113 password: instanceData.proxyPassword,114 });115 }116 117 const settings: wa.LocalSettings = {118 rejectCall: instanceData.rejectCall === true,119 msgCall: instanceData.msgCall || '',120 groupsIgnore: instanceData.groupsIgnore === true,121 alwaysOnline: instanceData.alwaysOnline === true,122 readMessages: instanceData.readMessages === true,123 readStatus: instanceData.readStatus === true,124 syncFullHistory: instanceData.syncFullHistory === true,125 wavoipToken: instanceData.wavoipToken || '',126 };127 128 await this.settingsService.create(instance, settings);129 130 let webhookWaBusiness = null,131 accessTokenWaBusiness = '';132 133 if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {134 if (!instanceData.number) {135 throw new BadRequestException('number is required');136 }137 const urlServer = this.configService.get<HttpServer>('SERVER').URL;138 webhookWaBusiness = `${urlServer}/webhook/meta`;139 accessTokenWaBusiness = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;140 }141 142 if (!instanceData.chatwootAccountId || !instanceData.chatwootToken || !instanceData.chatwootUrl) {143 let getQrcode: wa.QrCode;144 145 if (instanceData.qrcode && instanceData.integration === Integration.WHATSAPP_BAILEYS) {146 await instance.connectToWhatsapp(instanceData.number);147 await delay(5000);148 getQrcode = instance.qrCode;149 }150 151 const result = {152 instance: {153 instanceName: instance.instanceName,154 instanceId: instanceId,155 integration: instanceData.integration,156 webhookWaBusiness,157 accessTokenWaBusiness,158 status: instance.connectionStatus.state,159 },160 hash,161 webhook: {162 webhookUrl: instanceData?.webhook?.url,163 webhookHeaders: instanceData?.webhook?.headers,164 webhookByEvents: instanceData?.webhook?.byEvents,165 webhookBase64: instanceData?.webhook?.base64,166 },167 websocket: {168 enabled: instanceData?.websocket?.enabled,169 },170 rabbitmq: {171 enabled: instanceData?.rabbitmq?.enabled,172 },173 nats: {174 enabled: instanceData?.nats?.enabled,175 },176 sqs: {177 enabled: instanceData?.sqs?.enabled,178 },179 settings,180 qrcode: getQrcode,181 };182 183 return result;184 }185 186 if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED)187 throw new BadRequestException('Chatwoot is not enabled');188 189 if (!instanceData.chatwootAccountId) {190 throw new BadRequestException('accountId is required');191 }192 193 if (!instanceData.chatwootToken) {194 throw new BadRequestException('token is required');195 }196 197 if (!instanceData.chatwootUrl) {198 throw new BadRequestException('url is required');199 }200 201 if (!isURL(instanceData.chatwootUrl, { require_tld: false })) {202 throw new BadRequestException('Invalid "url" property in chatwoot');203 }204 205 if (instanceData.chatwootSignMsg !== true && instanceData.chatwootSignMsg !== false) {206 throw new BadRequestException('signMsg is required');207 }208 209 if (instanceData.chatwootReopenConversation !== true && instanceData.chatwootReopenConversation !== false) {210 throw new BadRequestException('reopenConversation is required');211 }212 213 if (instanceData.chatwootConversationPending !== true && instanceData.chatwootConversationPending !== false) {214 throw new BadRequestException('conversationPending is required');215 }216 217 const urlServer = this.configService.get<HttpServer>('SERVER').URL;218 219 try {220 this.chatwootService.create(instance, {221 enabled: true,222 accountId: instanceData.chatwootAccountId,223 token: instanceData.chatwootToken,224 url: instanceData.chatwootUrl,225 signMsg: instanceData.chatwootSignMsg || false,226 nameInbox: instanceData.chatwootNameInbox ?? instance.instanceName.split('-cwId-')[0],227 number: instanceData.number,228 reopenConversation: instanceData.chatwootReopenConversation || false,229 conversationPending: instanceData.chatwootConversationPending || false,230 importContacts: instanceData.chatwootImportContacts ?? true,231 mergeBrazilContacts: instanceData.chatwootMergeBrazilContacts ?? false,232 importMessages: instanceData.chatwootImportMessages ?? true,233 daysLimitImportMessages: instanceData.chatwootDaysLimitImportMessages ?? 60,234 organization: instanceData.chatwootOrganization,235 logo: instanceData.chatwootLogo,236 autoCreate: instanceData.chatwootAutoCreate !== false,237 });238 } catch (error) {239 this.logger.log(error);240 }241 242 return {243 instance: {244 instanceName: instance.instanceName,245 instanceId: instanceId,246 integration: instanceData.integration,247 webhookWaBusiness,248 accessTokenWaBusiness,249 status: instance.connectionStatus.state,250 },251 hash,252 webhook: {253 webhookUrl: instanceData?.webhook?.url,254 webhookHeaders: instanceData?.webhook?.headers,255 webhookByEvents: instanceData?.webhook?.byEvents,256 webhookBase64: instanceData?.webhook?.base64,257 },258 websocket: {259 enabled: instanceData?.websocket?.enabled,260 },261 rabbitmq: {262 enabled: instanceData?.rabbitmq?.enabled,263 },264 nats: {265 enabled: instanceData?.nats?.enabled,266 },267 sqs: {268 enabled: instanceData?.sqs?.enabled,269 },270 settings,271 chatwoot: {272 enabled: true,273 accountId: instanceData.chatwootAccountId,274 token: instanceData.chatwootToken,275 url: instanceData.chatwootUrl,276 signMsg: instanceData.chatwootSignMsg || false,277 reopenConversation: instanceData.chatwootReopenConversation || false,278 conversationPending: instanceData.chatwootConversationPending || false,279 mergeBrazilContacts: instanceData.chatwootMergeBrazilContacts ?? false,280 importContacts: instanceData.chatwootImportContacts ?? true,281 importMessages: instanceData.chatwootImportMessages ?? true,282 daysLimitImportMessages: instanceData.chatwootDaysLimitImportMessages || 60,283 number: instanceData.number,284 nameInbox: instanceData.chatwootNameInbox ?? instance.instanceName,285 webhookUrl: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,286 },287 };288 } catch (error) {289 this.waMonitor.deleteInstance(instanceData.instanceName);290 this.logger.error(isArray(error.message) ? error.message[0] : error.message);291 throw new BadRequestException(isArray(error.message) ? error.message[0] : error.message);292 }293 }294 295 public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) {296 try {297 const instance = this.waMonitor.waInstances[instanceName];298 const state = instance?.connectionStatus?.state;299 300 if (!state) {301 throw new BadRequestException('The "' + instanceName + '" instance does not exist');302 }303 304 if (state == 'open') {305 return await this.connectionState({ instanceName });306 }307 308 if (state == 'connecting') {309 return instance.qrCode;310 }311 312 if (state == 'close') {313 await instance.connectToWhatsapp(number);314 315 await delay(2000);316 return instance.qrCode;317 }318 319 return {320 instance: {321 instanceName: instanceName,322 status: state,323 },324 qrcode: instance?.qrCode,325 };326 } catch (error) {327 this.logger.error(error);328 return { error: true, message: error.toString() };329 }330 }331 332 public async restartInstance({ instanceName }: InstanceDto) {333 try {334 const instance = this.waMonitor.waInstances[instanceName];335 const state = instance?.connectionStatus?.state;336 337 if (!state) {338 throw new BadRequestException('The "' + instanceName + '" instance does not exist');339 }340 341 if (state == 'close') {342 throw new BadRequestException('The "' + instanceName + '" instance is not connected');343 } else if (state == 'open') {344 if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) instance.clearCacheChatwoot();345 this.logger.info('restarting instance' + instanceName);346 347 instance.client?.ws?.close();348 instance.client?.end(new Error('restart'));349 return await this.connectToWhatsapp({ instanceName });350 } else if (state == 'connecting') {351 instance.client?.ws?.close();352 instance.client?.end(new Error('restart'));353 return await this.connectToWhatsapp({ instanceName });354 }355 } catch (error) {356 this.logger.error(error);357 return { error: true, message: error.toString() };358 }359 }360 361 public async connectionState({ instanceName }: InstanceDto) {362 return {363 instance: {364 instanceName: instanceName,365 state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state,366 },367 };368 }369 370 public async fetchInstances({ instanceName, instanceId, number }: InstanceDto, key: string) {371 const env = this.configService.get<Auth>('AUTHENTICATION').API_KEY;372 373 if (env.KEY !== key) {374 const instancesByKey = await this.prismaRepository.instance.findMany({375 where: {376 token: key,377 name: instanceName || undefined,378 id: instanceId || undefined,379 },380 });381 382 if (instancesByKey.length > 0) {383 const names = instancesByKey.map((instance) => instance.name);384 385 return this.waMonitor.instanceInfo(names);386 } else {387 throw new UnauthorizedException();388 }389 }390 391 if (instanceId || number) {392 return this.waMonitor.instanceInfoById(instanceId, number);393 }394 395 const instanceNames = instanceName ? [instanceName] : null;396 397 return this.waMonitor.instanceInfo(instanceNames);398 }399 400 public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {401 return await this.waMonitor.waInstances[instanceName].setPresence(data);402 }403 404 public async logout({ instanceName }: InstanceDto) {405 const { instance } = await this.connectionState({ instanceName });406 407 if (instance.state === 'close') {408 throw new BadRequestException('The "' + instanceName + '" instance is not connected');409 }410 411 try {412 this.waMonitor.waInstances[instanceName]?.logoutInstance();413 414 return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };415 } catch (error) {416 throw new InternalServerErrorException(error.toString());417 }418 }419 420 public async deleteInstance({ instanceName }: InstanceDto) {421 const { instance } = await this.connectionState({ instanceName });422 try {423 const waInstances = this.waMonitor.waInstances[instanceName];424 if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) waInstances?.clearCacheChatwoot();425 426 if (instance.state === 'connecting' || instance.state === 'open') {427 await this.logout({ instanceName });428 }429 430 try {431 waInstances?.sendDataWebhook(Events.INSTANCE_DELETE, {432 instanceName,433 instanceId: waInstances.instanceId,434 });435 } catch (error) {436 this.logger.error(error);437 }438 439 this.eventEmitter.emit('remove.instance', instanceName, 'inner');440 return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };441 } catch (error) {442 throw new BadRequestException(error.toString());443 }444 }445}446 