Initial branch creation with 1. experiment upload.

This commit is contained in:
Florian Zevedei 2024-01-18 19:41:03 +01:00
parent 2d3393aa05
commit 9f41ab352f
14 changed files with 892 additions and 0 deletions

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Sessions;
namespace DemoBot.ActionManager.Actions
{
public class GuidAction : IExternalAction
{
public Type FormType { get; }
public String Method { get; set; }
public Action<FormBase, Guid> SetProperty { get; set; }
Guid? _lastValue { get; set; }
public GuidAction(Type formType, string method, Action<FormBase, Guid> setProperty)
{
FormType = formType;
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr, DeviceSession session)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await session.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(String method, Guid guid)
{
return new CallbackData(method, guid.ToString());
}
}
public class GuidAction<TForm> : IExternalAction
where TForm : FormBase
{
public String Method { get; set; }
public Action<TForm, Guid> SetProperty { get; set; }
Guid? _lastValue { get; set; }
public GuidAction(string method, Action<TForm, Guid> setProperty)
{
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr, DeviceSession session)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await session.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(String method, Guid guid)
{
return new CallbackData(method, guid.ToString());
}
}
public static class GuidAction_Extensions
{
public static void AddGuidAction<TForm>(this ExternalActionManager manager, string method, Action<TForm, Guid> action)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new GuidAction<TForm>(method, action));
}
public static void AddGuidAction(this ExternalActionManager manager, Type formType, string method, Action<FormBase, Guid> action)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new GuidAction(formType, method, action));
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Diagnostics;
using TelegramBotBase.Base;
using TelegramBotBase.DependencyInjection;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace DemoBot.ActionManager.Actions
{
public class StartWithAction : IExternalAction
{
public Type FormType { get; }
public string Value { get; set; }
public Action<FormBase, String> SetProperty { get; set; }
String? _lastValue { get; set; }
public StartWithAction(Type formType, string value, Action<FormBase, string> setProperty)
{
FormType = formType;
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
if (!raw_action.StartsWith(Value))
return false;
_lastValue = raw_action;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr, DeviceSession session)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
{
SetProperty(new_form, _lastValue);
}
await session.ActiveForm.NavigateTo(new_form);
}
}
public class StartWithAction<TForm> : IExternalAction
where TForm : FormBase
{
public string Value { get; set; }
public Action<TForm, String> SetProperty { get; set; }
String? _lastValue { get; set; }
public StartWithAction(string value, Action<TForm, String> setProperty)
{
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
if (!raw_action.StartsWith(Value))
return false;
_lastValue = raw_action;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr, DeviceSession session)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
{
SetProperty(new_form, _lastValue);
}
await session.ActiveForm.NavigateTo(new_form);
}
}
public static class StartWithAction_Extensions
{
public static void AddStartsWithAction<TForm>(this ExternalActionManager manager, string value, Action<TForm, String> setProperty)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StartWithAction<TForm>(value, setProperty));
}
public static void AddStartsWithAction(this ExternalActionManager manager, Type formType, string value, Action<FormBase, String> setProperty)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StartWithAction(formType, value, setProperty));
}
}
}

View File

@ -0,0 +1,42 @@
using DemoBot.ActionManager.Actions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace DemoBot.ActionManager
{
public partial class ExternalActionManager
{
List<IExternalAction> actions = new List<IExternalAction>();
public void Add(IExternalAction action)
{
actions.Add(action);
}
public async Task<bool> ManageCall(UpdateResult ur, MessageResult mr, DeviceSession session)
{
foreach (var action in actions)
{
if (!action.DoesFit(mr.RawData))
continue;
await action.DoAction(ur, mr, session);
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,15 @@
using TelegramBotBase.Base;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace DemoBot.ActionManager
{
public interface IExternalAction
{
bool DoesFit(string raw_action);
Task DoAction(UpdateResult ur, MessageResult mr, DeviceSession session);
}
}

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DemoBot.ActionManager;
using Telegram.Bot.Types.Enums;
using TelegramBotBase;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace DemoBot
{
public class CustomFormBaseMessageLoop : IMessageLoopFactory
{
private static readonly object EvUnhandledCall = new();
private readonly EventHandlerList _events = new();
public ExternalActionManager ExternalActionManager { get; set; }
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr)
{
var update = ur.RawData;
if (update.Type != UpdateType.Message
&& update.Type != UpdateType.EditedMessage
&& update.Type != UpdateType.CallbackQuery)
{
return;
}
//Is this a bot command ?
if (mr.IsFirstHandler && mr.IsBotCommand && bot.IsKnownBotCommand(mr.BotCommand))
{
var sce = new BotCommandEventArgs(mr.BotCommand, mr.BotCommandParameters, mr.Message, session.DeviceId,
session);
await bot.OnBotCommand(sce);
if (sce.Handled)
{
return;
}
}
mr.Device = session;
ur.Device = session;
var activeForm = session.ActiveForm;
//Pre Loading Event
await activeForm.PreLoad(mr);
//Send Load event to controls
await activeForm.LoadControls(mr);
//Loading Event
await activeForm.Load(mr);
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (update.Type == UpdateType.Message)
{
if ((mr.MessageType == MessageType.Contact)
| (mr.MessageType == MessageType.Document)
| (mr.MessageType == MessageType.Location)
| (mr.MessageType == MessageType.Photo)
| (mr.MessageType == MessageType.Video)
| (mr.MessageType == MessageType.Audio))
{
await activeForm.SentData(new DataResult(ur));
}
}
//Message edited ?
if (update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{
//Send Action event to controls
await activeForm.ActionControls(mr);
//Send Action event to form itself
await activeForm.Action(mr);
if (!mr.Handled)
{
var handled = await ExternalActionManager?.ManageCall(ur, mr, session);
if (handled)
{
mr.Handled = true;
if (!session.FormSwitched)
{
return;
}
}
else
{
var uhc = new UnhandledCallEventArgs(ur.Message.Text, mr.RawData, session.DeviceId, mr.MessageId, ur.Message, session);
OnUnhandledCall(uhc);
if (uhc.Handled)
{
mr.Handled = true;
if (!session.FormSwitched)
{
return;
}
}
}
}
}
if (!session.FormSwitched)
{
//Render Event
await activeForm.RenderControls(mr);
await activeForm.Render(mr);
}
}
/// <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);
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenForm : AutoCleanForm
{
public String value { get; set; }
public HiddenForm() {
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
value = value.Replace("_", "\\_");
await Device.Send($"Welcome to Hidden form\n\nThe given value is {value}", bf);
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenOpenForm : AutoCleanForm
{
public Guid guid { get; set; }
public HiddenOpenForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
await Device.Send($"Welcome to Hidden open form\n\nYour guid is: {guid}", bf);
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenTicketForm : AutoCleanForm
{
public Guid ticketId { get; set; }
public HiddenTicketForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
await Device.Send($"Welcome to Hidden ticket form\n\nYour ticket Id is: {ticketId}", bf);
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
internal class StartForm : AutoCleanForm
{
public StartForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
Opened += StartForm_Opened;
}
private async Task StartForm_Opened(object sender, EventArgs e)
{
await Device.Send("Hey!", disableNotification: true);
}
public override async Task Load(MessageResult message)
{
}
}
}

View File

@ -0,0 +1,165 @@
using DemoBot.ActionManager;
using DemoBot.ActionManager.Actions;
using DemoBot.Forms;
using Telegram.Bot;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Builder;
using TelegramBotBase.Commands;
using TelegramBotBase.Form;
namespace DemoBot
{
internal class Program
{
public static String Token = Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set");
static async Task Main(string[] args)
{
//Using a custom FormBase message loop which is based on the original FormBaseMessageLoop within the framework.
//Would integrate this later into the BotBaseBuilder -> MessageLoop step.
var cfb = new CustomFormBaseMessageLoop();
var eam = new ExternalActionManager();
eam.AddStartsWithAction<HiddenForm>("n_", (a, b) =>
{
a.value = b;
});
eam.AddStartsWithAction(typeof(HiddenForm), "t_", (a, b) =>
{
var hf = a as HiddenForm;
if (hf == null)
return;
hf.value = b;
});
eam.AddGuidAction<HiddenTicketForm>("tickets", (a, b) =>
{
a.ticketId = b;
});
eam.AddGuidAction<HiddenOpenForm>("open", (a, b) =>
{
a.guid = b;
});
cfb.ExternalActionManager = eam;
var bb = BotBaseBuilder.Create()
.WithAPIKey(Token)
.CustomMessageLoop(cfb)
.WithStartForm<Forms.StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
a.Add("test", "Sends a test notification");
})
.NoSerialization()
.UseGerman()
.UseSingleThread()
.Build();
bb.BotCommand += Bb_BotCommand;
bb.UnhandledCall += Bb_UnhandledCall;
await bb.Start();
await bb.UploadBotCommands();
Console.WriteLine("Bot started.");
Console.ReadLine();
await bb.Stop();
}
private static void Bb_UnhandledCall(object? sender, TelegramBotBase.Args.UnhandledCallEventArgs e)
{
Console.WriteLine($"Unhandled call: {e.RawData}");
}
private static async Task Bb_BotCommand(object sender, TelegramBotBase.Args.BotCommandEventArgs e)
{
var current_form = e.Device.ActiveForm;
switch (e.Command)
{
case "/start":
//Already on start form
if (current_form.GetType() == typeof(Forms.StartForm))
{
return;
}
var st = new Forms.StartForm();
await current_form.NavigateTo(st);
break;
case "/test":
//Send test notification
//Test values
String max_value = "n_".PadRight(32, '5'); //Starts with
String max_value2 = "t_".PadRight(32, '5'); //Starts with
Guid test_value = Guid.NewGuid(); //Unhandled caller
var callback_guid = GuidAction.GetCallback("open", Guid.NewGuid()); //HiddenOpenForm
var callback_tickets = GuidAction.GetCallback("tickets", Guid.NewGuid()); //HiddenTicketForm
String message = $"Test notification from 'outside'\n\nTest values are:\n\nTest: {max_value}\nTest2: {max_value2}\nTest (Guid): {test_value.ToString()}\nTest (Callback Guid): {callback_guid.Value}\nTickets (Guid): {callback_tickets.Value}\n";
var tb = new TelegramBotClient(Token);
var bf = new ButtonForm();
bf.AddButtonRow(new ButtonBase("Ok", "n_ok"), new ButtonBase("Later", "n_later"));
bf.AddButtonRow("Test", max_value);
bf.AddButtonRow("Test2", max_value2);
bf.AddButtonRow("Test (Guid)", test_value.ToString());
bf.AddButtonRow("Test (Callback Gui)", callback_guid);
bf.AddButtonRow("Tickets", callback_tickets);
bf.AddButtonRow("Close", "close");
await tb.SendTextMessageAsync(e.DeviceId, message, disableNotification: true, replyMarkup: (InlineKeyboardMarkup)bf);
break;
}
}
}
}

View File

@ -0,0 +1,11 @@
# ExternalActionManager
Idea of this experiment is to find a good intuitive way for handling "Unhandled calls".
Right now they are "thrown" into an event handler and you have to take care over them on yourself.
Source of them would be in most cases Telegram Bot messages which has an Inlinekeyboard attached.
And the button press should navigate to a different form, or invoke a different action.
Begin: 18.01.2024

View File

@ -1,3 +1,20 @@
# Development Lab
Attention: This is a lab for **experimental features**. Here we can try out different ideas and scenarios if they will work and what can be improved.
There is no guarantee that any of these ideas comes in the main project.
Please try everything on your own. Nothing can work, so everything can as well.
You will find all experiments within the ["Experiments"](Experiements) sub folder.
For feedback please join our Telegram group.
**Support group: [@tgbotbase](https://t.me/tgbotbase)**
---
# .NET Telegram Bot Framework - Context based addon
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/vpre/TelegramBotBase.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase/)

View File

@ -36,6 +36,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Exam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.Extensions.Images.IronSoftware", "TelegramBotBase.Extensions.Images.IronSoftware\TelegramBotBase.Extensions.Images.IronSoftware.csproj", "{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Experiments", "Experiments", "{2E1FF127-4DC2-40A1-849C-ED1432204E8A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalActionManager", "Experiments\ExternalActionManager\DemoBot\ExternalActionManager.csproj", "{5184D3F8-8526-413D-8EB0-23636EA7A4EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -90,6 +94,10 @@ Global
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.Build.0 = Release|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -105,6 +113,7 @@ Global
{067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
{5184D3F8-8526-413D-8EB0-23636EA7A4EF} = {2E1FF127-4DC2-40A1-849C-ED1432204E8A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057}