Team Ai
Apppublic

oex2003/evolution-api

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
base-chatbot.controller.ts936 linesDownload Raw Back to chatbot
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