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
}