oex2003/evolution-api
0
1import { IgnoreJidDto } from '@api/dto/chatbot.dto';2import { InstanceDto } from '@api/dto/instance.dto';3import { PrismaRepository } from '@api/repository/repository.service';4import { WAMonitoringService } from '@api/services/monitor.service';5import { Logger } from '@config/logger.config';6import { BadRequestException } from '@exceptions';7import { TriggerOperator, TriggerType } from '@prisma/client';8import { getConversationMessage } from '@utils/getConversationMessage';9 10import { BaseChatbotDto } from './base-chatbot.dto';11import { ChatbotController, ChatbotControllerInterface, EmitData } from './chatbot.controller';12 13// Common settings interface for all chatbot integrations14export interface ChatbotSettings {15 expire: number;16 keywordFinish: string;17 delayMessage: number;18 unknownMessage: string;19 listeningFromMe: boolean;20 stopBotFromMe: boolean;21 keepOpen: boolean;22 debounceTime: number;23 ignoreJids: string[];24 splitMessages: boolean;25 timePerChar: number;26 [key: string]: any;27}28 29// Common bot properties for all chatbot integrations30export interface BaseBotData {31 enabled?: boolean;32 description: string;33 expire?: number;34 keywordFinish?: string;35 delayMessage?: number;36 unknownMessage?: string;37 listeningFromMe?: boolean;38 stopBotFromMe?: boolean;39 keepOpen?: boolean;40 debounceTime?: number;41 triggerType: string | TriggerType;42 triggerOperator?: string | TriggerOperator;43 triggerValue?: string;44 ignoreJids?: string[];45 splitMessages?: boolean;46 timePerChar?: number;47 [key: string]: any;48}49 50export abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>51 extends ChatbotController52 implements ChatbotControllerInterface53{54 public readonly logger: Logger;55 56 integrationEnabled: boolean;57 botRepository: any;58 settingsRepository: any;59 sessionRepository: any;60 userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};61 62 // Name of the integration, to be set by the derived class63 protected abstract readonly integrationName: string;64 65 // Method to process bot-specific logic66 protected abstract processBot(67 waInstance: any,68 remoteJid: string,69 bot: BotType,70 session: any,71 settings: ChatbotSettings,72 content: string,73 pushName?: string,74 msg?: any,75 ): Promise<void>;76 77 // Method to get the fallback bot ID from settings78 protected abstract getFallbackBotId(settings: any): string | undefined;79 80 constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {81 super(prismaRepository, waMonitor);82 83 this.sessionRepository = this.prismaRepository.integrationSession;84 }85 86 // Base create bot implementation87 public async createBot(instance: InstanceDto, data: BotData) {88 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);89 90 const instanceId = await this.prismaRepository.instance91 .findFirst({92 where: {93 name: instance.instanceName,94 },95 })96 .then((instance) => instance.id);97 98 // Set default settings if not provided99 if (100 !data.expire ||101 !data.keywordFinish ||102 !data.delayMessage ||103 !data.unknownMessage ||104 !data.listeningFromMe ||105 !data.stopBotFromMe ||106 !data.keepOpen ||107 !data.debounceTime ||108 !data.ignoreJids ||109 !data.splitMessages ||110 !data.timePerChar111 ) {112 const defaultSettingCheck = await this.settingsRepository.findFirst({113 where: {114 instanceId: instanceId,115 },116 });117 118 if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck?.expire;119 if (data.keywordFinish === undefined || data.keywordFinish === null)120 data.keywordFinish = defaultSettingCheck?.keywordFinish;121 if (data.delayMessage === undefined || data.delayMessage === null)122 data.delayMessage = defaultSettingCheck?.delayMessage;123 if (data.unknownMessage === undefined || data.unknownMessage === null)124 data.unknownMessage = defaultSettingCheck?.unknownMessage;125 if (data.listeningFromMe === undefined || data.listeningFromMe === null)126 data.listeningFromMe = defaultSettingCheck?.listeningFromMe;127 if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)128 data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe;129 if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck?.keepOpen;130 if (data.debounceTime === undefined || data.debounceTime === null)131 data.debounceTime = defaultSettingCheck?.debounceTime;132 if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck?.ignoreJids;133 if (data.splitMessages === undefined || data.splitMessages === null)134 data.splitMessages = defaultSettingCheck?.splitMessages ?? false;135 if (data.timePerChar === undefined || data.timePerChar === null)136 data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;137 138 if (!defaultSettingCheck) {139 await this.settings(instance, {140 expire: data.expire,141 keywordFinish: data.keywordFinish,142 delayMessage: data.delayMessage,143 unknownMessage: data.unknownMessage,144 listeningFromMe: data.listeningFromMe,145 stopBotFromMe: data.stopBotFromMe,146 keepOpen: data.keepOpen,147 debounceTime: data.debounceTime,148 ignoreJids: data.ignoreJids,149 splitMessages: data.splitMessages,150 timePerChar: data.timePerChar,151 });152 }153 }154 155 const checkTriggerAll = await this.botRepository.findFirst({156 where: {157 enabled: true,158 triggerType: 'all',159 instanceId: instanceId,160 },161 });162 163 if (checkTriggerAll && data.triggerType === 'all') {164 throw new Error(165 `You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`,166 );167 }168 169 // Check for trigger keyword duplicates170 if (data.triggerType === 'keyword') {171 if (!data.triggerOperator || !data.triggerValue) {172 throw new Error('Trigger operator and value are required');173 }174 175 const checkDuplicate = await this.botRepository.findFirst({176 where: {177 triggerOperator: data.triggerOperator,178 triggerValue: data.triggerValue,179 instanceId: instanceId,180 },181 });182 183 if (checkDuplicate) {184 throw new Error('Trigger already exists');185 }186 }187 188 // Check for trigger advanced duplicates189 if (data.triggerType === 'advanced') {190 if (!data.triggerValue) {191 throw new Error('Trigger value is required');192 }193 194 const checkDuplicate = await this.botRepository.findFirst({195 where: {196 triggerValue: data.triggerValue,197 instanceId: instanceId,198 },199 });200 201 if (checkDuplicate) {202 throw new Error('Trigger already exists');203 }204 }205 206 // Derived classes should implement the specific duplicate checking before calling this method207 // and add bot-specific fields to the data object208 209 try {210 const botData = {211 enabled: data?.enabled,212 description: data.description,213 expire: data.expire,214 keywordFinish: data.keywordFinish,215 delayMessage: data.delayMessage,216 unknownMessage: data.unknownMessage,217 listeningFromMe: data.listeningFromMe,218 stopBotFromMe: data.stopBotFromMe,219 keepOpen: data.keepOpen,220 debounceTime: data.debounceTime,221 instanceId: instanceId,222 triggerType: data.triggerType,223 triggerOperator: data.triggerOperator,224 triggerValue: data.triggerValue,225 ignoreJids: data.ignoreJids,226 splitMessages: data.splitMessages,227 timePerChar: data.timePerChar,228 ...this.getAdditionalBotData(data),229 };230 231 const bot = await this.botRepository.create({232 data: botData,233 });234 235 return bot;236 } catch (error) {237 this.logger.error(error);238 throw new Error(`Error creating ${this.integrationName}`);239 }240 }241 242 // Additional fields needed for specific bot types243 protected abstract getAdditionalBotData(data: BotData): Record<string, any>;244 245 // Common implementation for findBot246 public async findBot(instance: InstanceDto) {247 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);248 249 const instanceId = await this.prismaRepository.instance250 .findFirst({251 where: {252 name: instance.instanceName,253 },254 })255 .then((instance) => instance.id);256 257 try {258 const bots = await this.botRepository.findMany({259 where: {260 instanceId: instanceId,261 },262 });263 264 return bots;265 } catch (error) {266 this.logger.error(error);267 throw new Error(`Error finding ${this.integrationName}`);268 }269 }270 271 // Common implementation for fetchBot272 public async fetchBot(instance: InstanceDto, botId: string) {273 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);274 275 try {276 const bot = await this.botRepository.findUnique({277 where: {278 id: botId,279 },280 });281 282 if (!bot) {283 return null;284 }285 286 return bot;287 } catch (error) {288 this.logger.error(error);289 throw new Error(`Error fetching ${this.integrationName}`);290 }291 }292 293 // Common implementation for settings294 public async settings(instance: InstanceDto, data: any) {295 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);296 297 try {298 const instanceId = await this.prismaRepository.instance299 .findFirst({300 where: {301 name: instance.instanceName,302 },303 })304 .then((instance) => instance.id);305 306 const existingSettings = await this.settingsRepository.findFirst({307 where: {308 instanceId: instanceId,309 },310 });311 312 // Get the name of the fallback field for this integration type313 const fallbackFieldName = this.getFallbackFieldName();314 315 const settingsData = {316 expire: data.expire,317 keywordFinish: data.keywordFinish,318 delayMessage: data.delayMessage,319 unknownMessage: data.unknownMessage,320 listeningFromMe: data.listeningFromMe,321 stopBotFromMe: data.stopBotFromMe,322 keepOpen: data.keepOpen,323 debounceTime: data.debounceTime,324 ignoreJids: data.ignoreJids,325 splitMessages: data.splitMessages,326 timePerChar: data.timePerChar,327 [fallbackFieldName]: data.fallbackId, // Use the correct field name dynamically328 };329 330 if (existingSettings) {331 const settings = await this.settingsRepository.update({332 where: {333 id: existingSettings.id,334 },335 data: settingsData,336 });337 338 // Map the specific fallback field to a generic 'fallbackId' in the response339 return {340 ...settings,341 fallbackId: settings[fallbackFieldName],342 };343 } else {344 const settings = await this.settingsRepository.create({345 data: {346 ...settingsData,347 Instance: {348 connect: {349 id: instanceId,350 },351 },352 },353 });354 355 // Map the specific fallback field to a generic 'fallbackId' in the response356 return {357 ...settings,358 fallbackId: settings[fallbackFieldName],359 };360 }361 } catch (error) {362 this.logger.error(error);363 throw new Error('Error setting default settings');364 }365 }366 367 // Abstract method to get the field name for the fallback ID368 protected abstract getFallbackFieldName(): string;369 370 // Abstract method to get the integration type (dify, n8n, evoai, etc.)371 protected abstract getIntegrationType(): string;372 373 // Common implementation for fetchSettings374 public async fetchSettings(instance: InstanceDto) {375 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);376 377 try {378 const instanceId = await this.prismaRepository.instance379 .findFirst({380 where: {381 name: instance.instanceName,382 },383 })384 .then((instance) => instance.id);385 386 const settings = await this.settingsRepository.findFirst({387 where: {388 instanceId: instanceId,389 },390 include: {391 Fallback: true,392 },393 });394 395 // Get the name of the fallback field for this integration type396 const fallbackFieldName = this.getFallbackFieldName();397 398 if (!settings) {399 return {400 expire: 300,401 keywordFinish: 'bye',402 delayMessage: 1000,403 unknownMessage: 'Sorry, I dont understand',404 listeningFromMe: true,405 stopBotFromMe: true,406 keepOpen: false,407 debounceTime: 1,408 ignoreJids: [],409 splitMessages: false,410 timePerChar: 0,411 fallbackId: '',412 fallback: null,413 };414 }415 416 // Return with standardized fallbackId field417 return {418 ...settings,419 fallbackId: settings[fallbackFieldName],420 fallback: settings.Fallback,421 };422 } catch (error) {423 this.logger.error(error);424 throw new Error('Error fetching settings');425 }426 }427 428 // Common implementation for changeStatus429 public async changeStatus(instance: InstanceDto, data: any) {430 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);431 432 try {433 const instanceId = await this.prismaRepository.instance434 .findFirst({435 where: {436 name: instance.instanceName,437 },438 })439 .then((instance) => instance.id);440 441 const defaultSettingCheck = await this.settingsRepository.findFirst({442 where: {443 instanceId,444 },445 });446 447 const remoteJid = data.remoteJid;448 const status = data.status;449 450 if (status === 'delete') {451 await this.sessionRepository.deleteMany({452 where: {453 remoteJid: remoteJid,454 botId: { not: null },455 },456 });457 458 return { bot: { remoteJid: remoteJid, status: status } };459 }460 461 if (status === 'closed') {462 if (defaultSettingCheck?.keepOpen) {463 await this.sessionRepository.updateMany({464 where: {465 remoteJid: remoteJid,466 botId: { not: null },467 },468 data: {469 status: 'closed',470 },471 });472 } else {473 await this.sessionRepository.deleteMany({474 where: {475 remoteJid: remoteJid,476 botId: { not: null },477 },478 });479 }480 481 return { bot: { ...instance, bot: { remoteJid: remoteJid, status: status } } };482 } else {483 const session = await this.sessionRepository.updateMany({484 where: {485 instanceId: instanceId,486 remoteJid: remoteJid,487 botId: { not: null },488 },489 data: {490 status: status,491 },492 });493 494 const botData = {495 remoteJid: remoteJid,496 status: status,497 session,498 };499 500 return { bot: { ...instance, bot: botData } };501 }502 } catch (error) {503 this.logger.error(error);504 throw new Error(`Error changing ${this.integrationName} status`);505 }506 }507 508 // Common implementation for fetchSessions509 public async fetchSessions(instance: InstanceDto, botId: string, remoteJid?: string) {510 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);511 512 try {513 const instanceId = await this.prismaRepository.instance514 .findFirst({515 where: {516 name: instance.instanceName,517 },518 })519 .then((instance) => instance.id);520 521 const bot = await this.botRepository.findFirst({522 where: {523 id: botId,524 },525 });526 527 if (bot && bot.instanceId !== instanceId) {528 throw new Error(`${this.integrationName} not found`);529 }530 531 // Get the integration type (dify, n8n, evoai, etc.)532 const integrationType = this.getIntegrationType();533 534 return await this.sessionRepository.findMany({535 where: {536 instanceId: instanceId,537 remoteJid,538 botId: bot ? botId : { not: null },539 type: integrationType,540 },541 });542 } catch (error) {543 this.logger.error(error);544 throw new Error('Error fetching sessions');545 }546 }547 548 // Common implementation for ignoreJid549 public async ignoreJid(instance: InstanceDto, data: IgnoreJidDto) {550 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);551 552 try {553 const instanceId = await this.prismaRepository.instance554 .findFirst({555 where: {556 name: instance.instanceName,557 },558 })559 .then((instance) => instance.id);560 561 const settings = await this.settingsRepository.findFirst({562 where: {563 instanceId: instanceId,564 },565 });566 567 if (!settings) {568 throw new Error('Settings not found');569 }570 571 let ignoreJids: any = settings?.ignoreJids || [];572 573 if (data.action === 'add') {574 if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids };575 576 ignoreJids.push(data.remoteJid);577 } else {578 ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid);579 }580 581 const updateSettings = await this.settingsRepository.update({582 where: {583 id: settings.id,584 },585 data: {586 ignoreJids: ignoreJids,587 },588 });589 590 return {591 ignoreJids: updateSettings.ignoreJids,592 };593 } catch (error) {594 this.logger.error(error);595 throw new Error('Error setting default settings');596 }597 }598 599 // Base implementation for updateBot600 public async updateBot(instance: InstanceDto, botId: string, data: BotData) {601 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);602 603 try {604 const instanceId = await this.prismaRepository.instance605 .findFirst({606 where: {607 name: instance.instanceName,608 },609 })610 .then((instance) => instance.id);611 612 const bot = await this.botRepository.findFirst({613 where: {614 id: botId,615 },616 });617 618 if (!bot) {619 throw new Error(`${this.integrationName} not found`);620 }621 622 if (bot.instanceId !== instanceId) {623 throw new Error(`${this.integrationName} not found`);624 }625 626 // Check for "all" trigger type conflicts627 if (data.triggerType === 'all') {628 const checkTriggerAll = await this.botRepository.findFirst({629 where: {630 enabled: true,631 triggerType: 'all',632 id: {633 not: botId,634 },635 instanceId: instanceId,636 },637 });638 639 if (checkTriggerAll) {640 throw new Error(641 `You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`,642 );643 }644 }645 646 // Let subclasses check for integration-specific duplicates647 await this.validateNoDuplicatesOnUpdate(botId, instanceId, data);648 649 // Check for keyword trigger duplicates650 if (data.triggerType === 'keyword') {651 if (!data.triggerOperator || !data.triggerValue) {652 throw new Error('Trigger operator and value are required');653 }654 655 const checkDuplicate = await this.botRepository.findFirst({656 where: {657 triggerOperator: data.triggerOperator,658 triggerValue: data.triggerValue,659 id: { not: botId },660 instanceId: instanceId,661 },662 });663 664 if (checkDuplicate) {665 throw new Error('Trigger already exists');666 }667 }668 669 // Check for advanced trigger duplicates670 if (data.triggerType === 'advanced') {671 if (!data.triggerValue) {672 throw new Error('Trigger value is required');673 }674 675 const checkDuplicate = await this.botRepository.findFirst({676 where: {677 triggerValue: data.triggerValue,678 id: { not: botId },679 instanceId: instanceId,680 },681 });682 683 if (checkDuplicate) {684 throw new Error('Trigger already exists');685 }686 }687 688 // Combine common fields with bot-specific fields689 const updateData = {690 enabled: data?.enabled,691 description: data.description,692 expire: data.expire,693 keywordFinish: data.keywordFinish,694 delayMessage: data.delayMessage,695 unknownMessage: data.unknownMessage,696 listeningFromMe: data.listeningFromMe,697 stopBotFromMe: data.stopBotFromMe,698 keepOpen: data.keepOpen,699 debounceTime: data.debounceTime,700 instanceId: instanceId,701 triggerType: data.triggerType,702 triggerOperator: data.triggerOperator,703 triggerValue: data.triggerValue,704 ignoreJids: data.ignoreJids,705 splitMessages: data.splitMessages,706 timePerChar: data.timePerChar,707 ...this.getAdditionalUpdateFields(data),708 };709 710 const updatedBot = await this.botRepository.update({711 where: {712 id: botId,713 },714 data: updateData,715 });716 717 return updatedBot;718 } catch (error) {719 this.logger.error(error);720 throw new Error(`Error updating ${this.integrationName}`);721 }722 }723 724 // Abstract method for validating bot-specific duplicates on update725 protected abstract validateNoDuplicatesOnUpdate(botId: string, instanceId: string, data: BotData): Promise<void>;726 727 // Abstract method for getting additional fields for update728 protected abstract getAdditionalUpdateFields(data: BotData): Record<string, any>;729 730 // Base implementation for deleteBot731 public async deleteBot(instance: InstanceDto, botId: string) {732 if (!this.integrationEnabled) throw new BadRequestException(`${this.integrationName} is disabled`);733 734 try {735 const instanceId = await this.prismaRepository.instance736 .findFirst({737 where: {738 name: instance.instanceName,739 },740 })741 .then((instance) => instance.id);742 743 const bot = await this.botRepository.findFirst({744 where: {745 id: botId,746 },747 });748 749 if (!bot) {750 throw new Error(`${this.integrationName} not found`);751 }752 753 if (bot.instanceId !== instanceId) {754 throw new Error(`${this.integrationName} not found`);755 }756 757 await this.prismaRepository.integrationSession.deleteMany({758 where: {759 botId: botId,760 },761 });762 763 await this.botRepository.delete({764 where: {765 id: botId,766 },767 });768 769 return { bot: { id: botId } };770 } catch (error) {771 this.logger.error(error);772 throw new Error(`Error deleting ${this.integrationName} bot`);773 }774 }775 776 // Base implementation for emit777 public async emit({ instance, remoteJid, msg }: EmitData) {778 if (!this.integrationEnabled) return;779 780 try {781 const settings = await this.settingsRepository.findFirst({782 where: {783 instanceId: instance.instanceId,784 },785 });786 787 if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return;788 789 const session = await this.getSession(remoteJid, instance);790 791 const content = getConversationMessage(msg);792 793 // Get integration type794 // const integrationType = this.getIntegrationType();795 796 // Find a bot for this message797 let findBot: any = await this.findBotTrigger(this.botRepository, content, instance, session);798 799 // If no bot is found, try to use fallback800 if (!findBot) {801 const fallback = await this.settingsRepository.findFirst({802 where: {803 instanceId: instance.instanceId,804 },805 });806 807 // Get the fallback ID for this integration type808 const fallbackId = this.getFallbackBotId(fallback);809 810 if (fallbackId) {811 const findFallback = await this.botRepository.findFirst({812 where: {813 id: fallbackId,814 },815 });816 817 findBot = findFallback;818 } else {819 return;820 }821 }822 823 // If we still don't have a bot, return824 if (!findBot) {825 return;826 }827 828 // Collect settings with fallbacks to default settings829 let expire = findBot.expire;830 let keywordFinish = findBot.keywordFinish;831 let delayMessage = findBot.delayMessage;832 let unknownMessage = findBot.unknownMessage;833 let listeningFromMe = findBot.listeningFromMe;834 let stopBotFromMe = findBot.stopBotFromMe;835 let keepOpen = findBot.keepOpen;836 let debounceTime = findBot.debounceTime;837 let ignoreJids = findBot.ignoreJids;838 let splitMessages = findBot.splitMessages;839 let timePerChar = findBot.timePerChar;840 841 if (expire === undefined || expire === null) expire = settings.expire;842 if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;843 if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;844 if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;845 if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;846 if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;847 if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;848 if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;849 if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;850 if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;851 if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;852 853 const key = msg.key as {854 id: string;855 remoteJid: string;856 fromMe: boolean;857 participant: string;858 };859 860 // Handle stopping the bot if message is from me861 if (stopBotFromMe && key.fromMe && session) {862 await this.prismaRepository.integrationSession.update({863 where: {864 id: session.id,865 },866 data: {867 status: 'paused',868 },869 });870 return;871 }872 873 // Skip if not listening to messages from me874 if (!listeningFromMe && key.fromMe) {875 return;876 }877 878 // Skip if session exists but not awaiting user input879 if (session && session.status === 'closed') {880 return;881 }882 883 // Skip if session exists and status is paused884 if (session && session.status === 'paused') {885 this.logger.warn(`Session for ${remoteJid} is paused, skipping message processing`);886 return;887 }888 889 // Merged settings890 const mergedSettings = {891 ...settings,892 expire,893 keywordFinish,894 delayMessage,895 unknownMessage,896 listeningFromMe,897 stopBotFromMe,898 keepOpen,899 debounceTime,900 ignoreJids,901 splitMessages,902 timePerChar,903 };904 905 // Process with debounce if needed906 if (debounceTime && debounceTime > 0) {907 this.processDebounce(this.userMessageDebounce, content, remoteJid, debounceTime, async (debouncedContent) => {908 await this.processBot(909 this.waMonitor.waInstances[instance.instanceName],910 remoteJid,911 findBot,912 session,913 mergedSettings,914 debouncedContent,915 msg?.pushName,916 msg,917 );918 });919 } else {920 await this.processBot(921 this.waMonitor.waInstances[instance.instanceName],922 remoteJid,923 findBot,924 session,925 mergedSettings,926 content,927 msg?.pushName,928 msg,929 );930 }931 } catch (error) {932 this.logger.error(error);933 }934 }935}936 