using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Types; using TelegramBotBase.Args; using TelegramBotBase.Base; using TelegramBotBase.Enums; using TelegramBotBase.Exceptions; using TelegramBotBase.Interfaces; using TelegramBotBase.Sessions; using Console = TelegramBotBase.Tools.Console; namespace TelegramBotBase; /// /// Bot base class for full Device/Context and message handling /// /// public sealed class BotBase { internal BotBase(string apiKey, MessageClient client) { ApiKey = apiKey; Client = client; SystemSettings = new Dictionary(); SetSetting(ESettings.MaxNumberOfRetries, 5); SetSetting(ESettings.NavigationMaximum, 10); SetSetting(ESettings.LogAllMessages, false); SetSetting(ESettings.SkipAllMessages, false); SetSetting(ESettings.SaveSessionsOnConsoleExit, false); BotCommandScopes = new Dictionary>(); Sessions = new SessionManager(this); } public MessageClient Client { get; } /// /// Your TelegramBot APIKey /// public string ApiKey { get; } /// /// List of all running/active sessions /// public SessionManager Sessions { get; } /// /// Contains System commands which will be available at everytime and didnt get passed to forms, i.e. /start /// public Dictionary> BotCommandScopes { get; internal set; } /// /// Enable the SessionState (you need to implement on call forms the IStateForm interface) /// public IStateMachine StateMachine { get; internal set; } /// /// Offers functionality to manage the creation process of the start form. /// public IStartFormFactory StartFormFactory { get; internal set; } /// /// Contains the message loop factory, which cares about "message-management." /// public IMessageLoopFactory MessageLoopFactory { get; internal set; } /// /// All internal used settings. /// public Dictionary SystemSettings { get; } /// /// Start your Bot /// public async Task Start() { if (Client == null) { return; } Client.MessageLoop += Client_MessageLoop; if (StateMachine != null) { await Sessions.LoadSessionStates(StateMachine); } // Enable auto session saving if (GetSetting(ESettings.SaveSessionsOnConsoleExit, false)) { // should be waited until finish Console.SetHandler(() => { Sessions.SaveSessionStates().GetAwaiter().GetResult(); }); } DeviceSession.MaxNumberOfRetries = GetSetting(ESettings.MaxNumberOfRetries, 5); Client.StartReceiving(); } private async Task Client_MessageLoop(object sender, UpdateResult e) { try { var ds = Sessions.GetSession(e.DeviceId); if (ds == null) { ds = await Sessions.StartSession(e.DeviceId); ds.LastMessage = e.Message; OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds)); } e.Device = ds; var mr = new MessageResult(e.RawData); var i = 0; //Should formulars get navigated (allow maximum of 10, to dont get loops) do { i++; //Reset navigation ds.FormSwitched = false; await MessageLoopFactory.MessageLoop(this, ds, e, mr); mr.IsFirstHandler = false; } while (ds.FormSwitched && i < GetSetting(ESettings.NavigationMaximum, 10)); } catch (InvalidServiceProviderConfiguration ex) { var ds = Sessions.GetSession(e.DeviceId); OnException(new SystemExceptionEventArgs(e.Message.Text, e.DeviceId, ds, ex)); throw; } catch (Exception ex) { var ds = Sessions.GetSession(e.DeviceId); OnException(new SystemExceptionEventArgs(e.Message.Text, e.DeviceId, ds, ex)); } } /// /// Stop your Bot /// public async Task Stop() { if (Client == null) { return; } Client.MessageLoop -= Client_MessageLoop; Client.StopReceiving(); await Sessions.SaveSessionStates(); } /// /// Send a message to all active Sessions. /// /// /// public async Task SentToAll(string message) { if (Client == null) { return; } foreach (var s in Sessions.SessionList) { await Client.TelegramClient.SendTextMessageAsync(s.Key, message); } } /// /// This will invoke the full message loop for the device even when no "userevent" like message or action has been /// raised. /// /// Contains the device/chat id of the device to update. public async Task InvokeMessageLoop(long deviceId) { var mr = new MessageResult(new Update { Message = new Message() }); await InvokeMessageLoop(deviceId, mr); } /// /// This will invoke the full message loop for the device even when no "userevent" like message or action has been /// raised. /// /// Contains the device/chat id of the device to update. /// public async Task InvokeMessageLoop(long deviceId, MessageResult e) { try { var ds = Sessions.GetSession(deviceId); e.Device = ds; await MessageLoopFactory.MessageLoop(this, ds, new UpdateResult(e.UpdateData, ds), e); } catch (Exception ex) { var ds = Sessions.GetSession(deviceId); OnException(new SystemExceptionEventArgs(e.Message.Text, deviceId, ds, ex)); } } /// /// Will get invoke on an unhandled call. /// /// /// public void MessageLoopFactory_UnhandledCall(object sender, UnhandledCallEventArgs e) { OnUnhandledCall(e); } /// /// This method will update all local created bot commands to the botfather. /// public async Task UploadBotCommands() { foreach (var bs in BotCommandScopes) { if (bs.Value != null) { await Client.SetBotCommands(bs.Value, bs.Key); } else { await Client.DeleteBotCommands(bs.Key); } } } /// /// Searching if parameter is a known command in all configured BotCommandScopes. /// /// /// public bool IsKnownBotCommand(string command) { foreach (var scope in BotCommandScopes) { if (scope.Value.Any(a => Constants.Telegram.BotCommandIndicator + a.Command == command)) { return true; } } return false; } /// /// Could set a variety of settings to improve the bot handling. /// /// /// public void SetSetting(ESettings set, uint value) { SystemSettings[set] = value; } /// /// Could set a variety of settings to improve the bot handling. /// /// /// public void SetSetting(ESettings set, bool value) { SystemSettings[set] = value ? 1u : 0u; } /// /// Could get the current value of a setting /// /// /// /// public uint GetSetting(ESettings set, uint defaultValue) { if (!SystemSettings.ContainsKey(set)) { return defaultValue; } return SystemSettings[set]; } /// /// Could get the current value of a setting /// /// /// /// public bool GetSetting(ESettings set, bool defaultValue) { if (!SystemSettings.ContainsKey(set)) { return defaultValue; } return SystemSettings[set] != 0u; } #region "Events" private readonly EventHandlerList _events = new(); private static readonly object EvSessionBegins = new(); private static readonly object EvMessage = new(); public delegate Task BotCommandEventHandler(object sender, BotCommandEventArgs e); private static readonly object EvException = new(); private static readonly object EvUnhandledCall = new(); #endregion #region "Events" /// /// Will be called if a session/context gets started /// public event EventHandler SessionBegins { add => _events.AddHandler(EvSessionBegins, value); remove => _events.RemoveHandler(EvSessionBegins, value); } public void OnSessionBegins(SessionBeginEventArgs e) { (_events[EvSessionBegins] as EventHandler)?.Invoke(this, e); } /// /// Will be called on incoming message /// public event EventHandler Message { add => _events.AddHandler(EvMessage, value); remove => _events.RemoveHandler(EvMessage, value); } public void OnMessage(MessageIncomeEventArgs e) { (_events[EvMessage] as EventHandler)?.Invoke(this, e); } /// /// Will be called if a bot command gets raised /// public event BotCommandEventHandler BotCommand; public async Task OnBotCommand(BotCommandEventArgs e) { if (BotCommand != null) { await BotCommand(this, e); } } /// /// Will be called on an inner exception /// public event EventHandler Exception { add => _events.AddHandler(EvException, value); remove => _events.RemoveHandler(EvException, value); } public void OnException(SystemExceptionEventArgs e) { (_events[EvException] as EventHandler)?.Invoke(this, e); } /// /// Will be called if no form handled this call /// public event EventHandler UnhandledCall { add => _events.AddHandler(EvUnhandledCall, value); remove => _events.RemoveHandler(EvUnhandledCall, value); } public void OnUnhandledCall(UnhandledCallEventArgs e) { (_events[EvUnhandledCall] as EventHandler)?.Invoke(this, e); } #endregion }