Merge 9b3e3de55df176ea66f953d92a330c4d2ce25ed7 into 26152ee348bafaf898583217217d60de0cb75c60

This commit is contained in:
AmirAbbas 2023-12-29 20:35:33 +01:00 committed by GitHub
commit 28ff3ed5ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 701 additions and 44 deletions

View File

@ -0,0 +1,47 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
internal sealed class StartForm : FormBase
{
public override async Task PreLoad(MessageResult message)
{
await this.Device.Send("PreLoad");
await Task.Delay(200);
}
public override async Task Load(MessageResult message)
{
await this.Device.Send("Load");
await Task.Delay(200);
}
public override async Task Edited(MessageResult message)
{
await this.Device.Send("Edited");
await Task.Delay(200);
}
public override async Task Action(MessageResult message)
{
await this.Device.Send("Action");
await Task.Delay(200);
}
public override async Task SentData(DataResult data)
{
await this.Device.Send("SentData");
await Task.Delay(200);
}
public override async Task Render(MessageResult message)
{
await this.Device.Send("Render");
await Task.Delay(200);
}
}

View File

@ -0,0 +1,14 @@
<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,195 @@
using Telegram.Bot.Types.Enums;
using TelegramBotBase;
using TelegramBotBase.Builder;
using TelegramBotBase.MessageLoops.Extensions;
public class Program
{
private static async Task Main(string[] args)
{
var bot = GetPhotoBot();
await bot.Start();
Console.WriteLine("Bot started :)");
Console.ReadLine();
}
/// <summary>
/// Creates a bot with middleware message loop and authentication for admin user
/// </summary>
private static BotBase GetAdminBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.Use(async (container, next) =>
{
var updateResult = container.UpdateResult;
if (updateResult.Message is not null)
{
if (updateResult.Message.From is not null)
{
var fromId = updateResult.Message.From.Id;
if (fromId == 1)
{
await next();
}
}
}
return;
})
.UseValidUpdateTypes(
UpdateType.Message,
UpdateType.EditedMessage,
UpdateType.CallbackQuery)
.UseBotCommands()
.UseForms())
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
/// <summary>
/// Creates a bot with middleware message loop for handle inline queries
/// </summary>
private static BotBase GetInlineQueryBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.UseValidUpdateTypes(UpdateType.InlineQuery)
.Use(async (container, next) =>
{
var query = container.UpdateResult.RawData.InlineQuery.Query;
if (!string.IsNullOrWhiteSpace(query))
{
// logic
await next();
}
return;
}))
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
/// <summary>
/// Creates a bot with middleware message loop like form base message loop
/// </summary>
private static BotBase GetFormBaseBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.UseValidUpdateTypes(
UpdateType.Message,
UpdateType.EditedMessage,
UpdateType.CallbackQuery)
.UseBotCommands()
.UseForms())
// OR instead of UseForms
// .UsePreLoad()
// .UseLoad()
// .UseAllAttachments()
// .UseActions()
// .UseRender()
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
/// <summary>
/// Creates a bot with middleware message loop like form base message loop
/// </summary>
private static BotBase GetFullBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.UseBotCommands()
.UseForms())
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
/// <summary>
/// Creates a bot with middleware message loop like minimal message loop
/// </summary>
private static BotBase GetMinimalBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.UseLoad())
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
private static BotBase GetPhotoBot()
{
var bot = BotBaseBuilder
.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set"))
.MiddlewareMessageLoop(
messageLoop =>
messageLoop
.UseAttachments(MessageType.Photo))
.WithStartForm<StartForm>()
.NoProxy()
.DefaultCommands()
.NoSerialization()
.UsePersian()
.Build();
return bot;
}
}

View File

@ -146,6 +146,12 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
return this;
}
public IStartFormSelectionStage MiddlewareMessageLoop(Func<MiddlewareBaseMessageLoop, MiddlewareBaseMessageLoop> messageLoopConfiguration)
{
_messageLoopFactory = messageLoopConfiguration(new MiddlewareBaseMessageLoop());
return this;
}
public IStartFormSelectionStage MinimalMessageLoop()
{

View File

@ -1,4 +1,6 @@
using TelegramBotBase.Interfaces;
using System;
using TelegramBotBase.Interfaces;
using TelegramBotBase.MessageLoops;
namespace TelegramBotBase.Builder.Interfaces;
@ -12,6 +14,12 @@ public interface IMessageLoopSelectionStage
IStartFormSelectionStage DefaultMessageLoop();
/// <summary>
/// Choses a fully customizable middleware-based message loop.
/// </summary>
IStartFormSelectionStage MiddlewareMessageLoop(Func<MiddlewareBaseMessageLoop, MiddlewareBaseMessageLoop> messageLoopConfiguration);
/// <summary>
/// Chooses a minimalistic message loop, which catches all update types and only calls the Load function.
/// </summary>

View File

@ -1,7 +1,7 @@
namespace TelegramBotBase.Localizations
namespace TelegramBotBase.Localizations;
public sealed class Persian : Localization
{
public sealed class Persian : Localization
{
public Persian()
{
Values["Language"] = "فارسی";
@ -33,5 +33,4 @@
Values["ButtonGrid_SearchIcon"] = "🔍";
Values["ButtonGrid_TagIcon"] = "📁";
}
}
}

View File

@ -0,0 +1,310 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using static System.Collections.Specialized.BitVector32;
namespace TelegramBotBase.MessageLoops.Extensions;
public static class MiddlewareBaseMessageLoopExtensions
{
/// <summary>
/// Adds middleware to the message loop then returns the message loop
/// </summary>
public static MiddlewareBaseMessageLoop Use(this MiddlewareBaseMessageLoop messageLoop, Func<MessageContainer, Func<Task>, Task> middleware)
{
messageLoop.AddMiddleware(middleware);
return messageLoop;
}
/// <summary>
/// Adds update type validator middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseValidUpdateTypes(this MiddlewareBaseMessageLoop messageLoop, params UpdateType[] updateTypes)
{
messageLoop.Use(async (container, next) =>
{
var updateType = container.UpdateResult.RawData.Type;
if (updateTypes.Contains(updateType))
{
await next();
}
});
return messageLoop;
}
/// <summary>
/// Adds bot commands handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseBotCommands(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var botBase = container.BotBase;
var messageResult = container.MessageResult;
if (messageResult.IsFirstHandler &&
messageResult.IsBotCommand &&
botBase.IsKnownBotCommand(messageResult.BotCommand))
{
var deviceSession = container.DeviceSession;
var sce =
new BotCommandEventArgs(messageResult.BotCommand,
messageResult.BotCommandParameters,
messageResult.Message,
deviceSession.DeviceId,
deviceSession);
await botBase.OnBotCommand(sce);
if (sce.Handled)
{
return;
}
}
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseForms(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var activeForm = container.DeviceSession.ActiveForm;
var messageResult = container.MessageResult;
//Pre Loading Event
await activeForm.PreLoad(messageResult);
//Send Load event to controls
await activeForm.LoadControls(messageResult);
//Loading Event
await activeForm.Load(messageResult);
var updateType = container.UpdateResult.RawData.Type;
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (updateType == UpdateType.Message)
{
if ((messageResult.MessageType == MessageType.Contact)
| (messageResult.MessageType == MessageType.Document)
| (messageResult.MessageType == MessageType.Location)
| (messageResult.MessageType == MessageType.Photo)
| (messageResult.MessageType == MessageType.Video)
| (messageResult.MessageType == MessageType.Audio))
{
var updateResult = container.UpdateResult;
await activeForm.SentData(new DataResult(updateResult));
}
}
var deviceSession = container.DeviceSession;
//Action Event
if (!deviceSession.FormSwitched && messageResult.IsAction)
{
//Send Action event to controls
await activeForm.ActionControls(messageResult);
//Send Action event to form itself
await activeForm.Action(messageResult);
if (!messageResult.Handled)
{
messageResult.Handled = true;
return;
}
}
if (!deviceSession.FormSwitched)
{
//Render Event
await activeForm.RenderControls(messageResult);
await activeForm.Render(messageResult);
}
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms pre loads handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UsePreLoad(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var activeForm = container.DeviceSession.ActiveForm;
var messageResult = container.MessageResult;
await activeForm.PreLoad(messageResult);
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms loads handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseLoad(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var activeForm = container.DeviceSession.ActiveForm;
var messageResult = container.MessageResult;
await activeForm.LoadControls(messageResult);
await activeForm.Load(messageResult);
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms custom attachments handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseAttachments(this MiddlewareBaseMessageLoop messageLoop, params MessageType[] validAttachments)
{
messageLoop.Use(async (container, next) =>
{
var activeForm = container.DeviceSession.ActiveForm;
var messageResult = container.MessageResult;
var updateType = container.UpdateResult.RawData.Type;
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (updateType == UpdateType.Message)
{
if ((messageResult.MessageType == MessageType.Contact)
| (messageResult.MessageType == MessageType.Document)
| (messageResult.MessageType == MessageType.Location)
| (messageResult.MessageType == MessageType.Photo)
| (messageResult.MessageType == MessageType.Video)
| (messageResult.MessageType == MessageType.Audio))
{
if (validAttachments.Contains(messageResult.MessageType))
{
var updateResult = container.UpdateResult;
await activeForm.SentData(new DataResult(updateResult));
}
}
}
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms all attachments handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseAllAttachments(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var activeForm = container.DeviceSession.ActiveForm;
var messageResult = container.MessageResult;
var updateType = container.UpdateResult.RawData.Type;
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (updateType == UpdateType.Message)
{
if ((messageResult.MessageType == MessageType.Contact)
| (messageResult.MessageType == MessageType.Document)
| (messageResult.MessageType == MessageType.Location)
| (messageResult.MessageType == MessageType.Photo)
| (messageResult.MessageType == MessageType.Video)
| (messageResult.MessageType == MessageType.Audio))
{
var updateResult = container.UpdateResult;
await activeForm.SentData(new DataResult(updateResult));
}
}
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms actions handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseActions(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var messageResult = container.MessageResult;
var deviceSession = container.DeviceSession;
var activeForm = deviceSession.ActiveForm;
//Action Event
if (!deviceSession.FormSwitched && messageResult.IsAction)
{
//Send Action event to controls
await activeForm.ActionControls(messageResult);
//Send Action event to form itself
await activeForm.Action(messageResult);
if (!messageResult.Handled)
{
messageResult.Handled = true;
return;
}
}
await next();
});
return messageLoop;
}
/// <summary>
/// Adds forms renders handler middleware to the message loop then returns message loop
/// </summary>
public static MiddlewareBaseMessageLoop UseRender(this MiddlewareBaseMessageLoop messageLoop)
{
messageLoop.Use(async (container, next) =>
{
var messageResult = container.MessageResult;
var deviceSession = container.DeviceSession;
var activeForm = deviceSession.ActiveForm;
if (!deviceSession.FormSwitched)
{
await activeForm.RenderControls(messageResult);
await activeForm.Render(messageResult);
}
await next();
});
return messageLoop;
}
}

View File

@ -12,7 +12,7 @@ namespace TelegramBotBase.MessageLoops;
/// <summary>
/// Thats the default message loop which reacts to Message, EditMessage and CallbackQuery.
/// </summary>
public class FormBaseMessageLoop : IMessageLoopFactory
public sealed class FormBaseMessageLoop : IMessageLoopFactory
{
private static readonly object EvUnhandledCall = new();
@ -22,7 +22,6 @@ public class FormBaseMessageLoop : IMessageLoopFactory
{
var update = ur.RawData;
if (update.Type != UpdateType.Message
&& update.Type != UpdateType.EditedMessage
&& update.Type != UpdateType.CallbackQuery)

View File

@ -12,7 +12,7 @@ namespace TelegramBotBase.MessageLoops;
/// <summary>
/// This message loop reacts to all update types.
/// </summary>
public class FullMessageLoop : IMessageLoopFactory
public sealed class FullMessageLoop : IMessageLoopFactory
{
private static readonly object EvUnhandledCall = new();
@ -20,9 +20,6 @@ public class FullMessageLoop : IMessageLoopFactory
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr)
{
var update = ur.RawData;
//Is this a bot command ?
if (mr.IsFirstHandler && mr.IsBotCommand && bot.IsKnownBotCommand(mr.BotCommand))
{
@ -50,6 +47,7 @@ public class FullMessageLoop : IMessageLoopFactory
//Loading Event
await activeForm.Load(mr);
var update = ur.RawData;
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (update.Type == UpdateType.Message)

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.MessageLoops;
/// <summary>
/// This message loop based on middleware pattern
/// </summary>
public sealed class MiddlewareBaseMessageLoop : IMessageLoopFactory
{
private List<Func<MessageContainer, Func<Task>, Task>> Middlewares = new();
public event EventHandler<UnhandledCallEventArgs> UnhandledCall;
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr)
{
ur.Device = session;
mr.Device = session;
var messageContainer = new MessageContainer(bot, session, ur, mr);
if (Middlewares.Any())
{
int middlewareIndex = 0;
await InvokeMiddleware(messageContainer, middlewareIndex);
}
}
/// <summary>
/// Invokes middleware with index
/// </summary>
private async Task InvokeMiddleware(MessageContainer messageContainer, int middlewareIndex)
{
await Middlewares[middlewareIndex]
.Invoke(messageContainer, async () =>
{
int nextMiddlewareIndex = middlewareIndex + 1;
if (nextMiddlewareIndex < Middlewares.Count)
{
await InvokeMiddleware(messageContainer, nextMiddlewareIndex);
}
});
}
/// <summary>
/// Adds a new middleware
/// </summary>
public void AddMiddleware(Func<MessageContainer, Func<Task>, Task> middleware)
{
Middlewares.Add(middleware);
}
}
public struct MessageContainer
{
public BotBase BotBase { get; private set; }
public DeviceSession DeviceSession { get; private set; }
public UpdateResult UpdateResult { get; private set; }
public MessageResult MessageResult { get; private set; }
public MessageContainer(BotBase botBase, DeviceSession deviceSession, UpdateResult updateResult, MessageResult messageResult)
{
BotBase = botBase;
DeviceSession = deviceSession;
UpdateResult = updateResult;
MessageResult = messageResult;
}
}

View File

@ -11,7 +11,7 @@ namespace TelegramBotBase.MessageLoops;
/// <summary>
/// This is a minimal message loop which will react to all update types and just calling the Load method.
/// </summary>
public class MinimalMessageLoop : IMessageLoopFactory
public sealed class MinimalMessageLoop : IMessageLoopFactory
{
private static readonly object EvUnhandledCall = new();
@ -19,9 +19,6 @@ public class MinimalMessageLoop : IMessageLoopFactory
public async Task MessageLoop(BotBase bot, DeviceSession session, UpdateResult ur, MessageResult mr)
{
var update = ur.RawData;
mr.Device = session;
var activeForm = session.ActiveForm;

View File

@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Exa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiddlewareBaseBot", "Examples\MiddlewareBaseBot\MiddlewareBaseBot.csproj", "{8D053824-966C-4F82-B184-4840DB36D5F2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
EndProject
Global
@ -80,6 +82,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
{8D053824-966C-4F82-B184-4840DB36D5F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D053824-966C-4F82-B184-4840DB36D5F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D053824-966C-4F82-B184-4840DB36D5F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D053824-966C-4F82-B184-4840DB36D5F2}.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
@ -97,6 +103,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}
{8D053824-966C-4F82-B184-4840DB36D5F2} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution