using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.MessageLoops;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase
{
///
/// Bot base class for full Device/Context and Messagehandling
///
///
public class BotBase
{
public MessageClient Client { get; set; }
///
/// Your TelegramBot APIKey
///
public String APIKey { get; set; } = "";
///
/// List of all running/active sessions
///
public SessionBase Sessions { get; set; }
///
/// Contains System commands which will be available at everytime and didnt get passed to forms, i.e. /start
///
public Dictionary> BotCommandScopes { get; set; } = new Dictionary>();
#region "Events"
private EventHandlerList __Events = new EventHandlerList();
private static object __evSessionBegins = new object();
private static object __evMessage = new object();
private static object __evSystemCall = new object();
public delegate Task BotCommandEventHandler(object sender, BotCommandEventArgs e);
private static object __evException = new object();
private static object __evUnhandledCall = new object();
#endregion
///
/// Enable the SessionState (you need to implement on call forms the IStateForm interface)
///
public IStateMachine StateMachine { get; set; }
///
/// Offers functionality to manage the creation process of the start form.
///
public IStartFormFactory StartFormFactory { get; set; }
///
/// Contains the message loop factory, which cares about "message-management."
///
public IMessageLoopFactory MessageLoopFactory { get; set; }
///
/// All internal used settings.
///
public Dictionary SystemSettings { get; private set; }
public BotBase()
{
this.SystemSettings = new Dictionary();
SetSetting(eSettings.MaxNumberOfRetries, 5);
SetSetting(eSettings.NavigationMaximum, 10);
SetSetting(eSettings.LogAllMessages, false);
SetSetting(eSettings.SkipAllMessages, false);
SetSetting(eSettings.SaveSessionsOnConsoleExit, false);
this.BotCommandScopes = new Dictionary>();
this.Sessions = new SessionBase();
this.Sessions.BotBase = this;
}
///
/// Start your Bot
///
public void Start()
{
if (this.Client == null)
return;
this.Client.MessageLoop += Client_MessageLoop;
if (this.StateMachine != null)
{
this.Sessions.LoadSessionStates(this.StateMachine);
}
//Enable auto session saving
if (this.GetSetting(eSettings.SaveSessionsOnConsoleExit, false))
{
TelegramBotBase.Tools.Console.SetHandler(() =>
{
this.Sessions.SaveSessionStates();
});
}
DeviceSession.MaxNumberOfRetries = this.GetSetting(eSettings.MaxNumberOfRetries, 5);
this.Client.StartReceiving();
}
private async Task Client_MessageLoop(object sender, UpdateResult e)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
if (ds == null)
{
ds = this.Sessions.StartSession(e.DeviceId).GetAwaiter().GetResult();
e.Device = ds;
ds.LastMessage = e.RawData.Message;
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
}
var mr = new MessageResult(e.RawData);
int 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 < this.GetSetting(eSettings.NavigationMaximum, 10));
}
///
/// Stop your Bot
///
public void Stop()
{
if (this.Client == null)
return;
this.Client.MessageLoop -= Client_MessageLoop;
this.Client.StopReceiving();
this.Sessions.SaveSessionStates();
}
///
/// Send a message to all active Sessions.
///
///
///
public async Task SentToAll(String message)
{
if (this.Client == null)
return;
foreach (var s in this.Sessions.SessionList)
{
await this.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();
mr.UpdateData = 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
{
DeviceSession ds = this.Sessions.GetSession(DeviceId);
e.Device = ds;
await MessageLoopFactory.MessageLoop(this, ds, new UpdateResult(e.UpdateData, ds), e);
//await Client_Loop(this, e);
}
catch (Exception ex)
{
DeviceSession ds = this.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 this.BotCommandScopes)
{
if(bs.Value !=null)
{
await this.Client.SetBotCommands(bs.Value, bs.Key);
}
else
{
await this.Client.DeleteBotCommands(bs.Key);
}
}
}
///
/// Searching if parameter is a known command in all configured BotCommandScopes.
///
///
///
public bool IsKnownBotCommand(String command)
{
foreach (var scope in this.BotCommandScopes)
{
if (scope.Value.Any(a => "/" + 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)
{
this.SystemSettings[set] = Value;
}
///
/// Could set a variety of settings to improve the bot handling.
///
///
///
public void SetSetting(eSettings set, bool Value)
{
this.SystemSettings[set] = (Value ? 1u : 0u);
}
///
/// Could get the current value of a setting
///
///
///
///
public uint GetSetting(eSettings set, uint defaultValue)
{
if (!this.SystemSettings.ContainsKey(set))
return defaultValue;
return this.SystemSettings[set];
}
///
/// Could get the current value of a setting
///
///
///
///
public bool GetSetting(eSettings set, bool defaultValue)
{
if (!this.SystemSettings.ContainsKey(set))
return defaultValue;
return this.SystemSettings[set] == 0u ? false : true;
}
#region "Events"
///
/// Will be called if a session/context gets started
///
public event EventHandler SessionBegins
{
add
{
this.__Events.AddHandler(__evSessionBegins, value);
}
remove
{
this.__Events.RemoveHandler(__evSessionBegins, value);
}
}
public void OnSessionBegins(SessionBeginEventArgs e)
{
(this.__Events[__evSessionBegins] as EventHandler)?.Invoke(this, e);
}
///
/// Will be called on incomming message
///
public event EventHandler Message
{
add
{
this.__Events.AddHandler(__evMessage, value);
}
remove
{
this.__Events.RemoveHandler(__evMessage, value);
}
}
public void OnMessage(MessageIncomeEventArgs e)
{
(this.__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 (this.BotCommand != null)
await BotCommand(this, e);
}
///
/// Will be called on an inner exception
///
public event EventHandler Exception
{
add
{
this.__Events.AddHandler(__evException, value);
}
remove
{
this.__Events.RemoveHandler(__evException, value);
}
}
public void OnException(SystemExceptionEventArgs e)
{
(this.__Events[__evException] as EventHandler)?.Invoke(this, e);
}
///
/// Will be called if no form handeled this call
///
public event EventHandler UnhandledCall
{
add
{
this.__Events.AddHandler(__evUnhandledCall, value);
}
remove
{
this.__Events.RemoveHandler(__evUnhandledCall, value);
}
}
public void OnUnhandledCall(UnhandledCallEventArgs e)
{
(this.__Events[__evUnhandledCall] as EventHandler)?.Invoke(this, e);
}
#endregion
}
}