import { DyFM_Error, DyFM_Log, hour, week } from '@futdevpro/fsm-dynamo' import { DyNTS_Bot_Routines_ControlService } from './bot-routines.control-service' import { DyNTS_Bot_Commands_ControlService } from './bot-commands.control-service' import { DyNTS_SingletonService } from '../../../_services/base/singleton.service' import { DyNTS_Bot_Main_ControlService } from './bot-main.control-service' import { DyNTS_global_settings } from '../../../_collections/global-settings.const' import { DyNTS_Bot_Global_Settings } from '../_models/bot-global-settings.interface' import { DyNTS_Bot_MessageWrapper } from '../_models/bot-message-wrapper.interface' import { DyNTS_Bot_MessagingProvider_ServiceBase } from './bot-messaging-provider.service-base' export abstract class DyNTS_Bot_IO_ControlService extends DyNTS_SingletonService { protected mainBot_CS: DyNTS_Bot_Main_ControlService; protected abstract getMainBotControlService(): DyNTS_Bot_Main_ControlService; protected commands_CS: DyNTS_Bot_Commands_ControlService; protected abstract getCommandsControlService(): DyNTS_Bot_Commands_ControlService; get defaultProvider(): DyNTS_Bot_MessagingProvider_ServiceBase { return this.mainBot_CS.defaultProvider; } /* get provider(): DyNTS_Bot_MessagingProvider { return this.mainBot_CS?.provider; } get botClientId(): string { return this.mainBot_CS.botClientId; } get botDisplayName(): string { return this.mainBot_CS.botDisplayName; } */ dontSendErrorReply?: boolean; async setup(issuer: string): Promise { try { this.mainBot_CS = this.getMainBotControlService(); this.commands_CS = this.getCommandsControlService(); if (!this.mainBot_CS) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setup', new Error('Main Bot Control Service not found'), issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-S01`, }) } if (!this.commands_CS) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setup', new Error('Commands Control Service not found'), issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-S02`, }) } } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setup', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-S000`, }) } } async start(issuer: string): Promise { try { if (!this.defaultProvider) { throw new DyFM_Error({ ...this.getDefaultErrorSettings( 'constructor', new Error( 'Bot provider not yet initialized, ' + 'should only be called after DyNTS_Bot_Main_ControlService.start' ), 'system-init' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-C000`, }) } } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('start', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-S000`, }) } } abstract handleMessage( message: DyNTS_Bot_MessageWrapper, issuer: string ): Promise>; /** * This is the internal handler for new messages, which will call the handleMessage() method. */ async _handleNewMessage( message: DyNTS_Bot_MessageWrapper, issuer: string ): Promise> { try { /* const message = message.message; */ const botSettings: DyNTS_Bot_Global_Settings = DyNTS_global_settings.bot_settings; if (botSettings?.debugLevel >= 1) { DyFM_Log.H_log(`incoming message (${message.authorDisplayName}) [${message.provider.type}]:\n${message.content}`) } const isForYou = await this.getMessageIsForBotToHandle(message, issuer); if (!isForYou) { return; } const isHandledAsCommand = await this.handleIfCommand(message, issuer); if (isHandledAsCommand) { return; } return await this.handleMessage(message, issuer); } catch (error) { DyFM_Error.logSimple('❌❌ Error handleNewMessage:', error); const botSettings = DyNTS_global_settings.bot_settings as DyNTS_Bot_Global_Settings; if (!this.dontSendErrorReply && botSettings?.debugLevel >= 1) { if (message.provider) { await message.provider.replyToMessage( message, `[SYSTEM|ERROR|${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-H0] ` + `Error occurred while handling the message:\n` + DyFM_Error.getAnyMessage(error) ).catch(err => { DyFM_Error.logSimple('❌❌ Error sending message to report channel:', err); }); } } throw new DyFM_Error({ ...this.getDefaultErrorSettings('handleNewMessage', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-H0`, }) } } async getMessageIsForBotToHandle( message: DyNTS_Bot_MessageWrapper, issuer: string, ): Promise { try { const botSettings = DyNTS_global_settings.bot_settings as DyNTS_Bot_Global_Settings; if (!message.provider || !this.mainBot_CS || !message.provider.botId) { DyFM_Log.H_error( '🚫❌ Bot not initialized yet. END wont respond to message:', message.content, new Error('Bot not initialized yet').stack ) return false; } const isReplyForYou = message.replyToMessageAuthorId && message.replyToMessageAuthorId === message.provider.botId; // Simplified check const isPinged = message.content.includes(message.provider.botId) || message.content.includes(`@${message.provider.botDisplayName}`) || isReplyForYou; const isOwnMessage = message.authorId === message.provider.botId || message.authorDisplayName === message.provider.botDisplayName; const haveSkipFlags = botSettings?.messageSettings?.skipFlags?.some( flag => message.content.includes(flag) ) || false; const channelName = message.channelName; const isDMChannel = message.channelName?.startsWith('DM-'); // Simplified DM detection const isInDefaultChannel = botSettings?.channelSettings?.defaultChannels?.includes( channelName ) || (isDMChannel && botSettings?.channelSettings?.dmIsAllowed); const isAllowedChannel = botSettings?.channelSettings?.allowedChannels?.includes( channelName ) || (isDMChannel && botSettings?.channelSettings?.dmIsAllowed); if (!isAllowedChannel) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫❌ not allowed channel. END') } DyFM_Log.H_warn('🚫❌ T1000 ', { replyToMessageAuthorId: message.replyToMessageAuthorId, botId: message.provider.botId, }); if (!isOwnMessage && isPinged) { message.provider.replyToMessage( message, `[SYSTEM|REFUSE|CHANNEL] I'm sorry, but I'm not allowed to answer you in this channel!` ) } return false; } if (isOwnMessage) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫🤖 will not answer; own message') } return false; } if (haveSkipFlags) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫🏳️ not for me; this message have some skip flags:', botSettings?.messageSettings?.skipFlags?.filter( flag => message.content.includes(flag) ), message.content ) } return false; } if ( message.isBot && !botSettings?.allowBotsInteractEachOther ) { if (isPinged && botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫🤖❌ not for me; this message is from a bot') message.provider.replyToMessage( message, '[SYSTEM|REFUSE|BOT] You should not ping me!' ) } return false; } if (!isPinged && !isInDefaultChannel) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫 not for me; not pinged, and not my message, and not in default channel') } return false; } const isAllowedUser = botSettings?.allowedUsers === 'all' || botSettings?.allowedUsers?.includes(message.authorId) || botSettings?.allowedUsers?.includes(message.authorName) || botSettings?.allowedUsers?.includes(message.authorDisplayName); if (!isAllowedUser) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫❌ not allowed user. END', message.authorName, message.authorDisplayName) message.provider.replyToMessage( message, `[SYSTEM|REFUSE|USER] I'm sorry, but I'm not allowed to answer you!` ) } return false; } if (!message.channelName) { if (botSettings?.debugLevel >= 2) { DyFM_Log.warn('🚫❌ not text based channel. END') } return false; } return true; } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('getMessageIsForBotToHandle', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-G0`, }) } } async handleIfCommand( message: DyNTS_Bot_MessageWrapper, issuer: string ): Promise { try { const isCommand = await this.isCommand(message, issuer); if (isCommand) { await this.commands_CS.handleCommand(message, issuer); } return Boolean(isCommand); } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('handleIfCommand', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-HIC0`, }) } } async isCommand( message: DyNTS_Bot_MessageWrapper, issuer: string ): Promise { try { const botSettings = DyNTS_global_settings.bot_settings as DyNTS_Bot_Global_Settings; let haveCommandOperator = false; if (!botSettings?.commandSettings?.commandOperator) { haveCommandOperator = true; } else if ( message.content.trim().startsWith(botSettings.commandSettings.commandOperator) ) { haveCommandOperator = true; } if (!haveCommandOperator) { return false; } const commandExists = botSettings?.commandSettings?.commands?.find( (command) => message.content.trim().startsWith( `${botSettings.commandSettings.commandOperator}${command.command}` ) ); return Boolean(commandExists); } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('isCommand', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-IO-IC0`, }) } } }