420 lines
12 KiB
C#
420 lines
12 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Bot base class for full Device/Context and message handling
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
public sealed class BotBase
|
|
{
|
|
internal BotBase(string apiKey, MessageClient client)
|
|
{
|
|
ApiKey = apiKey;
|
|
Client = client;
|
|
|
|
SystemSettings = new Dictionary<ESettings, uint>();
|
|
|
|
SetSetting(ESettings.MaxNumberOfRetries, 5);
|
|
SetSetting(ESettings.NavigationMaximum, 10);
|
|
SetSetting(ESettings.LogAllMessages, false);
|
|
SetSetting(ESettings.SkipAllMessages, false);
|
|
SetSetting(ESettings.SaveSessionsOnConsoleExit, false);
|
|
|
|
BotCommandScopes = new Dictionary<BotCommandScope, List<BotCommand>>();
|
|
|
|
Sessions = new SessionManager(this);
|
|
}
|
|
|
|
public MessageClient Client { get; }
|
|
|
|
/// <summary>
|
|
/// Your TelegramBot APIKey
|
|
/// </summary>
|
|
public string ApiKey { get; }
|
|
|
|
/// <summary>
|
|
/// List of all running/active sessions
|
|
/// </summary>
|
|
public SessionManager Sessions { get; }
|
|
|
|
/// <summary>
|
|
/// Contains System commands which will be available at everytime and didnt get passed to forms, i.e. /start
|
|
/// </summary>
|
|
public Dictionary<BotCommandScope, List<BotCommand>> BotCommandScopes { get; internal set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Enable the SessionState (you need to implement on call forms the IStateForm interface)
|
|
/// </summary>
|
|
public IStateMachine StateMachine { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Offers functionality to manage the creation process of the start form.
|
|
/// </summary>
|
|
public IStartFormFactory StartFormFactory { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Contains the message loop factory, which cares about "message-management."
|
|
/// </summary>
|
|
public IMessageLoopFactory MessageLoopFactory { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// All internal used settings.
|
|
/// </summary>
|
|
public Dictionary<ESettings, uint> SystemSettings { get; }
|
|
|
|
|
|
/// <summary>
|
|
/// Start your Bot
|
|
/// </summary>
|
|
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));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Stop your Bot
|
|
/// </summary>
|
|
public async Task Stop()
|
|
{
|
|
if (Client == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Client.MessageLoop -= Client_MessageLoop;
|
|
Client.StopReceiving();
|
|
|
|
await Sessions.SaveSessionStates();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a message to all active Sessions.
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <returns></returns>
|
|
public async Task SentToAll(string message)
|
|
{
|
|
if (Client == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var s in Sessions.SessionList)
|
|
{
|
|
await Client.TelegramClient.SendTextMessageAsync(s.Key, message);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This will invoke the full message loop for the device even when no "userevent" like message or action has been
|
|
/// raised.
|
|
/// </summary>
|
|
/// <param name="deviceId">Contains the device/chat id of the device to update.</param>
|
|
public async Task InvokeMessageLoop(long deviceId)
|
|
{
|
|
var mr = new MessageResult(new Update
|
|
{
|
|
Message = new Message()
|
|
});
|
|
|
|
await InvokeMessageLoop(deviceId, mr);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will invoke the full message loop for the device even when no "userevent" like message or action has been
|
|
/// raised.
|
|
/// </summary>
|
|
/// <param name="deviceId">Contains the device/chat id of the device to update.</param>
|
|
/// <param name="e"></param>
|
|
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));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Will get invoke on an unhandled call.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
public void MessageLoopFactory_UnhandledCall(object sender, UnhandledCallEventArgs e)
|
|
{
|
|
OnUnhandledCall(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method will update all local created bot commands to the botfather.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searching if parameter is a known command in all configured BotCommandScopes.
|
|
/// </summary>
|
|
/// <param name="command"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could set a variety of settings to improve the bot handling.
|
|
/// </summary>
|
|
/// <param name="set"></param>
|
|
/// <param name="value"></param>
|
|
public void SetSetting(ESettings set, uint value)
|
|
{
|
|
SystemSettings[set] = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could set a variety of settings to improve the bot handling.
|
|
/// </summary>
|
|
/// <param name="set"></param>
|
|
/// <param name="value"></param>
|
|
public void SetSetting(ESettings set, bool value)
|
|
{
|
|
SystemSettings[set] = value ? 1u : 0u;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could get the current value of a setting
|
|
/// </summary>
|
|
/// <param name="set"></param>
|
|
/// <param name="defaultValue"></param>
|
|
/// <returns></returns>
|
|
public uint GetSetting(ESettings set, uint defaultValue)
|
|
{
|
|
if (!SystemSettings.ContainsKey(set))
|
|
{
|
|
return defaultValue;
|
|
}
|
|
|
|
return SystemSettings[set];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Could get the current value of a setting
|
|
/// </summary>
|
|
/// <param name="set"></param>
|
|
/// <param name="defaultValue"></param>
|
|
/// <returns></returns>
|
|
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"
|
|
|
|
/// <summary>
|
|
/// Will be called if a session/context gets started
|
|
/// </summary>
|
|
public event EventHandler<SessionBeginEventArgs> SessionBegins
|
|
{
|
|
add => _events.AddHandler(EvSessionBegins, value);
|
|
remove => _events.RemoveHandler(EvSessionBegins, value);
|
|
}
|
|
|
|
public void OnSessionBegins(SessionBeginEventArgs e)
|
|
{
|
|
(_events[EvSessionBegins] as EventHandler<SessionBeginEventArgs>)?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will be called on incoming message
|
|
/// </summary>
|
|
public event EventHandler<MessageIncomeEventArgs> Message
|
|
{
|
|
add => _events.AddHandler(EvMessage, value);
|
|
remove => _events.RemoveHandler(EvMessage, value);
|
|
}
|
|
|
|
public void OnMessage(MessageIncomeEventArgs e)
|
|
{
|
|
(_events[EvMessage] as EventHandler<MessageIncomeEventArgs>)?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will be called if a bot command gets raised
|
|
/// </summary>
|
|
public event BotCommandEventHandler BotCommand;
|
|
|
|
|
|
public async Task OnBotCommand(BotCommandEventArgs e)
|
|
{
|
|
if (BotCommand != null)
|
|
{
|
|
await BotCommand(this, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will be called on an inner exception
|
|
/// </summary>
|
|
public event EventHandler<SystemExceptionEventArgs> Exception
|
|
{
|
|
add => _events.AddHandler(EvException, value);
|
|
remove => _events.RemoveHandler(EvException, value);
|
|
}
|
|
|
|
public void OnException(SystemExceptionEventArgs e)
|
|
{
|
|
(_events[EvException] as EventHandler<SystemExceptionEventArgs>)?.Invoke(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will be called if no form handled this call
|
|
/// </summary>
|
|
public event EventHandler<UnhandledCallEventArgs> UnhandledCall
|
|
{
|
|
add => _events.AddHandler(EvUnhandledCall, value);
|
|
remove => _events.RemoveHandler(EvUnhandledCall, value);
|
|
}
|
|
|
|
public void OnUnhandledCall(UnhandledCallEventArgs e)
|
|
{
|
|
(_events[EvUnhandledCall] as EventHandler<UnhandledCallEventArgs>)?.Invoke(this, e);
|
|
}
|
|
|
|
#endregion
|
|
}
|