import { DyFM_Async, DyFM_Error, DyFM_Log } from '@futdevpro/fsm-dynamo'; import { DyFM_Msg_Provider_Type } from '@futdevpro/fsm-dynamo/messaging'; import { DyNTS_SingletonService } from '../../../_services/base/singleton.service'; import { DyNTS_global_settings } from '../../../_collections/global-settings.const'; import { DyNTS_GlobalService } from '../../../_services/core/global.service'; import { DyNTS_Bot_MessagingProvider_ServiceBase } from './bot-messaging-provider.service-base'; import { DyNTS_Bot_MessageWrapper } from '../_models/bot-message-wrapper.interface'; import { DyNTS_Bot_IO_ControlService } from './bot-io.control-service'; import { DyNTS_Bot_Routines_ControlService } from './bot-routines.control-service'; import { DyNTS_Bot_Commands_ControlService } from './bot-commands.control-service'; /** * This is the main control service for the Bot. * * Platform-agnostic bot service that works with any provider (Discord, Slack, Teams, etc.) * * Setup: * 1. Configure provider in DyNTS_global_settings.bot_settings * 2. Set provider type and credentials * 3. Initialize the bot with setup() */ export abstract class DyNTS_Bot_Main_ControlService< T_MessagingPlatformProvider_Type extends DyFM_Msg_Provider_Type | string = DyFM_Msg_Provider_Type, > extends DyNTS_SingletonService { //protected _provider: DyNTS_Bot_MessagingProvider; protected _providers: Map< T_MessagingPlatformProvider_Type, DyNTS_Bot_MessagingProvider_ServiceBase > = new Map(); protected _defaultProvider: DyNTS_Bot_MessagingProvider_ServiceBase; get defaultProvider(): DyNTS_Bot_MessagingProvider_ServiceBase { return this._defaultProvider; } /* get provider(): DyNTS_Bot_MessagingProvider { return this._provider; } */ /* get providers(): Map> { return this._providers; } */ /* getProviderById(providerId: string): DyNTS_Bot_MessagingProvider | undefined { return this._providers.get(providerId); } */ /* get botClientId(): string { return this.provider?.getBotId() ?? DyNTS_global_settings?.bot_settings?.discord?.clientId; } get botDisplayName(): string { return this.provider?.getBotDisplayName(); } */ botIO_CS: DyNTS_Bot_IO_ControlService; protected abstract getBotIOControlService(): DyNTS_Bot_IO_ControlService; routines_CS: DyNTS_Bot_Routines_ControlService; protected abstract getRoutinesControlService(): DyNTS_Bot_Routines_ControlService; commands_CS: DyNTS_Bot_Commands_ControlService; protected abstract getCommandsControlService(): DyNTS_Bot_Commands_ControlService; protected abstract defaultProviderType: T_MessagingPlatformProvider_Type; protected abstract availableProviders: DyNTS_Bot_MessagingProvider_ServiceBase[]; protected debugLog = false; /* protected constructor() { super(); } */ async setup(issuer: string): Promise { try { await DyFM_Async.delay(100); /* const configs = this.getProviderConfigs(); if (configs.length === 0) { throw new Error( `No bot provider configured!` + `\nPlease set the provider in DyNTS_global_settings.bot_settings.provider` + `\nSupported providers: 'discord', 'slack', 'teams', 'custom'` ); } */ if (this.availableProviders.length === 0) { throw new Error( `No bot providers configured!` + `\nPlease set the providers in DyNTS_global_settings.bot_settings.providers` ); } if (!this.defaultProviderType) { throw new Error( `No default bot provider type configured!` + `\nPlease set the default provider type in DyNTS_global_settings.bot_settings.defaultProviderType` ); } if (!this.availableProviders.find(provider => provider.type === this.defaultProviderType)) { throw new Error( `Default bot provider type not found in available providers!` + `\nPlease set the default provider type in DyNTS_global_settings.bot_settings.defaultProviderType` ); } this.botIO_CS = this.getBotIOControlService(); this.routines_CS = this.getRoutinesControlService(); this.commands_CS = this.getCommandsControlService(); if (!this.botIO_CS) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setup', new Error('Bot IO Control Service not found'), issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-S01`, }) } if (!this.routines_CS) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setup', new Error('Routines Control Service not found'), issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-S02`, }) } 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-MCS-S03`, }) } await this.botIO_CS.setup(issuer); await this.routines_CS.setup(issuer); await this.commands_CS.setup(issuer); for (const provider of this.availableProviders) { await this.setupProvider(provider, issuer); this._providers.set(provider.type as T_MessagingPlatformProvider_Type, provider); } this._defaultProvider = this.getProviderByType(this.defaultProviderType); await this.commands_CS.start(issuer); await this.routines_CS.start(issuer); await this.botIO_CS.start(issuer); for (const provider of this.availableProviders) { await provider.start(issuer); } DyFM_Log.H_success('Bot setup complete') } catch (error) { DyFM_Error.logSimple('Error during bot startup:', error); throw new DyFM_Error({ ...this.getDefaultErrorSettings('start', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-SE0`, }) } } async start(issuer: string): Promise { try { this.botIO_CS = this.getBotIOControlService(); this.routines_CS = this.getRoutinesControlService(); await this.routines_CS.start(issuer); await this.botIO_CS.start(issuer); DyFM_Log.success('Bot started') } catch (error) { DyFM_Error.logSimple('Error during bot start:', error); throw new DyFM_Error({ ...this.getDefaultErrorSettings('start', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-ST0`, }) } } async setupProvider(provider: DyNTS_Bot_MessagingProvider_ServiceBase, issuer: string): Promise { try { await provider.setup(issuer); provider.create_onErrorEventListener((error) => { if (DyNTS_global_settings.bot_settings?.debugLevel >= 1) { DyFM_Error.logSimple(`${provider.name} provider error:`, error); } DyNTS_GlobalService.globalErrorHandler(error); }); if (this.debugLog) { DyFM_Log.success(`${provider.name} Error event registered\n\n`, new Error().stack); } else { DyFM_Log.success(`${provider.name} Error event registered`); } provider.create_onMessageEventListener(async (message: DyNTS_Bot_MessageWrapper, messageIssuer: string) => { try { await this.botIO_CS._handleNewMessage(message, messageIssuer) } catch (error) { DyFM_Error.logSimple(`${provider.name} error handling new message:`, error); DyNTS_GlobalService.globalErrorHandler(error); } }); if (this.debugLog) { DyFM_Log.success('Message create event registered\n\n', new Error().stack); } else { DyFM_Log.success('Message create event registered'); } process.on('exit', () => { try { DyFM_Log.warn('Bot is shutting down...') provider.sendMessageToChannelByName( provider.config.reportChannelName || 'bot-reports', `Bot is shutting down...`, 'system-shutdown' ) } catch (error) { DyFM_Error.logSimple(`${provider.name} error sending message to report channel:`, error); DyNTS_GlobalService.globalErrorHandler(error); } }); if (this.debugLog) { DyFM_Log.success('Exit event registered\n\n', new Error().stack); } else { DyFM_Log.success('Exit event registered'); } } catch (error) { DyFM_Error.logSimple('Error during bot provider setup:', error); throw new DyFM_Error({ ...this.getDefaultErrorSettings(`setupProvider ${provider.name}`, error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-SPS0`, }) } } getProviderByType< T_MessagingPlatformProvider extends DyNTS_Bot_MessagingProvider_ServiceBase >(type: T_MessagingPlatformProvider_Type): T_MessagingPlatformProvider { return this._providers.get(type) as T_MessagingPlatformProvider; } async sendReport(message: string): Promise { await this.defaultProvider.sendReport(message); } /** * Check if the bot is configured for multiple providers */ /* protected isMultiProviderMode(): boolean { const settings = DyNTS_global_settings.bot_settings as any; return Array.isArray(settings?.providers) && settings.providers.length > 0; } */ /** * Get all provider configurations (supports both single and multi-provider mode) */ /* protected getProviderConfigs(): DyNTS_Bot_MessagingProvider_Config[] { const settings = DyNTS_global_settings.bot_settings as any; if (this.isMultiProviderMode()) { return settings.providers; } else { // Single provider mode - wrap existing config const config = settings as DyNTS_Bot_MessagingProvider_Config; if (!config.messagingProviderId) { config.messagingProviderId = config.messagingProvider || 'default'; } return [config]; } } */ /* private getTokenForProvider(): string { const provider = DyNTS_global_settings.bot_settings?.provider; switch (provider) { case 'discord': return DyNTS_global_settings.bot_settings?.discord?.token; case 'slack': return DyNTS_global_settings.bot_settings?.slack?.token; case 'teams': return DyNTS_global_settings.bot_settings?.teams?.appPassword; default: throw new Error(`Unsupported provider: ${provider}`); } } */ /* private async setupMultipleProviders( configs: DyNTS_Bot_MessagingProvider_Config[], issuer: string ): Promise { const providers = DyNTS_Bot_MessagingProvider_Factory.createProviders(configs); this._providers = providers as Map>; const setupPromises = Array.from(providers.entries()).map(([providerId, provider]) => { return new Promise((resolve, reject) => { provider.onError((error) => { if (DyNTS_global_settings.bot_settings?.debugLevel >= 1) { DyFM_Error.logSimple(`Bot provider error [${providerId}]:`, error); } DyNTS_GlobalService.globalErrorHandler(error); reject(error); }); provider.onReady(async () => { if (DyNTS_global_settings.bot_settings?.debugLevel >= 1) { DyFM_Log.success(`Bot logged in as ${provider.getBotDisplayName()} [${providerId}]`); } resolve(); }); provider.onMessage((message, messageIssuer, msgProviderId) => { try { const wrapper: DyNTS_Bot_MessageWrapper = { message: message, providerId: msgProviderId || provider.getProviderId() || providerId, providerType: provider.getProviderName() as any, provider: provider as any }; this.botIO_CS.handleNewMessage(wrapper, messageIssuer) } catch (error) { DyFM_Error.logSimple('Error handling new message:', error); } }); const config = configs.find(c => c.messagingProviderId === providerId); if (!config) { reject(new Error(`No config found for provider ${providerId}`)); return; } provider.initialize(config).then(() => { const token = this.getTokenForProviderConfig(config); return provider.login(token); }).catch((error) => { reject(error); }); }); }); await Promise.all(setupPromises); // Set default provider (first one) for backward compatibility if (this._provider) { this._provider = providers.values().next().value; } await this.start(issuer); DyFM_Log.success('All bot providers setup complete'); } */ /* private getTokenForProviderConfig(config: DyNTS_Bot_MessagingProvider_Config): string { switch (config.messagingProvider) { case 'discord': return config.discord?.token; case 'slack': return config.slack?.token; case 'teams': return config.teams?.appPassword; default: throw new Error(`Unsupported provider: ${config.messagingProvider}`); } } */ /* async getRolePingByName(roleName: string): Promise { const role = this.getRoleByName(roleName); return role ? await this.provider.getRolePingByName?.(roleName) || '' : ''; } async getRolePingsByName(roleNames: string[]): Promise { const pings: string[] = await Promise.all( roleNames.map(roleName => this.getRolePingByName(roleName)) ); return pings.join(' '); } getRoleByName(roleName: string): any { this._rolesByNames[roleName] ??= this.provider.getRoleByName?.(roleName); return this._rolesByNames[roleName]; } */ /* async getChannelByName(channelName: string): Promise> { if (!this._channelsByNames[channelName]) { const channel = await this.provider.getChannelByName(channelName); this._channelsByNames[channelName] = { channel: channel, providerId: this.provider.getProviderId(), providerType: this.provider.getProviderName() as any, provider: this.provider }; } return this._channelsByNames[channelName]; } */ /* async sendMessageToChannelByName( channelName: string, message: string, issuer: string, ): Promise> { try { const channelWrapper = await this.getChannelByName(channelName); const channel = channelWrapper.channel; if (!channel) { throw new DyFM_Error({ ...this.getDefaultErrorSettings( 'sendMessageToChannelByName', new Error('Channel not found'), issuer ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-SMCN1`, }) } if (!channel.isTextBased) { throw new DyFM_Error({ ...this.getDefaultErrorSettings( 'sendMessageToChannelByName', new Error('Channel is not a text channel'), issuer ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-SMCN2`, }) } const sentMessage = await this.provider.sendMessageToChannelByName(channelName, message); return { message: sentMessage, providerId: this.provider.getProviderId(), providerType: this.provider.getProviderName() as any, provider: this.provider }; } catch (error) { DyFM_Error.logSimple('Error sending message to channel by name:', error); throw new DyFM_Error({ ...this.getDefaultErrorSettings('sendMessageToChannelByName', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-SMCN0`, }) } } */ /* async clearChannel( channelWrapper: DyNTS_Bot_ChannelWrapper, issuer: string, ): Promise { try { DyFM_Log.testInfo('Clearing channel... ', channelWrapper.name); await channelWrapper.provider.clearChannel(channelWrapper); DyFM_Log.success('Channel cleared'); await this.sendMessageToChannelByName( channelWrapper.name, '[SYSTEM|OPERATION|RESULT] Channel messages deletion completed successfully.', issuer ); } catch (error) { DyFM_Error.logSimple('Error clearing channel:', error); throw new DyFM_Error({ ...this.getDefaultErrorSettings('clearChannel', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-Bot-MCS-CC0`, }) } } */ }