mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-16 12:12:55 -06:00
refactor(chatbot): unify keywordFinish type and enhance session handling
- Changed the type of `keywordFinish` from an array to a string in multiple DTOs and controller interfaces to simplify data handling. - Updated the `BaseChatbotService` to include logic for updating session status to 'opened' and managing user responses more effectively. - Refactored the media message handling in the `BaseChatbotService` to streamline the process and improve readability. - Enhanced error logging across various services to ensure better traceability during operations. This commit focuses on improving the structure and consistency of chatbot integrations while ensuring that session management is robust and user-friendly.
This commit is contained in:
parent
d673c83a93
commit
f9567fbeaa
@ -31,7 +31,7 @@ export interface BaseBotData {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string[];
|
keywordFinish?: string;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
@ -792,7 +792,7 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
|
|||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
// Get integration type
|
// Get integration type
|
||||||
const integrationType = this.getIntegrationType();
|
// const integrationType = this.getIntegrationType();
|
||||||
|
|
||||||
// Find a bot for this message
|
// Find a bot for this message
|
||||||
let findBot: any = await this.findBotTrigger(this.botRepository, content, instance, session);
|
let findBot: any = await this.findBotTrigger(this.botRepository, content, instance, session);
|
||||||
|
@ -8,7 +8,7 @@ export class BaseChatbotDto {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string[];
|
keywordFinish?: string;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
@ -28,7 +28,7 @@ export class BaseChatbotDto {
|
|||||||
*/
|
*/
|
||||||
export class BaseChatbotSettingDto {
|
export class BaseChatbotSettingDto {
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string[];
|
keywordFinish?: string;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
|
@ -194,6 +194,17 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
|
|
||||||
// Forward the message to the chatbot API
|
// Forward the message to the chatbot API
|
||||||
await this.sendMessageToBot(instance, session, settings, bot, remoteJid, pushName || '', content, msg);
|
await this.sendMessageToBot(instance, session, settings, bot, remoteJid, pushName || '', content, msg);
|
||||||
|
|
||||||
|
// Update session to indicate we're waiting for user response
|
||||||
|
await this.prismaRepository.integrationSession.update({
|
||||||
|
where: {
|
||||||
|
id: session.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
status: 'opened',
|
||||||
|
awaitUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error in process: ${error}`);
|
this.logger.error(`Error in process: ${error}`);
|
||||||
return;
|
return;
|
||||||
@ -218,12 +229,9 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
const splitMessages = (settings as any)?.splitMessages ?? false;
|
const splitMessages = (settings as any)?.splitMessages ?? false;
|
||||||
const timePerChar = (settings as any)?.timePerChar ?? 0;
|
|
||||||
const minDelay = 1000;
|
|
||||||
const maxDelay = 20000;
|
|
||||||
|
|
||||||
while ((match = linkRegex.exec(message)) !== null) {
|
while ((match = linkRegex.exec(message)) !== null) {
|
||||||
const [fullMatch, exclamation, altText, url] = match;
|
const [, , altText, url] = match;
|
||||||
const mediaType = this.getMediaType(url);
|
const mediaType = this.getMediaType(url);
|
||||||
const beforeText = message.slice(lastIndex, match.index);
|
const beforeText = message.slice(lastIndex, match.index);
|
||||||
|
|
||||||
@ -240,38 +248,26 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
|
|
||||||
// Handle sending the media
|
// Handle sending the media
|
||||||
try {
|
try {
|
||||||
switch (mediaType) {
|
if (mediaType === 'audio') {
|
||||||
case 'image':
|
await instance.audioWhatsapp({
|
||||||
await instance.mediaMessage({
|
number: remoteJid.split('@')[0],
|
||||||
|
delay: (settings as any)?.delayMessage || 1000,
|
||||||
|
audio: url,
|
||||||
|
caption: altText,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await instance.mediaMessage(
|
||||||
|
{
|
||||||
number: remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
delay: (settings as any)?.delayMessage || 1000,
|
||||||
|
mediatype: mediaType,
|
||||||
|
media: url,
|
||||||
caption: altText,
|
caption: altText,
|
||||||
media: url,
|
fileName: mediaType === 'document' ? altText || 'document' : undefined,
|
||||||
});
|
},
|
||||||
break;
|
null,
|
||||||
case 'video':
|
false,
|
||||||
await instance.mediaMessage({
|
);
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
|
||||||
caption: altText,
|
|
||||||
media: url,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'document':
|
|
||||||
await instance.documentMessage({
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
|
||||||
fileName: altText || 'document',
|
|
||||||
media: url,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'audio':
|
|
||||||
await instance.audioMessage({
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
|
||||||
media: url,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error sending media: ${error}`);
|
this.logger.error(`Error sending media: ${error}`);
|
||||||
@ -283,12 +279,15 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
textBuffer += `[${altText}](${url})`;
|
textBuffer += `[${altText}](${url})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndex = match.index + fullMatch.length;
|
lastIndex = linkRegex.lastIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any remaining text after the last match
|
// Add any remaining text after the last match
|
||||||
if (lastIndex < message.length) {
|
if (lastIndex < message.length) {
|
||||||
textBuffer += message.slice(lastIndex);
|
const remainingText = message.slice(lastIndex);
|
||||||
|
if (remainingText.trim()) {
|
||||||
|
textBuffer += remainingText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send any remaining text
|
// Send any remaining text
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { EvoaiDto } from '@api/integrations/chatbot/evoai/dto/evoai.dto';
|
import { EvoaiDto } from '@api/integrations/chatbot/evoai/dto/evoai.dto';
|
||||||
import { EvoaiService } from '@api/integrations/chatbot/evoai/services/evoai.service';
|
import { EvoaiService } from '@api/integrations/chatbot/evoai/services/evoai.service';
|
||||||
@ -9,7 +8,7 @@ import { Logger } from '@config/logger.config';
|
|||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import { Evoai as EvoaiModel, IntegrationSession } from '@prisma/client';
|
import { Evoai as EvoaiModel, IntegrationSession } from '@prisma/client';
|
||||||
|
|
||||||
import { BaseChatbotController, ChatbotSettings } from '../../base-chatbot.controller';
|
import { BaseChatbotController } from '../../base-chatbot.controller';
|
||||||
|
|
||||||
export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto> {
|
export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto> {
|
||||||
constructor(
|
constructor(
|
||||||
@ -34,7 +33,7 @@ export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto>
|
|||||||
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
||||||
|
|
||||||
protected getFallbackBotId(settings: any): string | undefined {
|
protected getFallbackBotId(settings: any): string | undefined {
|
||||||
return settings?.fallbackId;
|
return settings?.evoaiIdFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFallbackFieldName(): string {
|
protected getFallbackFieldName(): string {
|
||||||
@ -168,6 +167,6 @@ export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto>
|
|||||||
pushName?: string,
|
pushName?: string,
|
||||||
msg?: any,
|
msg?: any,
|
||||||
) {
|
) {
|
||||||
this.evoaiService.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
|
await this.evoaiService.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { EvolutionBot } from '@prisma/client';
|
import { EvolutionBot, IntegrationSession } from '@prisma/client';
|
||||||
import { getConversationMessage } from '@utils/getConversationMessage';
|
|
||||||
|
|
||||||
import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller';
|
import { BaseChatbotController } from '../../base-chatbot.controller';
|
||||||
import { EvolutionBotDto } from '../dto/evolutionBot.dto';
|
import { EvolutionBotDto } from '../dto/evolutionBot.dto';
|
||||||
import { EvolutionBotService } from '../services/evolutionBot.service';
|
import { EvolutionBotService } from '../services/evolutionBot.service';
|
||||||
|
|
||||||
export class EvolutionBotController extends ChatbotController implements ChatbotControllerInterface {
|
export class EvolutionBotController extends BaseChatbotController<EvolutionBot, EvolutionBotDto> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly evolutionBotService: EvolutionBotService,
|
private readonly evolutionBotService: EvolutionBotService,
|
||||||
prismaRepository: PrismaRepository,
|
prismaRepository: PrismaRepository,
|
||||||
@ -24,258 +21,49 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
|||||||
}
|
}
|
||||||
|
|
||||||
public readonly logger = new Logger('EvolutionBotController');
|
public readonly logger = new Logger('EvolutionBotController');
|
||||||
|
protected readonly integrationName = 'EvolutionBot';
|
||||||
|
|
||||||
integrationEnabled: boolean;
|
integrationEnabled = true; // Set to true by default or use config value if available
|
||||||
botRepository: any;
|
botRepository: any;
|
||||||
settingsRepository: any;
|
settingsRepository: any;
|
||||||
sessionRepository: any;
|
sessionRepository: any;
|
||||||
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
||||||
|
|
||||||
// Bots
|
// Implementation of abstract methods required by BaseChatbotController
|
||||||
public async createBot(instance: InstanceDto, data: EvolutionBotDto) {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
if (
|
protected getFallbackBotId(settings: any): string | undefined {
|
||||||
!data.expire ||
|
return settings?.botIdFallback;
|
||||||
!data.keywordFinish ||
|
|
||||||
!data.delayMessage ||
|
|
||||||
!data.unknownMessage ||
|
|
||||||
!data.listeningFromMe ||
|
|
||||||
!data.stopBotFromMe ||
|
|
||||||
!data.keepOpen ||
|
|
||||||
!data.debounceTime ||
|
|
||||||
!data.ignoreJids ||
|
|
||||||
!data.splitMessages ||
|
|
||||||
!data.timePerChar
|
|
||||||
) {
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
|
||||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
|
||||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
|
||||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
|
||||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
|
||||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
|
||||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
|
||||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
|
||||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
|
||||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
|
||||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
|
||||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
|
||||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
|
||||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
|
||||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
|
||||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
|
||||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
|
||||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
|
||||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
|
||||||
|
|
||||||
if (!defaultSettingCheck) {
|
|
||||||
await this.settings(instance, {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll && data.triggerType === 'all') {
|
|
||||||
throw new Error('You already have a dify with an "All" trigger, you cannot have more bots while it is active');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Dify already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.create({
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
instanceId: instanceId,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error creating bot');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findBot(instance: InstanceDto) {
|
protected getFallbackFieldName(): string {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return 'botIdFallback';
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bots = await this.botRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bots.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchBot(instance: InstanceDto, botId: string) {
|
protected getIntegrationType(): string {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return 'evolution';
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateBot(instance: InstanceDto, botId: string, data: EvolutionBotDto) {
|
protected getAdditionalBotData(data: EvolutionBotDto): Record<string, any> {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return {
|
||||||
.findFirst({
|
apiUrl: data.apiUrl,
|
||||||
where: {
|
apiKey: data.apiKey,
|
||||||
name: instance.instanceName,
|
};
|
||||||
},
|
}
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
// Implementation for bot-specific updates
|
||||||
where: {
|
protected getAdditionalUpdateFields(data: EvolutionBotDto): Record<string, any> {
|
||||||
id: botId,
|
return {
|
||||||
},
|
apiUrl: data.apiUrl,
|
||||||
});
|
apiKey: data.apiKey,
|
||||||
|
};
|
||||||
if (!bot) {
|
}
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'all') {
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
id: {
|
|
||||||
not: botId,
|
|
||||||
},
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll) {
|
|
||||||
throw new Error('You already have a bot with an "All" trigger, you cannot have more bots while it is active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Implementation for bot-specific duplicate validation on update
|
||||||
|
protected async validateNoDuplicatesOnUpdate(
|
||||||
|
botId: string,
|
||||||
|
instanceId: string,
|
||||||
|
data: EvolutionBotDto,
|
||||||
|
): Promise<void> {
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
const checkDuplicate = await this.botRepository.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@ -288,573 +76,20 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (checkDuplicate) {
|
if (checkDuplicate) {
|
||||||
throw new Error('Bot already exists');
|
throw new Error('Evolution Bot already exists');
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: { not: botId },
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: { not: botId },
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.update({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
instanceId: instanceId,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error updating bot');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteBot(instance: InstanceDto, botId: string) {
|
// Process bot-specific logic
|
||||||
const instanceId = await this.prismaRepository.instance
|
protected async processBot(
|
||||||
.findFirst({
|
instance: any,
|
||||||
where: {
|
remoteJid: string,
|
||||||
name: instance.instanceName,
|
bot: EvolutionBot,
|
||||||
},
|
session: IntegrationSession,
|
||||||
})
|
settings: any,
|
||||||
.then((instance) => instance.id);
|
content: string,
|
||||||
|
pushName?: string,
|
||||||
const bot = await this.botRepository.findFirst({
|
) {
|
||||||
where: {
|
await this.evolutionBotService.process(instance, remoteJid, bot, session, settings, content, pushName);
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.botRepository.delete({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { bot: { id: botId } };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error deleting bot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
public async settings(instance: InstanceDto, data: any) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (settings) {
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
botIdFallback: data.botIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: updateSettings.expire,
|
|
||||||
keywordFinish: updateSettings.keywordFinish,
|
|
||||||
delayMessage: updateSettings.delayMessage,
|
|
||||||
unknownMessage: updateSettings.unknownMessage,
|
|
||||||
listeningFromMe: updateSettings.listeningFromMe,
|
|
||||||
stopBotFromMe: updateSettings.stopBotFromMe,
|
|
||||||
keepOpen: updateSettings.keepOpen,
|
|
||||||
debounceTime: updateSettings.debounceTime,
|
|
||||||
botIdFallback: updateSettings.botIdFallback,
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
splitMessages: updateSettings.splitMessages,
|
|
||||||
timePerChar: updateSettings.timePerChar,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSetttings = await this.settingsRepository.create({
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
botIdFallback: data.botIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: newSetttings.expire,
|
|
||||||
keywordFinish: newSetttings.keywordFinish,
|
|
||||||
delayMessage: newSetttings.delayMessage,
|
|
||||||
unknownMessage: newSetttings.unknownMessage,
|
|
||||||
listeningFromMe: newSetttings.listeningFromMe,
|
|
||||||
stopBotFromMe: newSetttings.stopBotFromMe,
|
|
||||||
keepOpen: newSetttings.keepOpen,
|
|
||||||
debounceTime: newSetttings.debounceTime,
|
|
||||||
botIdFallback: newSetttings.botIdFallback,
|
|
||||||
ignoreJids: newSetttings.ignoreJids,
|
|
||||||
splitMessages: newSetttings.splitMessages,
|
|
||||||
timePerChar: newSetttings.timePerChar,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSettings(instance: InstanceDto) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Fallback: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
return {
|
|
||||||
expire: 0,
|
|
||||||
keywordFinish: '',
|
|
||||||
delayMessage: 0,
|
|
||||||
unknownMessage: '',
|
|
||||||
listeningFromMe: false,
|
|
||||||
stopBotFromMe: false,
|
|
||||||
keepOpen: false,
|
|
||||||
ignoreJids: [],
|
|
||||||
splitMessages: false,
|
|
||||||
timePerChar: 0,
|
|
||||||
botIdFallback: '',
|
|
||||||
fallback: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: settings.expire,
|
|
||||||
keywordFinish: settings.keywordFinish,
|
|
||||||
delayMessage: settings.delayMessage,
|
|
||||||
unknownMessage: settings.unknownMessage,
|
|
||||||
listeningFromMe: settings.listeningFromMe,
|
|
||||||
stopBotFromMe: settings.stopBotFromMe,
|
|
||||||
keepOpen: settings.keepOpen,
|
|
||||||
ignoreJids: settings.ignoreJids,
|
|
||||||
splitMessages: settings.splitMessages,
|
|
||||||
timePerChar: settings.timePerChar,
|
|
||||||
botIdFallback: settings.botIdFallback,
|
|
||||||
fallback: settings.Fallback,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sessions
|
|
||||||
public async changeStatus(instance: InstanceDto, data: any) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const remoteJid = data.remoteJid;
|
|
||||||
const status = data.status;
|
|
||||||
|
|
||||||
if (status === 'delete') {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { bot: { remoteJid: remoteJid, status: status } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'closed') {
|
|
||||||
if (defaultSettingCheck?.keepOpen) {
|
|
||||||
await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { bot: { ...instance, bot: { remoteJid: remoteJid, status: status } } };
|
|
||||||
} else {
|
|
||||||
const session = await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: status,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const botData = {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
status: status,
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { bot: { ...instance, bot: botData } };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error changing status');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (bot && bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Dify not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.sessionRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid,
|
|
||||||
botId: bot ? botId : { not: null },
|
|
||||||
type: 'evolution',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching sessions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
throw new Error('Settings not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
let ignoreJids: any = settings?.ignoreJids || [];
|
|
||||||
|
|
||||||
if (data.action === 'add') {
|
|
||||||
if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids };
|
|
||||||
|
|
||||||
ignoreJids.push(data.remoteJid);
|
|
||||||
} else {
|
|
||||||
ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
ignoreJids: ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit
|
|
||||||
public async emit({ instance, remoteJid, msg }: EmitData) {
|
|
||||||
try {
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return;
|
|
||||||
|
|
||||||
const session = await this.getSession(remoteJid, instance);
|
|
||||||
|
|
||||||
const content = getConversationMessage(msg);
|
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as EvolutionBot;
|
|
||||||
|
|
||||||
if (!findBot) {
|
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fallback?.botIdFallback) {
|
|
||||||
const findFallback = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: fallback.botIdFallback,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
findBot = findFallback;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expire = findBot?.expire;
|
|
||||||
let keywordFinish = findBot?.keywordFinish;
|
|
||||||
let delayMessage = findBot?.delayMessage;
|
|
||||||
let unknownMessage = findBot?.unknownMessage;
|
|
||||||
let listeningFromMe = findBot?.listeningFromMe;
|
|
||||||
let stopBotFromMe = findBot?.stopBotFromMe;
|
|
||||||
let keepOpen = findBot?.keepOpen;
|
|
||||||
let debounceTime = findBot?.debounceTime;
|
|
||||||
let ignoreJids = findBot?.ignoreJids;
|
|
||||||
let splitMessages = findBot?.splitMessages;
|
|
||||||
let timePerChar = findBot?.timePerChar;
|
|
||||||
|
|
||||||
if (expire === undefined || expire === null) expire = settings.expire;
|
|
||||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
|
||||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
|
||||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
|
||||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
|
||||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
|
||||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
|
||||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
|
||||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
|
||||||
|
|
||||||
const key = msg.key as {
|
|
||||||
id: string;
|
|
||||||
remoteJid: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
participant: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stopBotFromMe && key.fromMe && session) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'paused',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!listeningFromMe && key.fromMe) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session && !session.awaitUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debounceTime && debounceTime > 0) {
|
|
||||||
this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => {
|
|
||||||
await this.evolutionBotService.processBot(
|
|
||||||
this.waMonitor.waInstances[instance.instanceName],
|
|
||||||
remoteJid,
|
|
||||||
findBot,
|
|
||||||
session,
|
|
||||||
{
|
|
||||||
...settings,
|
|
||||||
expire,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
debounceTime,
|
|
||||||
ignoreJids,
|
|
||||||
splitMessages,
|
|
||||||
timePerChar,
|
|
||||||
},
|
|
||||||
debouncedContent,
|
|
||||||
msg?.pushName,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.evolutionBotService.processBot(
|
|
||||||
this.waMonitor.waInstances[instance.instanceName],
|
|
||||||
remoteJid,
|
|
||||||
findBot,
|
|
||||||
session,
|
|
||||||
{
|
|
||||||
...settings,
|
|
||||||
expire,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
debounceTime,
|
|
||||||
ignoreJids,
|
|
||||||
splitMessages,
|
|
||||||
timePerChar,
|
|
||||||
},
|
|
||||||
content,
|
|
||||||
msg?.pushName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { TriggerOperator, TriggerType } from '@prisma/client';
|
import { TriggerOperator, TriggerType } from '@prisma/client';
|
||||||
|
|
||||||
export class EvolutionBotDto {
|
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
|
||||||
|
|
||||||
|
export class EvolutionBotDto extends BaseChatbotDto {
|
||||||
|
apiUrl: string;
|
||||||
|
apiKey: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
description?: string;
|
|
||||||
apiUrl?: string;
|
|
||||||
apiKey?: string;
|
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
keywordFinish?: string | null;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
stopBotFromMe?: boolean;
|
stopBotFromMe?: boolean;
|
||||||
keepOpen?: boolean;
|
keepOpen?: boolean;
|
||||||
debounceTime?: number;
|
debounceTime?: number;
|
||||||
triggerType?: TriggerType;
|
triggerType: TriggerType;
|
||||||
triggerOperator?: TriggerOperator;
|
triggerOperator?: TriggerOperator;
|
||||||
triggerValue?: string;
|
triggerValue?: string;
|
||||||
ignoreJids?: any;
|
ignoreJids?: any;
|
||||||
@ -21,9 +22,9 @@ export class EvolutionBotDto {
|
|||||||
timePerChar?: number;
|
timePerChar?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EvolutionBotSettingDto {
|
export class EvolutionBotSettingDto extends BaseChatbotSettingDto {
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
keywordFinish?: string | null;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
|
@ -1,428 +1,103 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { Integration } from '@api/types/wa.types';
|
import { Integration } from '@api/types/wa.types';
|
||||||
import { Auth, ConfigService, HttpServer } from '@config/env.config';
|
import { Auth, ConfigService, HttpServer } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
|
||||||
import { EvolutionBot, EvolutionBotSetting, IntegrationSession } from '@prisma/client';
|
import { EvolutionBot, EvolutionBotSetting, IntegrationSession } from '@prisma/client';
|
||||||
import { sendTelemetry } from '@utils/sendTelemetry';
|
import { sendTelemetry } from '@utils/sendTelemetry';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export class EvolutionBotService {
|
import { BaseChatbotService } from '../../base-chatbot.service';
|
||||||
constructor(
|
|
||||||
private readonly waMonitor: WAMonitoringService,
|
|
||||||
private readonly configService: ConfigService,
|
|
||||||
private readonly prismaRepository: PrismaRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private readonly logger = new Logger('EvolutionBotService');
|
export class EvolutionBotService extends BaseChatbotService<EvolutionBot, EvolutionBotSetting> {
|
||||||
|
constructor(waMonitor: WAMonitoringService, configService: ConfigService, prismaRepository: PrismaRepository) {
|
||||||
public async createNewSession(instance: InstanceDto, data: any) {
|
super(waMonitor, prismaRepository, 'EvolutionBotService', configService);
|
||||||
try {
|
|
||||||
const session = await this.prismaRepository.integrationSession.create({
|
|
||||||
data: {
|
|
||||||
remoteJid: data.remoteJid,
|
|
||||||
pushName: data.pushName,
|
|
||||||
sessionId: data.remoteJid,
|
|
||||||
status: 'opened',
|
|
||||||
awaitUser: false,
|
|
||||||
botId: data.botId,
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
type: 'evolution',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { session };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isImageMessage(content: string) {
|
/**
|
||||||
return content.includes('imageMessage');
|
* Get the bot type identifier
|
||||||
|
*/
|
||||||
|
protected getBotType(): string {
|
||||||
|
return 'evolution';
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendMessageToBot(
|
/**
|
||||||
|
* Send a message to the Evolution Bot API
|
||||||
|
*/
|
||||||
|
protected async sendMessageToBot(
|
||||||
instance: any,
|
instance: any,
|
||||||
session: IntegrationSession,
|
session: IntegrationSession,
|
||||||
|
settings: EvolutionBotSetting,
|
||||||
bot: EvolutionBot,
|
bot: EvolutionBot,
|
||||||
remoteJid: string,
|
remoteJid: string,
|
||||||
pushName: string,
|
pushName: string,
|
||||||
content: string,
|
content: string,
|
||||||
) {
|
msg?: any,
|
||||||
const payload: any = {
|
): Promise<void> {
|
||||||
inputs: {
|
try {
|
||||||
sessionId: session.id,
|
const payload: any = {
|
||||||
remoteJid: remoteJid,
|
inputs: {
|
||||||
pushName: pushName,
|
sessionId: session.id,
|
||||||
instanceName: instance.instanceName,
|
remoteJid: remoteJid,
|
||||||
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
pushName: pushName,
|
||||||
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
|
fromMe: msg?.key?.fromMe,
|
||||||
},
|
instanceName: instance.instanceName,
|
||||||
query: content,
|
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
||||||
conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId,
|
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
|
||||||
user: remoteJid,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isImageMessage(content)) {
|
|
||||||
const contentSplit = content.split('|');
|
|
||||||
|
|
||||||
payload.files = [
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
url: contentSplit[1].split('?')[0],
|
|
||||||
},
|
},
|
||||||
];
|
query: content,
|
||||||
payload.query = contentSplit[2] || content;
|
conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId,
|
||||||
}
|
user: remoteJid,
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers: any = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bot.apiKey) {
|
|
||||||
headers = {
|
|
||||||
...headers,
|
|
||||||
Authorization: `Bearer ${bot.apiKey}`,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post(bot.apiUrl, payload, {
|
if (this.isImageMessage(content)) {
|
||||||
headers,
|
const contentSplit = content.split('|');
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS)
|
payload.files = [
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
|
|
||||||
const message = response?.data?.message;
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendMessageWhatsApp(
|
|
||||||
instance: any,
|
|
||||||
remoteJid: string,
|
|
||||||
session: IntegrationSession,
|
|
||||||
settings: EvolutionBotSetting,
|
|
||||||
message: string,
|
|
||||||
) {
|
|
||||||
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
|
|
||||||
|
|
||||||
let textBuffer = '';
|
|
||||||
let lastIndex = 0;
|
|
||||||
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
|
|
||||||
const getMediaType = (url: string): string | null => {
|
|
||||||
const extension = url.split('.').pop()?.toLowerCase();
|
|
||||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
|
||||||
const audioExtensions = ['mp3', 'wav', 'aac', 'ogg'];
|
|
||||||
const videoExtensions = ['mp4', 'avi', 'mkv', 'mov'];
|
|
||||||
const documentExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
|
|
||||||
|
|
||||||
if (imageExtensions.includes(extension || '')) return 'image';
|
|
||||||
if (audioExtensions.includes(extension || '')) return 'audio';
|
|
||||||
if (videoExtensions.includes(extension || '')) return 'video';
|
|
||||||
if (documentExtensions.includes(extension || '')) return 'document';
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
while ((match = linkRegex.exec(message)) !== null) {
|
|
||||||
const [fullMatch, exclMark, altText, url] = match;
|
|
||||||
const mediaType = getMediaType(url);
|
|
||||||
|
|
||||||
const beforeText = message.slice(lastIndex, match.index);
|
|
||||||
if (beforeText) {
|
|
||||||
textBuffer += beforeText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType) {
|
|
||||||
const splitMessages = settings.splitMessages ?? false;
|
|
||||||
const timePerChar = settings.timePerChar ?? 0;
|
|
||||||
const minDelay = 1000;
|
|
||||||
const maxDelay = 20000;
|
|
||||||
|
|
||||||
if (textBuffer.trim()) {
|
|
||||||
if (splitMessages) {
|
|
||||||
const multipleMessages = textBuffer.trim().split('\n\n');
|
|
||||||
|
|
||||||
for (let index = 0; index < multipleMessages.length; index++) {
|
|
||||||
const message = multipleMessages[index];
|
|
||||||
|
|
||||||
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: textBuffer.trim(),
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
textBuffer = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
|
||||||
await instance.audioWhatsapp({
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
audio: url,
|
|
||||||
caption: altText,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await instance.mediaMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
mediatype: mediaType,
|
|
||||||
media: url,
|
|
||||||
caption: altText,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
textBuffer += `[${altText}](${url})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = linkRegex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastIndex < message.length) {
|
|
||||||
const remainingText = message.slice(lastIndex);
|
|
||||||
if (remainingText.trim()) {
|
|
||||||
textBuffer += remainingText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const splitMessages = settings.splitMessages ?? false;
|
|
||||||
const timePerChar = settings.timePerChar ?? 0;
|
|
||||||
const minDelay = 1000;
|
|
||||||
const maxDelay = 20000;
|
|
||||||
|
|
||||||
if (textBuffer.trim()) {
|
|
||||||
if (splitMessages) {
|
|
||||||
const multipleMessages = textBuffer.trim().split('\n\n');
|
|
||||||
|
|
||||||
for (let index = 0; index < multipleMessages.length; index++) {
|
|
||||||
const message = multipleMessages[index];
|
|
||||||
|
|
||||||
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
{
|
||||||
number: remoteJid.split('@')[0],
|
type: 'image',
|
||||||
delay: settings?.delayMessage || 1000,
|
url: contentSplit[1].split('?')[0],
|
||||||
text: textBuffer.trim(),
|
|
||||||
},
|
},
|
||||||
false,
|
];
|
||||||
);
|
payload.query = contentSplit[2] || content;
|
||||||
}
|
}
|
||||||
textBuffer = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
||||||
|
await instance.client.presenceSubscribe(remoteJid);
|
||||||
|
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
||||||
|
}
|
||||||
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
let headers: any = {
|
||||||
where: {
|
'Content-Type': 'application/json',
|
||||||
id: session.id,
|
};
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'opened',
|
|
||||||
awaitUser: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async initNewSession(
|
if (bot.apiKey) {
|
||||||
instance: any,
|
headers = {
|
||||||
remoteJid: string,
|
...headers,
|
||||||
bot: EvolutionBot,
|
Authorization: `Bearer ${bot.apiKey}`,
|
||||||
settings: EvolutionBotSetting,
|
};
|
||||||
session: IntegrationSession,
|
}
|
||||||
content: string,
|
|
||||||
pushName?: string,
|
|
||||||
) {
|
|
||||||
const data = await this.createNewSession(instance, {
|
|
||||||
remoteJid,
|
|
||||||
pushName,
|
|
||||||
botId: bot.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.session) {
|
const response = await axios.post(bot.apiUrl, payload, {
|
||||||
session = data.session;
|
headers,
|
||||||
}
|
});
|
||||||
|
|
||||||
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
|
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
||||||
|
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
||||||
|
}
|
||||||
|
|
||||||
if (!message) return;
|
const message = response?.data?.message;
|
||||||
|
|
||||||
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
|
if (message) {
|
||||||
|
// Use the base class method to send the message to WhatsApp
|
||||||
|
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
// Send telemetry
|
||||||
}
|
sendTelemetry('/message/sendText');
|
||||||
|
} catch (error) {
|
||||||
public async processBot(
|
this.logger.error(`Error in sendMessageToBot: ${error.message || JSON.stringify(error)}`);
|
||||||
instance: any,
|
|
||||||
remoteJid: string,
|
|
||||||
bot: EvolutionBot,
|
|
||||||
session: IntegrationSession,
|
|
||||||
settings: EvolutionBotSetting,
|
|
||||||
content: string,
|
|
||||||
pushName?: string,
|
|
||||||
) {
|
|
||||||
if (session && session.status !== 'opened') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session && settings.expire && settings.expire > 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
const sessionUpdatedAt = new Date(session.updatedAt).getTime();
|
|
||||||
|
|
||||||
const diff = now - sessionUpdatedAt;
|
|
||||||
|
|
||||||
const diffInMinutes = Math.floor(diff / 1000 / 60);
|
|
||||||
|
|
||||||
if (diffInMinutes > settings.expire) {
|
|
||||||
if (settings.keepOpen) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: bot.id,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'opened',
|
|
||||||
awaitUser: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
if (settings.unknownMessage) {
|
|
||||||
this.waMonitor.waInstances[instance.instanceName].textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings.delayMessage || 1000,
|
|
||||||
text: settings.unknownMessage,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
|
|
||||||
if (settings.keepOpen) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: bot.id,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = await this.sendMessageToBot(instance, session, bot, remoteJid, pushName, content);
|
|
||||||
|
|
||||||
if (!message) return;
|
|
||||||
|
|
||||||
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { Flowise } from '@prisma/client';
|
import { Flowise, IntegrationSession } from '@prisma/client';
|
||||||
import { getConversationMessage } from '@utils/getConversationMessage';
|
|
||||||
|
|
||||||
import { ChatbotController, ChatbotControllerInterface, EmitData } from '../../chatbot.controller';
|
import { BaseChatbotController } from '../../base-chatbot.controller';
|
||||||
import { FlowiseDto } from '../dto/flowise.dto';
|
import { FlowiseDto } from '../dto/flowise.dto';
|
||||||
import { FlowiseService } from '../services/flowise.service';
|
import { FlowiseService } from '../services/flowise.service';
|
||||||
|
|
||||||
export class FlowiseController extends ChatbotController implements ChatbotControllerInterface {
|
export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly flowiseService: FlowiseService,
|
private readonly flowiseService: FlowiseService,
|
||||||
prismaRepository: PrismaRepository,
|
prismaRepository: PrismaRepository,
|
||||||
@ -24,258 +21,45 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public readonly logger = new Logger('FlowiseController');
|
public readonly logger = new Logger('FlowiseController');
|
||||||
|
protected readonly integrationName = 'Flowise';
|
||||||
|
|
||||||
integrationEnabled: boolean;
|
integrationEnabled = true; // Set to true by default or use config value if available
|
||||||
botRepository: any;
|
botRepository: any;
|
||||||
settingsRepository: any;
|
settingsRepository: any;
|
||||||
sessionRepository: any;
|
sessionRepository: any;
|
||||||
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
||||||
|
|
||||||
// Bots
|
// Implementation of abstract methods required by BaseChatbotController
|
||||||
public async createBot(instance: InstanceDto, data: FlowiseDto) {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
if (
|
protected getFallbackBotId(settings: any): string | undefined {
|
||||||
!data.expire ||
|
return settings?.flowiseIdFallback;
|
||||||
!data.keywordFinish ||
|
|
||||||
!data.delayMessage ||
|
|
||||||
!data.unknownMessage ||
|
|
||||||
!data.listeningFromMe ||
|
|
||||||
!data.stopBotFromMe ||
|
|
||||||
!data.keepOpen ||
|
|
||||||
!data.debounceTime ||
|
|
||||||
!data.ignoreJids ||
|
|
||||||
!data.splitMessages ||
|
|
||||||
!data.timePerChar
|
|
||||||
) {
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
|
||||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
|
||||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
|
||||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
|
||||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
|
||||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
|
||||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
|
||||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
|
||||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
|
||||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
|
||||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
|
||||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
|
||||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
|
||||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
|
||||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
|
||||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
|
||||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
|
||||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
|
||||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
|
||||||
|
|
||||||
if (!defaultSettingCheck) {
|
|
||||||
await this.settings(instance, {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll && data.triggerType === 'all') {
|
|
||||||
throw new Error('You already have a Flowise with an "All" trigger, you cannot have more bots while it is active');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Flowise already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.create({
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
instanceId: instanceId,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error creating bot');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findBot(instance: InstanceDto) {
|
protected getFallbackFieldName(): string {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return 'flowiseIdFallback';
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bots = await this.botRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bots.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchBot(instance: InstanceDto, botId: string) {
|
protected getIntegrationType(): string {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return 'flowise';
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateBot(instance: InstanceDto, botId: string, data: FlowiseDto) {
|
protected getAdditionalBotData(data: FlowiseDto): Record<string, any> {
|
||||||
const instanceId = await this.prismaRepository.instance
|
return {
|
||||||
.findFirst({
|
apiUrl: data.apiUrl,
|
||||||
where: {
|
apiKey: data.apiKey,
|
||||||
name: instance.instanceName,
|
};
|
||||||
},
|
}
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
// Implementation for bot-specific updates
|
||||||
where: {
|
protected getAdditionalUpdateFields(data: FlowiseDto): Record<string, any> {
|
||||||
id: botId,
|
return {
|
||||||
},
|
apiUrl: data.apiUrl,
|
||||||
});
|
apiKey: data.apiKey,
|
||||||
|
};
|
||||||
if (!bot) {
|
}
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'all') {
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
id: {
|
|
||||||
not: botId,
|
|
||||||
},
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll) {
|
|
||||||
throw new Error('You already have a bot with an "All" trigger, you cannot have more bots while it is active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Implementation for bot-specific duplicate validation on update
|
||||||
|
protected async validateNoDuplicatesOnUpdate(botId: string, instanceId: string, data: FlowiseDto): Promise<void> {
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
const checkDuplicate = await this.botRepository.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
@ -288,573 +72,20 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (checkDuplicate) {
|
if (checkDuplicate) {
|
||||||
throw new Error('Bot already exists');
|
throw new Error('Flowise already exists');
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: { not: botId },
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: { not: botId },
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.update({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
apiUrl: data.apiUrl,
|
|
||||||
apiKey: data.apiKey,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
instanceId: instanceId,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error updating bot');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteBot(instance: InstanceDto, botId: string) {
|
// Process bot-specific logic
|
||||||
const instanceId = await this.prismaRepository.instance
|
protected async processBot(
|
||||||
.findFirst({
|
instance: any,
|
||||||
where: {
|
remoteJid: string,
|
||||||
name: instance.instanceName,
|
bot: Flowise,
|
||||||
},
|
session: IntegrationSession,
|
||||||
})
|
settings: any,
|
||||||
.then((instance) => instance.id);
|
content: string,
|
||||||
|
pushName?: string,
|
||||||
const bot = await this.botRepository.findFirst({
|
) {
|
||||||
where: {
|
await this.flowiseService.process(instance, remoteJid, bot, session, settings, content, pushName);
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Bot not found');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.botRepository.delete({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { bot: { id: botId } };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error deleting bot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
public async settings(instance: InstanceDto, data: any) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (settings) {
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
flowiseIdFallback: data.flowiseIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: updateSettings.expire,
|
|
||||||
keywordFinish: updateSettings.keywordFinish,
|
|
||||||
delayMessage: updateSettings.delayMessage,
|
|
||||||
unknownMessage: updateSettings.unknownMessage,
|
|
||||||
listeningFromMe: updateSettings.listeningFromMe,
|
|
||||||
stopBotFromMe: updateSettings.stopBotFromMe,
|
|
||||||
keepOpen: updateSettings.keepOpen,
|
|
||||||
debounceTime: updateSettings.debounceTime,
|
|
||||||
flowiseIdFallback: updateSettings.flowiseIdFallback,
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
splitMessages: updateSettings.splitMessages,
|
|
||||||
timePerChar: updateSettings.timePerChar,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSetttings = await this.settingsRepository.create({
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
flowiseIdFallback: data.flowiseIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
instanceId: instanceId,
|
|
||||||
splitMessages: data.splitMessages,
|
|
||||||
timePerChar: data.timePerChar,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: newSetttings.expire,
|
|
||||||
keywordFinish: newSetttings.keywordFinish,
|
|
||||||
delayMessage: newSetttings.delayMessage,
|
|
||||||
unknownMessage: newSetttings.unknownMessage,
|
|
||||||
listeningFromMe: newSetttings.listeningFromMe,
|
|
||||||
stopBotFromMe: newSetttings.stopBotFromMe,
|
|
||||||
keepOpen: newSetttings.keepOpen,
|
|
||||||
debounceTime: newSetttings.debounceTime,
|
|
||||||
flowiseIdFallback: newSetttings.flowiseIdFallback,
|
|
||||||
ignoreJids: newSetttings.ignoreJids,
|
|
||||||
splitMessages: newSetttings.splitMessages,
|
|
||||||
timePerChar: newSetttings.timePerChar,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSettings(instance: InstanceDto) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Fallback: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
return {
|
|
||||||
expire: 0,
|
|
||||||
keywordFinish: '',
|
|
||||||
delayMessage: 0,
|
|
||||||
unknownMessage: '',
|
|
||||||
listeningFromMe: false,
|
|
||||||
stopBotFromMe: false,
|
|
||||||
keepOpen: false,
|
|
||||||
ignoreJids: [],
|
|
||||||
splitMessages: false,
|
|
||||||
timePerChar: 0,
|
|
||||||
flowiseIdFallback: '',
|
|
||||||
fallback: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: settings.expire,
|
|
||||||
keywordFinish: settings.keywordFinish,
|
|
||||||
delayMessage: settings.delayMessage,
|
|
||||||
unknownMessage: settings.unknownMessage,
|
|
||||||
listeningFromMe: settings.listeningFromMe,
|
|
||||||
stopBotFromMe: settings.stopBotFromMe,
|
|
||||||
keepOpen: settings.keepOpen,
|
|
||||||
ignoreJids: settings.ignoreJids,
|
|
||||||
splitMessages: settings.splitMessages,
|
|
||||||
timePerChar: settings.timePerChar,
|
|
||||||
flowiseIdFallback: settings.flowiseIdFallback,
|
|
||||||
fallback: settings.Fallback,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sessions
|
|
||||||
public async changeStatus(instance: InstanceDto, data: any) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const remoteJid = data.remoteJid;
|
|
||||||
const status = data.status;
|
|
||||||
|
|
||||||
if (status === 'delete') {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { bot: { remoteJid: remoteJid, status: status } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'closed') {
|
|
||||||
if (defaultSettingCheck?.keepOpen) {
|
|
||||||
await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { bot: { ...instance, bot: { remoteJid: remoteJid, status: status } } };
|
|
||||||
} else {
|
|
||||||
const session = await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: status,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const botData = {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
status: status,
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { bot: { ...instance, bot: botData } };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error changing status');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (bot && bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Dify not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.sessionRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid,
|
|
||||||
botId: bot ? botId : { not: null },
|
|
||||||
type: 'flowise',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching sessions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) {
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
throw new Error('Settings not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
let ignoreJids: any = settings?.ignoreJids || [];
|
|
||||||
|
|
||||||
if (data.action === 'add') {
|
|
||||||
if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids };
|
|
||||||
|
|
||||||
ignoreJids.push(data.remoteJid);
|
|
||||||
} else {
|
|
||||||
ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
ignoreJids: ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit
|
|
||||||
public async emit({ instance, remoteJid, msg }: EmitData) {
|
|
||||||
try {
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return;
|
|
||||||
|
|
||||||
const session = await this.getSession(remoteJid, instance);
|
|
||||||
|
|
||||||
const content = getConversationMessage(msg);
|
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as Flowise;
|
|
||||||
|
|
||||||
if (!findBot) {
|
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fallback?.flowiseIdFallback) {
|
|
||||||
const findFallback = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: fallback.flowiseIdFallback,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
findBot = findFallback;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let expire = findBot?.expire;
|
|
||||||
let keywordFinish = findBot?.keywordFinish;
|
|
||||||
let delayMessage = findBot?.delayMessage;
|
|
||||||
let unknownMessage = findBot?.unknownMessage;
|
|
||||||
let listeningFromMe = findBot?.listeningFromMe;
|
|
||||||
let stopBotFromMe = findBot?.stopBotFromMe;
|
|
||||||
let keepOpen = findBot?.keepOpen;
|
|
||||||
let debounceTime = findBot?.debounceTime;
|
|
||||||
let ignoreJids = findBot?.ignoreJids;
|
|
||||||
let splitMessages = findBot?.splitMessages;
|
|
||||||
let timePerChar = findBot?.timePerChar;
|
|
||||||
|
|
||||||
if (expire === undefined || expire === null) expire = settings.expire;
|
|
||||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
|
||||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
|
||||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
|
||||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
|
||||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
|
||||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
|
||||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
|
||||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
|
||||||
|
|
||||||
const key = msg.key as {
|
|
||||||
id: string;
|
|
||||||
remoteJid: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
participant: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stopBotFromMe && key.fromMe && session) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'paused',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!listeningFromMe && key.fromMe) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session && !session.awaitUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debounceTime && debounceTime > 0) {
|
|
||||||
this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => {
|
|
||||||
await this.flowiseService.processBot(
|
|
||||||
this.waMonitor.waInstances[instance.instanceName],
|
|
||||||
remoteJid,
|
|
||||||
findBot,
|
|
||||||
session,
|
|
||||||
{
|
|
||||||
...settings,
|
|
||||||
expire,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
debounceTime,
|
|
||||||
ignoreJids,
|
|
||||||
splitMessages,
|
|
||||||
timePerChar,
|
|
||||||
},
|
|
||||||
debouncedContent,
|
|
||||||
msg?.pushName,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.flowiseService.processBot(
|
|
||||||
this.waMonitor.waInstances[instance.instanceName],
|
|
||||||
remoteJid,
|
|
||||||
findBot,
|
|
||||||
session,
|
|
||||||
{
|
|
||||||
...settings,
|
|
||||||
expire,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
debounceTime,
|
|
||||||
ignoreJids,
|
|
||||||
splitMessages,
|
|
||||||
timePerChar,
|
|
||||||
},
|
|
||||||
content,
|
|
||||||
msg?.pushName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import { TriggerOperator, TriggerType } from '@prisma/client';
|
import { TriggerOperator, TriggerType } from '@prisma/client';
|
||||||
|
|
||||||
export class FlowiseDto {
|
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
|
||||||
|
|
||||||
|
export class FlowiseDto extends BaseChatbotDto {
|
||||||
|
apiUrl: string;
|
||||||
|
apiKey: string;
|
||||||
|
description: string;
|
||||||
|
keywordFinish?: string | null;
|
||||||
|
triggerType: TriggerType;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
description?: string;
|
|
||||||
apiUrl?: string;
|
|
||||||
apiKey?: string;
|
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
stopBotFromMe?: boolean;
|
stopBotFromMe?: boolean;
|
||||||
keepOpen?: boolean;
|
keepOpen?: boolean;
|
||||||
debounceTime?: number;
|
debounceTime?: number;
|
||||||
triggerType?: TriggerType;
|
|
||||||
triggerOperator?: TriggerOperator;
|
triggerOperator?: TriggerOperator;
|
||||||
triggerValue?: string;
|
triggerValue?: string;
|
||||||
ignoreJids?: any;
|
ignoreJids?: any;
|
||||||
@ -21,9 +23,9 @@ export class FlowiseDto {
|
|||||||
timePerChar?: number;
|
timePerChar?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FlowiseSettingDto {
|
export class FlowiseSettingDto extends BaseChatbotSettingDto {
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
keywordFinish?: string | null;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
|
@ -1,425 +1,111 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { Integration } from '@api/types/wa.types';
|
import { Integration } from '@api/types/wa.types';
|
||||||
import { Auth, ConfigService, HttpServer } from '@config/env.config';
|
import { Auth, ConfigService, HttpServer } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
|
||||||
import { Flowise, FlowiseSetting, IntegrationSession } from '@prisma/client';
|
import { Flowise, FlowiseSetting, IntegrationSession } from '@prisma/client';
|
||||||
import { sendTelemetry } from '@utils/sendTelemetry';
|
import { sendTelemetry } from '@utils/sendTelemetry';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export class FlowiseService {
|
import { BaseChatbotService } from '../../base-chatbot.service';
|
||||||
constructor(
|
|
||||||
private readonly waMonitor: WAMonitoringService,
|
|
||||||
private readonly configService: ConfigService,
|
|
||||||
private readonly prismaRepository: PrismaRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private readonly logger = new Logger('FlowiseService');
|
export class FlowiseService extends BaseChatbotService<Flowise, FlowiseSetting> {
|
||||||
|
constructor(waMonitor: WAMonitoringService, configService: ConfigService, prismaRepository: PrismaRepository) {
|
||||||
|
super(waMonitor, prismaRepository, 'FlowiseService', configService);
|
||||||
|
}
|
||||||
|
|
||||||
public async createNewSession(instance: InstanceDto, data: any) {
|
/**
|
||||||
|
* Get the bot type identifier
|
||||||
|
*/
|
||||||
|
protected getBotType(): string {
|
||||||
|
return 'flowise';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the Flowise API
|
||||||
|
*/
|
||||||
|
protected async sendMessageToBot(
|
||||||
|
instance: any,
|
||||||
|
session: IntegrationSession,
|
||||||
|
settings: FlowiseSetting,
|
||||||
|
bot: Flowise,
|
||||||
|
remoteJid: string,
|
||||||
|
pushName: string,
|
||||||
|
content: string,
|
||||||
|
msg?: any,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const session = await this.prismaRepository.integrationSession.create({
|
const payload: any = {
|
||||||
data: {
|
question: content,
|
||||||
remoteJid: data.remoteJid,
|
overrideConfig: {
|
||||||
pushName: data.pushName,
|
sessionId: remoteJid,
|
||||||
sessionId: data.remoteJid,
|
vars: {
|
||||||
status: 'opened',
|
remoteJid: remoteJid,
|
||||||
awaitUser: false,
|
pushName: pushName,
|
||||||
botId: data.botId,
|
instanceName: instance.instanceName,
|
||||||
instanceId: instance.instanceId,
|
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
||||||
type: 'flowise',
|
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { session };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isImageMessage(content: string) {
|
|
||||||
return content.includes('imageMessage');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendMessageToBot(instance: any, bot: Flowise, remoteJid: string, pushName: string, content: string) {
|
|
||||||
const payload: any = {
|
|
||||||
question: content,
|
|
||||||
overrideConfig: {
|
|
||||||
sessionId: remoteJid,
|
|
||||||
vars: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
pushName: pushName,
|
|
||||||
instanceName: instance.instanceName,
|
|
||||||
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
|
||||||
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isImageMessage(content)) {
|
|
||||||
const contentSplit = content.split('|');
|
|
||||||
|
|
||||||
payload.uploads = [
|
|
||||||
{
|
|
||||||
data: contentSplit[1].split('?')[0],
|
|
||||||
type: 'url',
|
|
||||||
name: 'Flowise.png',
|
|
||||||
mime: 'image/png',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
payload.question = contentSplit[2] || content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers: any = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bot.apiKey) {
|
|
||||||
headers = {
|
|
||||||
...headers,
|
|
||||||
Authorization: `Bearer ${bot.apiKey}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = bot.apiUrl;
|
|
||||||
|
|
||||||
if (!endpoint) return null;
|
|
||||||
|
|
||||||
const response = await axios.post(endpoint, payload, {
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS)
|
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
|
|
||||||
const message = response?.data?.text;
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendMessageWhatsApp(
|
|
||||||
instance: any,
|
|
||||||
remoteJid: string,
|
|
||||||
session: IntegrationSession,
|
|
||||||
settings: FlowiseSetting,
|
|
||||||
message: string,
|
|
||||||
) {
|
|
||||||
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
|
|
||||||
|
|
||||||
let textBuffer = '';
|
|
||||||
let lastIndex = 0;
|
|
||||||
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
|
|
||||||
const getMediaType = (url: string): string | null => {
|
|
||||||
const extension = url.split('.').pop()?.toLowerCase();
|
|
||||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
|
||||||
const audioExtensions = ['mp3', 'wav', 'aac', 'ogg'];
|
|
||||||
const videoExtensions = ['mp4', 'avi', 'mkv', 'mov'];
|
|
||||||
const documentExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
|
|
||||||
|
|
||||||
if (imageExtensions.includes(extension || '')) return 'image';
|
|
||||||
if (audioExtensions.includes(extension || '')) return 'audio';
|
|
||||||
if (videoExtensions.includes(extension || '')) return 'video';
|
|
||||||
if (documentExtensions.includes(extension || '')) return 'document';
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
while ((match = linkRegex.exec(message)) !== null) {
|
|
||||||
const [fullMatch, exclMark, altText, url] = match;
|
|
||||||
const mediaType = getMediaType(url);
|
|
||||||
|
|
||||||
const beforeText = message.slice(lastIndex, match.index);
|
|
||||||
if (beforeText) {
|
|
||||||
textBuffer += beforeText;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType) {
|
|
||||||
const splitMessages = settings.splitMessages ?? false;
|
|
||||||
const timePerChar = settings.timePerChar ?? 0;
|
|
||||||
const minDelay = 1000;
|
|
||||||
const maxDelay = 20000;
|
|
||||||
|
|
||||||
if (textBuffer.trim()) {
|
|
||||||
if (splitMessages) {
|
|
||||||
const multipleMessages = textBuffer.trim().split('\n\n');
|
|
||||||
|
|
||||||
for (let index = 0; index < multipleMessages.length; index++) {
|
|
||||||
const message = multipleMessages[index];
|
|
||||||
|
|
||||||
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: textBuffer.trim(),
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
textBuffer = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
|
||||||
await instance.audioWhatsapp({
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
audio: url,
|
|
||||||
caption: altText,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await instance.mediaMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
mediatype: mediaType,
|
|
||||||
media: url,
|
|
||||||
caption: altText,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
textBuffer += `[${altText}](${url})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = linkRegex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastIndex < message.length) {
|
|
||||||
const remainingText = message.slice(lastIndex);
|
|
||||||
if (remainingText.trim()) {
|
|
||||||
textBuffer += remainingText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const splitMessages = settings.splitMessages ?? false;
|
|
||||||
const timePerChar = settings.timePerChar ?? 0;
|
|
||||||
const minDelay = 1000;
|
|
||||||
const maxDelay = 20000;
|
|
||||||
|
|
||||||
if (textBuffer.trim()) {
|
|
||||||
if (splitMessages) {
|
|
||||||
const multipleMessages = textBuffer.trim().split('\n\n');
|
|
||||||
|
|
||||||
for (let index = 0; index < multipleMessages.length; index++) {
|
|
||||||
const message = multipleMessages[index];
|
|
||||||
|
|
||||||
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.presenceSubscribe(remoteJid);
|
|
||||||
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((resolve) => {
|
|
||||||
setTimeout(async () => {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await instance.textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings?.delayMessage || 1000,
|
|
||||||
text: textBuffer.trim(),
|
|
||||||
},
|
},
|
||||||
false,
|
},
|
||||||
);
|
};
|
||||||
|
|
||||||
|
if (this.isImageMessage(content)) {
|
||||||
|
const contentSplit = content.split('|');
|
||||||
|
|
||||||
|
payload.uploads = [
|
||||||
|
{
|
||||||
|
data: contentSplit[1].split('?')[0],
|
||||||
|
type: 'url',
|
||||||
|
name: 'Flowise.png',
|
||||||
|
mime: 'image/png',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
payload.question = contentSplit[2] || content;
|
||||||
}
|
}
|
||||||
textBuffer = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
||||||
|
await instance.client.presenceSubscribe(remoteJid);
|
||||||
|
await instance.client.sendPresenceUpdate('composing', remoteJid);
|
||||||
|
}
|
||||||
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
let headers: any = {
|
||||||
where: {
|
'Content-Type': 'application/json',
|
||||||
id: session.id,
|
};
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'opened',
|
|
||||||
awaitUser: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
if (bot.apiKey) {
|
||||||
}
|
headers = {
|
||||||
|
...headers,
|
||||||
|
Authorization: `Bearer ${bot.apiKey}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async initNewSession(
|
const endpoint = bot.apiUrl;
|
||||||
instance: any,
|
|
||||||
remoteJid: string,
|
|
||||||
bot: Flowise,
|
|
||||||
settings: FlowiseSetting,
|
|
||||||
session: IntegrationSession,
|
|
||||||
content: string,
|
|
||||||
pushName?: string,
|
|
||||||
) {
|
|
||||||
const data = await this.createNewSession(instance, {
|
|
||||||
remoteJid,
|
|
||||||
pushName,
|
|
||||||
botId: bot.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.session) {
|
if (!endpoint) {
|
||||||
session = data.session;
|
this.logger.error('No Flowise endpoint defined');
|
||||||
}
|
|
||||||
|
|
||||||
const message = await this.sendMessageToBot(instance, bot, remoteJid, pushName, content);
|
|
||||||
|
|
||||||
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async processBot(
|
|
||||||
instance: any,
|
|
||||||
remoteJid: string,
|
|
||||||
bot: Flowise,
|
|
||||||
session: IntegrationSession,
|
|
||||||
settings: FlowiseSetting,
|
|
||||||
content: string,
|
|
||||||
pushName?: string,
|
|
||||||
) {
|
|
||||||
if (session && session.status !== 'opened') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session && settings.expire && settings.expire > 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
const sessionUpdatedAt = new Date(session.updatedAt).getTime();
|
|
||||||
|
|
||||||
const diff = now - sessionUpdatedAt;
|
|
||||||
|
|
||||||
const diffInMinutes = Math.floor(diff / 1000 / 60);
|
|
||||||
|
|
||||||
if (diffInMinutes > settings.expire) {
|
|
||||||
if (settings.keepOpen) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: bot.id,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
const response = await axios.post(endpoint, payload, {
|
||||||
await this.initNewSession(instance, remoteJid, bot, settings, session, content, pushName);
|
headers,
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
||||||
where: {
|
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'opened',
|
|
||||||
awaitUser: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
if (settings.unknownMessage) {
|
|
||||||
this.waMonitor.waInstances[instance.instanceName].textMessage(
|
|
||||||
{
|
|
||||||
number: remoteJid.split('@')[0],
|
|
||||||
delay: settings.delayMessage || 1000,
|
|
||||||
text: settings.unknownMessage,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.keywordFinish && content.toLowerCase() === settings.keywordFinish.toLowerCase()) {
|
const message = response?.data?.text;
|
||||||
if (settings.keepOpen) {
|
|
||||||
await this.prismaRepository.integrationSession.update({
|
if (message) {
|
||||||
where: {
|
// Use the base class method to send the message to WhatsApp
|
||||||
id: session.id,
|
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'closed',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
|
||||||
where: {
|
|
||||||
botId: bot.id,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send telemetry
|
||||||
|
sendTelemetry('/message/sendText');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error in sendMessageToBot: ${error.message || JSON.stringify(error)}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await this.sendMessageToBot(instance, bot, remoteJid, pushName, content);
|
|
||||||
|
|
||||||
await this.sendMessageWhatsApp(instance, remoteJid, session, settings, message);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export class N8nDto extends BaseChatbotDto {
|
|||||||
triggerOperator?: TriggerOperator;
|
triggerOperator?: TriggerOperator;
|
||||||
triggerValue?: string;
|
triggerValue?: string;
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string[];
|
keywordFinish?: string;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
import { ConfigService } from '@config/env.config';
|
import { Auth, ConfigService, HttpServer } from '@config/env.config';
|
||||||
import { IntegrationSession, N8n, N8nSetting } from '@prisma/client';
|
import { IntegrationSession, N8n, N8nSetting } from '@prisma/client';
|
||||||
import { sendTelemetry } from '@utils/sendTelemetry';
|
import { sendTelemetry } from '@utils/sendTelemetry';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@ -104,26 +104,6 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to the N8n bot webhook.
|
|
||||||
*/
|
|
||||||
public async sendMessage(n8nId: string, chatInput: string, sessionId: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const bot = await this.prismaRepository.n8n.findFirst({ where: { id: n8nId, enabled: true } });
|
|
||||||
if (!bot) throw new Error('N8n bot not found or not enabled');
|
|
||||||
const headers: Record<string, string> = {};
|
|
||||||
if (bot.basicAuthUser && bot.basicAuthPass) {
|
|
||||||
const auth = Buffer.from(`${bot.basicAuthUser}:${bot.basicAuthPass}`).toString('base64');
|
|
||||||
headers['Authorization'] = `Basic ${auth}`;
|
|
||||||
}
|
|
||||||
const response = await axios.post(bot.webhookUrl, { chatInput, sessionId }, { headers });
|
|
||||||
return response.data.output;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error sending message to n8n bot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createNewSession(instance: InstanceDto, data: any) {
|
public async createNewSession(instance: InstanceDto, data: any) {
|
||||||
return super.createNewSession(instance, data, 'n8n');
|
return super.createNewSession(instance, data, 'n8n');
|
||||||
}
|
}
|
||||||
@ -143,6 +123,12 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
|
|||||||
const payload: any = {
|
const payload: any = {
|
||||||
chatInput: content,
|
chatInput: content,
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
pushName: pushName,
|
||||||
|
fromMe: msg?.key?.fromMe,
|
||||||
|
instanceName: instance.instanceName,
|
||||||
|
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
||||||
|
apiKey: this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle audio messages
|
// Handle audio messages
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { OpenaiCredsDto, OpenaiDto } from '@api/integrations/chatbot/openai/dto/openai.dto';
|
import { OpenaiCredsDto, OpenaiDto } from '@api/integrations/chatbot/openai/dto/openai.dto';
|
||||||
import { OpenaiService } from '@api/integrations/chatbot/openai/services/openai.service';
|
import { OpenaiService } from '@api/integrations/chatbot/openai/services/openai.service';
|
||||||
|
@ -306,7 +306,24 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get thread ID from session or create new thread
|
// Get thread ID from session or create new thread
|
||||||
const threadId = session.sessionId === remoteJid ? (await this.client.beta.threads.create()).id : session.sessionId;
|
let threadId = session.sessionId;
|
||||||
|
|
||||||
|
// Create a new thread if one doesn't exist or invalid format
|
||||||
|
if (!threadId || threadId === remoteJid) {
|
||||||
|
const newThread = await this.client.beta.threads.create();
|
||||||
|
threadId = newThread.id;
|
||||||
|
|
||||||
|
// Save the new thread ID to the session
|
||||||
|
await this.prismaRepository.integrationSession.update({
|
||||||
|
where: {
|
||||||
|
id: session.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
sessionId: threadId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(`Created new thread ID: ${threadId} for session: ${session.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Add message to thread
|
// Add message to thread
|
||||||
await this.client.beta.threads.messages.create(threadId, messageData);
|
await this.client.beta.threads.messages.create(threadId, messageData);
|
||||||
@ -334,6 +351,7 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract the response text safely with type checking
|
// Extract the response text safely with type checking
|
||||||
|
let responseText = "I couldn't generate a proper response. Please try again.";
|
||||||
try {
|
try {
|
||||||
const messages = response?.data || [];
|
const messages = response?.data || [];
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0) {
|
||||||
@ -341,7 +359,7 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
|
|||||||
if (messageContent.length > 0) {
|
if (messageContent.length > 0) {
|
||||||
const textContent = messageContent[0];
|
const textContent = messageContent[0];
|
||||||
if (textContent && 'text' in textContent && textContent.text && 'value' in textContent.text) {
|
if (textContent && 'text' in textContent && textContent.text && 'value' in textContent.text) {
|
||||||
return textContent.text.value;
|
responseText = textContent.text.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,8 +367,20 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
|
|||||||
this.logger.error(`Error extracting response text: ${error}`);
|
this.logger.error(`Error extracting response text: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update session with the thread ID to ensure continuity
|
||||||
|
await this.prismaRepository.integrationSession.update({
|
||||||
|
where: {
|
||||||
|
id: session.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
status: 'opened',
|
||||||
|
awaitUser: true,
|
||||||
|
sessionId: threadId, // Ensure thread ID is saved consistently
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Return fallback message if unable to extract text
|
// Return fallback message if unable to extract text
|
||||||
return "I couldn't generate a proper response. Please try again.";
|
return responseText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { TypebotDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto';
|
import { TypebotDto } from '@api/integrations/chatbot/typebot/dto/typebot.dto';
|
||||||
import { TypebotService } from '@api/integrations/chatbot/typebot/services/typebot.service';
|
import { TypebotService } from '@api/integrations/chatbot/typebot/services/typebot.service';
|
||||||
@ -8,13 +7,12 @@ import { Events } from '@api/types/wa.types';
|
|||||||
import { configService, Typebot } from '@config/env.config';
|
import { configService, Typebot } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import { Typebot as TypebotModel } from '@prisma/client';
|
import { IntegrationSession, Typebot as TypebotModel } from '@prisma/client';
|
||||||
import { getConversationMessage } from '@utils/getConversationMessage';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { ChatbotController, ChatbotControllerInterface } from '../../chatbot.controller';
|
import { BaseChatbotController } from '../../base-chatbot.controller';
|
||||||
|
|
||||||
export class TypebotController extends ChatbotController implements ChatbotControllerInterface {
|
export class TypebotController extends BaseChatbotController<TypebotModel, TypebotDto> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly typebotService: TypebotService,
|
private readonly typebotService: TypebotService,
|
||||||
prismaRepository: PrismaRepository,
|
prismaRepository: PrismaRepository,
|
||||||
@ -28,6 +26,7 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public readonly logger = new Logger('TypebotController');
|
public readonly logger = new Logger('TypebotController');
|
||||||
|
protected readonly integrationName = 'Typebot';
|
||||||
|
|
||||||
integrationEnabled = configService.get<Typebot>('TYPEBOT').ENABLED;
|
integrationEnabled = configService.get<Typebot>('TYPEBOT').ENABLED;
|
||||||
botRepository: any;
|
botRepository: any;
|
||||||
@ -35,245 +34,35 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
sessionRepository: any;
|
sessionRepository: any;
|
||||||
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
|
||||||
|
|
||||||
// Bots
|
protected getFallbackBotId(settings: any): string | undefined {
|
||||||
public async createBot(instance: InstanceDto, data: TypebotDto) {
|
return settings?.typebotIdFallback;
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!data.expire ||
|
|
||||||
!data.keywordFinish ||
|
|
||||||
!data.delayMessage ||
|
|
||||||
!data.unknownMessage ||
|
|
||||||
!data.listeningFromMe ||
|
|
||||||
!data.stopBotFromMe ||
|
|
||||||
!data.keepOpen ||
|
|
||||||
!data.debounceTime ||
|
|
||||||
!data.ignoreJids
|
|
||||||
) {
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
|
||||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR';
|
|
||||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
|
||||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi';
|
|
||||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
|
||||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
|
||||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
|
||||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
|
||||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
|
||||||
|
|
||||||
if (!defaultSettingCheck) {
|
|
||||||
await this.settings(instance, {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll && data.triggerType === 'all') {
|
|
||||||
throw new Error('You already have a typebot with an "All" trigger, you cannot have more bots while it is active');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
url: data.url,
|
|
||||||
typebot: data.typebot,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Typebot already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.create({
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
url: data.url,
|
|
||||||
typebot: data.typebot,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
instanceId: instanceId,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error creating typebot');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findBot(instance: InstanceDto) {
|
protected getFallbackFieldName(): string {
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
return 'typebotIdFallback';
|
||||||
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bots = await this.botRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bots.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchBot(instance: InstanceDto, botId: string) {
|
protected getIntegrationType(): string {
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
return 'typebot';
|
||||||
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const bot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new Error('Typebot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Typebot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateBot(instance: InstanceDto, botId: string, data: TypebotDto) {
|
protected getAdditionalBotData(data: TypebotDto): Record<string, any> {
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
return {
|
||||||
|
url: data.url,
|
||||||
|
typebot: data.typebot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
// Implementation for bot-specific updates
|
||||||
.findFirst({
|
protected getAdditionalUpdateFields(data: TypebotDto): Record<string, any> {
|
||||||
where: {
|
return {
|
||||||
name: instance.instanceName,
|
url: data.url,
|
||||||
},
|
typebot: data.typebot,
|
||||||
})
|
};
|
||||||
.then((instance) => instance.id);
|
}
|
||||||
|
|
||||||
const typebot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!typebot) {
|
|
||||||
throw new Error('Typebot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typebot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Typebot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'all') {
|
|
||||||
const checkTriggerAll = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
enabled: true,
|
|
||||||
triggerType: 'all',
|
|
||||||
id: {
|
|
||||||
not: botId,
|
|
||||||
},
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkTriggerAll) {
|
|
||||||
throw new Error(
|
|
||||||
'You already have a typebot with an "All" trigger, you cannot have more bots while it is active',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Implementation for bot-specific duplicate validation on update
|
||||||
|
protected async validateNoDuplicatesOnUpdate(botId: string, instanceId: string, data: TypebotDto): Promise<void> {
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
const checkDuplicate = await this.botRepository.findFirst({
|
||||||
where: {
|
where: {
|
||||||
url: data.url,
|
url: data.url,
|
||||||
@ -288,263 +77,39 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
if (checkDuplicate) {
|
if (checkDuplicate) {
|
||||||
throw new Error('Typebot already exists');
|
throw new Error('Typebot already exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.triggerType === 'keyword') {
|
|
||||||
if (!data.triggerOperator || !data.triggerValue) {
|
|
||||||
throw new Error('Trigger operator and value are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: {
|
|
||||||
not: botId,
|
|
||||||
},
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.triggerType === 'advanced') {
|
|
||||||
if (!data.triggerValue) {
|
|
||||||
throw new Error('Trigger value is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDuplicate = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
id: { not: botId },
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (checkDuplicate) {
|
|
||||||
throw new Error('Trigger already exists');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const bot = await this.botRepository.update({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
enabled: data?.enabled,
|
|
||||||
description: data.description,
|
|
||||||
url: data.url,
|
|
||||||
typebot: data.typebot,
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
triggerType: data.triggerType,
|
|
||||||
triggerOperator: data.triggerOperator,
|
|
||||||
triggerValue: data.triggerValue,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error updating typebot');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteBot(instance: InstanceDto, botId: string) {
|
// Process Typebot-specific bot logic
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
protected async processBot(
|
||||||
|
instance: any,
|
||||||
const instanceId = await this.prismaRepository.instance
|
remoteJid: string,
|
||||||
.findFirst({
|
bot: TypebotModel,
|
||||||
where: {
|
session: IntegrationSession,
|
||||||
name: instance.instanceName,
|
settings: any,
|
||||||
},
|
content: string,
|
||||||
})
|
pushName?: string,
|
||||||
.then((instance) => instance.id);
|
msg?: any,
|
||||||
|
) {
|
||||||
const typebot = await this.botRepository.findFirst({
|
await this.typebotService.processTypebot(
|
||||||
where: {
|
instance,
|
||||||
id: botId,
|
remoteJid,
|
||||||
},
|
msg,
|
||||||
});
|
session,
|
||||||
|
bot,
|
||||||
if (!typebot) {
|
bot.url,
|
||||||
throw new Error('Typebot not found');
|
settings.expire,
|
||||||
}
|
bot.typebot,
|
||||||
|
settings.keywordFinish,
|
||||||
if (typebot.instanceId !== instanceId) {
|
settings.delayMessage,
|
||||||
throw new Error('Typebot not found');
|
settings.unknownMessage,
|
||||||
}
|
settings.listeningFromMe,
|
||||||
try {
|
settings.stopBotFromMe,
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
settings.keepOpen,
|
||||||
where: {
|
content,
|
||||||
botId: botId,
|
);
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.botRepository.delete({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { typebot: { id: botId } };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error deleting typebot');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// TypeBot specific method for starting a bot from API
|
||||||
public async settings(instance: InstanceDto, data: any) {
|
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (settings) {
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
typebotIdFallback: data.typebotIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: updateSettings.expire,
|
|
||||||
keywordFinish: updateSettings.keywordFinish,
|
|
||||||
delayMessage: updateSettings.delayMessage,
|
|
||||||
unknownMessage: updateSettings.unknownMessage,
|
|
||||||
listeningFromMe: updateSettings.listeningFromMe,
|
|
||||||
stopBotFromMe: updateSettings.stopBotFromMe,
|
|
||||||
keepOpen: updateSettings.keepOpen,
|
|
||||||
debounceTime: updateSettings.debounceTime,
|
|
||||||
typebotIdFallback: updateSettings.typebotIdFallback,
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSetttings = await this.settingsRepository.create({
|
|
||||||
data: {
|
|
||||||
expire: data.expire,
|
|
||||||
keywordFinish: data.keywordFinish,
|
|
||||||
delayMessage: data.delayMessage,
|
|
||||||
unknownMessage: data.unknownMessage,
|
|
||||||
listeningFromMe: data.listeningFromMe,
|
|
||||||
stopBotFromMe: data.stopBotFromMe,
|
|
||||||
keepOpen: data.keepOpen,
|
|
||||||
debounceTime: data.debounceTime,
|
|
||||||
typebotIdFallback: data.typebotIdFallback,
|
|
||||||
ignoreJids: data.ignoreJids,
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: newSetttings.expire,
|
|
||||||
keywordFinish: newSetttings.keywordFinish,
|
|
||||||
delayMessage: newSetttings.delayMessage,
|
|
||||||
unknownMessage: newSetttings.unknownMessage,
|
|
||||||
listeningFromMe: newSetttings.listeningFromMe,
|
|
||||||
stopBotFromMe: newSetttings.stopBotFromMe,
|
|
||||||
keepOpen: newSetttings.keepOpen,
|
|
||||||
debounceTime: newSetttings.debounceTime,
|
|
||||||
typebotIdFallback: newSetttings.typebotIdFallback,
|
|
||||||
ignoreJids: newSetttings.ignoreJids,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSettings(instance: InstanceDto) {
|
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Fallback: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
return {
|
|
||||||
expire: 0,
|
|
||||||
keywordFinish: '',
|
|
||||||
delayMessage: 0,
|
|
||||||
unknownMessage: '',
|
|
||||||
listeningFromMe: false,
|
|
||||||
stopBotFromMe: false,
|
|
||||||
keepOpen: false,
|
|
||||||
ignoreJids: [],
|
|
||||||
typebotIdFallback: null,
|
|
||||||
fallback: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
expire: settings.expire,
|
|
||||||
keywordFinish: settings.keywordFinish,
|
|
||||||
delayMessage: settings.delayMessage,
|
|
||||||
unknownMessage: settings.unknownMessage,
|
|
||||||
listeningFromMe: settings.listeningFromMe,
|
|
||||||
stopBotFromMe: settings.stopBotFromMe,
|
|
||||||
keepOpen: settings.keepOpen,
|
|
||||||
ignoreJids: settings.ignoreJids,
|
|
||||||
typebotIdFallback: settings.typebotIdFallback,
|
|
||||||
fallback: settings.Fallback,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sessions
|
|
||||||
public async startBot(instance: InstanceDto, data: any) {
|
public async startBot(instance: InstanceDto, data: any) {
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
||||||
|
|
||||||
@ -747,321 +312,4 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async changeStatus(instance: InstanceDto, data: any) {
|
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const remoteJid = data.remoteJid;
|
|
||||||
const status = data.status;
|
|
||||||
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (status === 'delete') {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
instanceId: instanceId,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'closed') {
|
|
||||||
if (defaultSettingCheck?.keepOpen) {
|
|
||||||
await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: status,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.sessionRepository.deleteMany({
|
|
||||||
where: {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
instanceId: instanceId,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { typebot: { ...instance, typebot: { remoteJid: remoteJid, status: status } } };
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await this.sessionRepository.updateMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
botId: { not: null },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: status,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const typebotData = {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
status: status,
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
|
||||||
|
|
||||||
return { typebot: { ...instance, typebot: typebotData } };
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error changing status');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) {
|
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const typebot = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: botId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typebot && typebot.instanceId !== instanceId) {
|
|
||||||
throw new Error('Typebot not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.sessionRepository.findMany({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
remoteJid,
|
|
||||||
botId: botId ?? { not: null },
|
|
||||||
type: 'typebot',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error fetching sessions');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) {
|
|
||||||
if (!this.integrationEnabled) throw new BadRequestException('Typebot is disabled');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceId = await this.prismaRepository.instance
|
|
||||||
.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((instance) => instance.id);
|
|
||||||
|
|
||||||
const settings = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
throw new Error('Settings not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
let ignoreJids: any = settings?.ignoreJids || [];
|
|
||||||
|
|
||||||
if (data.action === 'add') {
|
|
||||||
if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids };
|
|
||||||
|
|
||||||
ignoreJids.push(data.remoteJid);
|
|
||||||
} else {
|
|
||||||
ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSettings = await this.settingsRepository.update({
|
|
||||||
where: {
|
|
||||||
id: settings.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
ignoreJids: ignoreJids,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
ignoreJids: updateSettings.ignoreJids,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
throw new Error('Error setting default settings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async emit({
|
|
||||||
instance,
|
|
||||||
remoteJid,
|
|
||||||
msg,
|
|
||||||
}: {
|
|
||||||
instance: InstanceDto;
|
|
||||||
remoteJid: string;
|
|
||||||
msg: any;
|
|
||||||
pushName?: string;
|
|
||||||
}) {
|
|
||||||
if (!this.integrationEnabled) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const instanceData = await this.prismaRepository.instance.findFirst({
|
|
||||||
where: {
|
|
||||||
name: instance.instanceName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!instanceData) throw new Error('Instance not found');
|
|
||||||
|
|
||||||
const session = await this.getSession(remoteJid, instance);
|
|
||||||
|
|
||||||
const content = getConversationMessage(msg);
|
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as TypebotModel;
|
|
||||||
|
|
||||||
if (!findBot) {
|
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fallback?.typebotIdFallback) {
|
|
||||||
const findFallback = await this.botRepository.findFirst({
|
|
||||||
where: {
|
|
||||||
id: fallback.typebotIdFallback,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
findBot = findFallback;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await this.prismaRepository.typebotSetting.findFirst({
|
|
||||||
where: {
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = findBot?.url;
|
|
||||||
const typebot = findBot?.typebot;
|
|
||||||
let expire = findBot?.expire;
|
|
||||||
let keywordFinish = findBot?.keywordFinish;
|
|
||||||
let delayMessage = findBot?.delayMessage;
|
|
||||||
let unknownMessage = findBot?.unknownMessage;
|
|
||||||
let listeningFromMe = findBot?.listeningFromMe;
|
|
||||||
let stopBotFromMe = findBot?.stopBotFromMe;
|
|
||||||
let keepOpen = findBot?.keepOpen;
|
|
||||||
let debounceTime = findBot?.debounceTime;
|
|
||||||
let ignoreJids = findBot?.ignoreJids;
|
|
||||||
|
|
||||||
if (expire === undefined || expire === null) expire = settings.expire;
|
|
||||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
|
||||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
|
||||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
|
||||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
|
||||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
|
||||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
|
||||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
|
||||||
|
|
||||||
if (this.checkIgnoreJids(ignoreJids, remoteJid)) return;
|
|
||||||
|
|
||||||
const key = msg.key as {
|
|
||||||
id: string;
|
|
||||||
remoteJid: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
participant: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (stopBotFromMe && key.fromMe && session) {
|
|
||||||
await this.sessionRepository.update({
|
|
||||||
where: {
|
|
||||||
id: session.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 'paused',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!listeningFromMe && key.fromMe) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debounceTime && debounceTime > 0) {
|
|
||||||
this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => {
|
|
||||||
await this.typebotService.processTypebot(
|
|
||||||
instanceData,
|
|
||||||
remoteJid,
|
|
||||||
msg,
|
|
||||||
session,
|
|
||||||
findBot,
|
|
||||||
url,
|
|
||||||
expire,
|
|
||||||
typebot,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
debouncedContent,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this.typebotService.processTypebot(
|
|
||||||
instanceData,
|
|
||||||
remoteJid,
|
|
||||||
msg,
|
|
||||||
session,
|
|
||||||
findBot,
|
|
||||||
url,
|
|
||||||
expire,
|
|
||||||
typebot,
|
|
||||||
keywordFinish,
|
|
||||||
delayMessage,
|
|
||||||
unknownMessage,
|
|
||||||
listeningFromMe,
|
|
||||||
stopBotFromMe,
|
|
||||||
keepOpen,
|
|
||||||
content,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session && !session.awaitUser) return;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { TriggerOperator, TriggerType } from '@prisma/client';
|
import { TriggerOperator, TriggerType } from '@prisma/client';
|
||||||
|
|
||||||
|
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
|
||||||
|
|
||||||
export class PrefilledVariables {
|
export class PrefilledVariables {
|
||||||
remoteJid?: string;
|
remoteJid?: string;
|
||||||
pushName?: string;
|
pushName?: string;
|
||||||
@ -7,28 +9,27 @@ export class PrefilledVariables {
|
|||||||
additionalData?: { [key: string]: any };
|
additionalData?: { [key: string]: any };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TypebotDto {
|
export class TypebotDto extends BaseChatbotDto {
|
||||||
enabled?: boolean;
|
|
||||||
description?: string;
|
|
||||||
url: string;
|
url: string;
|
||||||
typebot?: string;
|
typebot: string;
|
||||||
|
description: string;
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
keywordFinish?: string | null;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
stopBotFromMe?: boolean;
|
stopBotFromMe?: boolean;
|
||||||
keepOpen?: boolean;
|
keepOpen?: boolean;
|
||||||
debounceTime?: number;
|
debounceTime?: number;
|
||||||
triggerType?: TriggerType;
|
triggerType: TriggerType;
|
||||||
triggerOperator?: TriggerOperator;
|
triggerOperator?: TriggerOperator;
|
||||||
triggerValue?: string;
|
triggerValue?: string;
|
||||||
ignoreJids?: any;
|
ignoreJids?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TypebotSettingDto {
|
export class TypebotSettingDto extends BaseChatbotSettingDto {
|
||||||
expire?: number;
|
expire?: number;
|
||||||
keywordFinish?: string;
|
keywordFinish?: string | null;
|
||||||
delayMessage?: number;
|
delayMessage?: number;
|
||||||
unknownMessage?: string;
|
unknownMessage?: string;
|
||||||
listeningFromMe?: boolean;
|
listeningFromMe?: boolean;
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user