Merge pull request #53 from MajMcCloud/development

Integrating development branch into master
This commit is contained in:
Florian Zevedei 2023-12-06 15:23:47 +01:00 committed by GitHub
commit 2a873cc29b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 381 additions and 12 deletions

View File

@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
namespace DependencyInjection.Database;
public class BotDbContext : DbContext
{
public BotDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace DependencyInjection.Database;
public class User
{
public long Id { get; set; }
public string LastMessage { get; set; }
}

View File

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

View File

@ -0,0 +1,67 @@
using DependencyInjection.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection.Forms
{
public class ConfirmationForm : FormBase
{
private readonly BotDbContext _dbContext;
public ConfirmationForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
{
await this.NavigateTo<StartForm>();
return;
}
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Go back");
switch (message.RawData)
{
case "back":
await this.NavigateTo<StartForm>();
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Back", "back");
await Device.Send($"ConfirmationForm: Your last message was: {user.LastMessage}. Click \"Back\" to get back.", bf);
}
}
}

View File

@ -0,0 +1,35 @@
using DependencyInjection.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.Builder;
namespace DependencyInjection
{
internal class Program
{
static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection()
.AddDbContext<BotDbContext>(x => x.UseInMemoryDatabase("TelegramBotBase"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var bot = BotBaseBuilder.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ??
throw new Exception("API_KEY is not set"))
.DefaultMessageLoop()
.WithServiceProvider<StartForm>(serviceProvider)
.NoProxy()
.NoCommands()
.NoSerialization()
.DefaultLanguage()
.Build();
await bot.Start();
await Task.Delay(-1);
}
}
}

View File

@ -0,0 +1,79 @@
using DependencyInjection.Database;
using DependencyInjection.Forms;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection;
public class StartForm : FormBase
{
private readonly BotDbContext _dbContext;
public StartForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user is null)
{
user = new User
{
Id = Device.DeviceId,
LastMessage = "<unknown>"
};
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
}
if (message.IsAction)
return;
user.LastMessage = string.IsNullOrWhiteSpace(message.MessageText) ? "<unknown>" : message.MessageText;
await _dbContext.SaveChangesAsync();
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Ok");
switch(message.RawData)
{
case "open":
await this.NavigateTo(typeof(ConfirmationForm));
var new_form = await this.NavigateTo<ConfirmationForm>();
if (new_form == null)
{
await Device.Send("Cant open ConfirmationForm");
}
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Open confirmation", "open");
await Device.Send($"Your last message's text was: `{user.LastMessage}`", bf);
}
}

View File

@ -27,6 +27,8 @@ namespace InlineAndReplyCombination
await BotBaseInstance.UploadBotCommands();
BotBaseInstance.BotCommand += BotBaseInstance_BotCommand;
await BotBaseInstance.Start();
@ -37,5 +39,28 @@ namespace InlineAndReplyCombination
await BotBaseInstance.Stop();
}
private static async Task BotBaseInstance_BotCommand(object sender, TelegramBotBase.Args.BotCommandEventArgs e)
{
switch(e.Command)
{
case "/start":
var start = new StartForm();
await e.Device.ActiveForm.NavigateTo(start);
break;
}
}
}
}

View File

@ -1,6 +1,6 @@
# .NET Telegram Bot Framework - Context based addon
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase/)
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/vpre/TelegramBotBase.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
@ -1083,3 +1083,8 @@ Having already a web application and want to add a TelegramBot side-by-side with
Want to use Inline- and ReplyMarkup at the same time ? Here is an example how you can do that:
- [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination)
Alpha: Full Dependency Injection example within this framework.
- [Examples/DependencyInjection](Examples/DependencyInjection)

View File

@ -31,7 +31,7 @@ namespace TelegramBotBase.Extensions.Serializer.Database.MSSQL
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
}

View File

@ -32,6 +32,8 @@ public class FormBase : IDisposable
public MessageClient Client { get; set; }
IServiceProvider _serviceProvider = null;
/// <summary>
/// has this formular already been disposed ?
/// </summary>

View File

@ -39,7 +39,10 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
/// </summary>
private Dictionary<BotCommandScope, List<BotCommand>> BotCommandScopes { get; } = new();
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
public BotBase Build()
{
var bot = new BotBase(_apiKey, _client)

View File

@ -2,5 +2,9 @@
public interface IBuildingStage
{
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
BotBase Build();
}

View File

@ -0,0 +1,86 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Form;
namespace TelegramBotBase.DependencyInjection
{
public static class Extensions
{
internal static FieldInfo _ServiceProviderField = typeof(FormBase).GetField("_serviceProvider", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Main variant)
/// </summary>
/// <typeparam name="NewForm"></typeparam>
/// <param name="args"></param>
/// <returns></returns>
public static async Task<NewForm> NavigateTo<NewForm>(this FormBase current_form, params object[] args)
where NewForm : FormBase
{
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, typeof(NewForm)) as NewForm;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Alternative variant)
/// </summary>
/// <param name="current_form"></param>
/// <param name="formBaseType"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static async Task<FormBase> NavigateTo(this FormBase current_form, Type formBaseType, params object[] args)
{
if (!typeof(FormBase).IsAssignableFrom(formBaseType))
throw new ArgumentException($"{nameof(formBaseType)} argument must be a {nameof(FormBase)} type");
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, formBaseType) as FormBase;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Sets the internal service provider field.
/// </summary>
/// <param name="form"></param>
/// <param name="serviceProvider"></param>
public static void SetServiceProvider(this FormBase form, IServiceProvider serviceProvider)
{
_ServiceProviderField?.SetValue(form, serviceProvider);
}
/// <summary>
/// Gets the internal service provider field value.
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public static IServiceProvider GetServiceProvider(this FormBase form)
{
var sp = _ServiceProviderField?.GetValue(form) as IServiceProvider;
return sp;
}
}
}

View File

@ -12,7 +12,7 @@ public class DefaultStartFormFactory : IStartFormFactory
{
if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{
throw new ArgumentException("startFormClass argument must be a FormBase type");
throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
}
_startFormClass = startFormClass;

View File

@ -1,5 +1,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.DependencyInjection;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
@ -14,16 +15,21 @@ public class ServiceProviderStartFormFactory : IStartFormFactory
{
if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{
throw new ArgumentException("startFormClass argument must be a FormBase type");
throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
}
_startFormClass = startFormClass;
_serviceProvider = serviceProvider;
}
public FormBase CreateForm()
{
return (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass);
var fb = (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass);
//Sets an internal field for future ServiceProvider navigation
fb.SetServiceProvider(_serviceProvider);
return fb;
}
}

View File

@ -72,6 +72,12 @@ public class FormBaseMessageLoop : IMessageLoopFactory
}
}
//Message edited ?
if(update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{

View File

@ -65,6 +65,12 @@ public class FullMessageLoop : IMessageLoopFactory
}
}
//Message edited ?
if (update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{

View File

@ -29,7 +29,7 @@ public class JsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));

View File

@ -30,7 +30,7 @@ public class SimpleJsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));

View File

@ -27,7 +27,7 @@ public class XmlStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6;net7</TargetFrameworks>
<LangVersion>10</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>

View File

@ -30,7 +30,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCoreBot", "Examples\EFCor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Examples\BotAndWebApplication\BotAndWebApplication.csproj", "{52EA3201-02E8-46F5-87C4-B4752C8A815C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -78,6 +80,10 @@ Global
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.Build.0 = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -91,6 +97,7 @@ Global
{261BED47-0404-4A9A-86FC-047DE42A7D25} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{52EA3201-02E8-46F5-87C4-B4752C8A815C} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057}