mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-16 04:02:54 -06:00

- Added new fields `ownerJid`, `profileName`, and `profilePicUrl` to the Instance DTO for improved user identification and personalization. - Updated InstanceController to include the new profile information in instance data handling. - Enhanced WAMonitoringService to utilize the additional profile fields, improving the context of instance data during monitoring operations.
440 lines
16 KiB
TypeScript
440 lines
16 KiB
TypeScript
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,
|
|
},
|
|
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,
|
|
},
|
|
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());
|
|
}
|
|
}
|
|
}
|