From 691029aa3f249acd2727e83a22e7333eb58324ad Mon Sep 17 00:00:00 2001 From: Dmitri Shimanski Date: Wed, 19 Mar 2025 12:46:50 +0200 Subject: [PATCH] Create tests and fix attributes --- Examples/Telegram.Examples/Program.cs | 12 +- .../Telegram.Examples/UpdatePolling/Update.cs | 2 +- Telegram.Net.sln | 6 + Telegram.Net/Attributes/CallbackAttribute.cs | 26 ---- Telegram.Net/Attributes/CommandAttribute.cs | 26 ---- .../Attributes/EditMessageAttribute.cs | 24 ---- Telegram.Net/Attributes/InlineAttribute.cs | 25 ---- .../Attributes/PreCheckoutAttribute.cs | 24 ---- Telegram.Net/Attributes/UpdateAttribute.cs | 24 ---- ...ingSerivce.cs => IUpdatePollingService.cs} | 2 +- Telegram.Net/ServiceBindings.cs | 2 +- .../Services/TelegramHostedService.cs | 128 +++++++++++++++++- Telegram.Net/Telegram.Net.csproj | 8 ++ global.json | 7 + tests/Telegram.Tests/GlobalUsings.cs | 1 + tests/Telegram.Tests/Telegram.Tests.csproj | 20 +++ tests/Telegram.Tests/UnitTest1.cs | 15 ++ 17 files changed, 194 insertions(+), 158 deletions(-) rename Telegram.Net/Interfaces/{IUpdatePollingSerivce.cs => IUpdatePollingService.cs} (80%) create mode 100644 global.json create mode 100644 tests/Telegram.Tests/GlobalUsings.cs create mode 100644 tests/Telegram.Tests/Telegram.Tests.csproj create mode 100644 tests/Telegram.Tests/UnitTest1.cs diff --git a/Examples/Telegram.Examples/Program.cs b/Examples/Telegram.Examples/Program.cs index ae1d2ca..1f2436a 100644 --- a/Examples/Telegram.Examples/Program.cs +++ b/Examples/Telegram.Examples/Program.cs @@ -1,14 +1,18 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Telegram.Net; var webHost = Host.CreateDefaultBuilder() + .ConfigureLogging(l => l.ClearProviders().AddConsole()) .ConfigureServices(k => { - k.ConnectTelegram(new("YOUR-TOKEN") + k.ConnectTelegram(new("TOKEN") { errorHandler = async (client, exception, ctx) => { - await Console.Out.WriteLineAsync(exception.Message); + Console.WriteLine(exception); } }); - }); \ No newline at end of file + }); +webHost.Build().Run(); \ No newline at end of file diff --git a/Examples/Telegram.Examples/UpdatePolling/Update.cs b/Examples/Telegram.Examples/UpdatePolling/Update.cs index 622baf1..f6c703c 100644 --- a/Examples/Telegram.Examples/UpdatePolling/Update.cs +++ b/Examples/Telegram.Examples/UpdatePolling/Update.cs @@ -5,7 +5,7 @@ using Telegram.Net.Interfaces; namespace Telegram.Examples.UpdatePolling; -public class Update : IUpdatePollingSerivce +public class Update : IUpdatePollingService { [Update] public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx) diff --git a/Telegram.Net.sln b/Telegram.Net.sln index 362a68f..9e3dd27 100644 --- a/Telegram.Net.sln +++ b/Telegram.Net.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Net", "Telegram.Ne EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Examples", "Examples\Telegram.Examples\Telegram.Examples.csproj", "{073E66F2-F82E-4378-B390-C2364DFDC491}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Tests", "tests\Telegram.Tests\Telegram.Tests.csproj", "{1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {073E66F2-F82E-4378-B390-C2364DFDC491}.Debug|Any CPU.Build.0 = Debug|Any CPU {073E66F2-F82E-4378-B390-C2364DFDC491}.Release|Any CPU.ActiveCfg = Release|Any CPU {073E66F2-F82E-4378-B390-C2364DFDC491}.Release|Any CPU.Build.0 = Release|Any CPU + {1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Telegram.Net/Attributes/CallbackAttribute.cs b/Telegram.Net/Attributes/CallbackAttribute.cs index bc4f28e..278e61b 100644 --- a/Telegram.Net/Attributes/CallbackAttribute.cs +++ b/Telegram.Net/Attributes/CallbackAttribute.cs @@ -29,31 +29,5 @@ public class CallbackAttribute : Attribute public CallbackAttribute(string queryId) { this.QueryId = queryId; - - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var attr = method.GetCustomAttribute(); - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.CallbackQueryHandler.Add(attr!.QueryId, handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(CallbackQuery) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Attributes/CommandAttribute.cs b/Telegram.Net/Attributes/CommandAttribute.cs index 89c7072..5b70dab 100644 --- a/Telegram.Net/Attributes/CommandAttribute.cs +++ b/Telegram.Net/Attributes/CommandAttribute.cs @@ -29,31 +29,5 @@ public class CommandAttribute : Attribute public CommandAttribute(string command) { Command = command; - - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var attr = method.GetCustomAttribute(); - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.CommandHandler.Add(attr!.Command, handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(Message) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Attributes/EditMessageAttribute.cs b/Telegram.Net/Attributes/EditMessageAttribute.cs index 0190c3c..f29724e 100644 --- a/Telegram.Net/Attributes/EditMessageAttribute.cs +++ b/Telegram.Net/Attributes/EditMessageAttribute.cs @@ -26,29 +26,5 @@ public class EditMessageAttribute : Attribute /// public EditMessageAttribute() { - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.EditedMessageHandler.Add(handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(Message) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Attributes/InlineAttribute.cs b/Telegram.Net/Attributes/InlineAttribute.cs index bfaa31d..218e7d0 100644 --- a/Telegram.Net/Attributes/InlineAttribute.cs +++ b/Telegram.Net/Attributes/InlineAttribute.cs @@ -31,30 +31,5 @@ public class InlineAttribute : Attribute public InlineAttribute(string inlineId) { this.InlineId = inlineId; - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var attr = method.GetCustomAttribute(); - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.InlineHandler.Add(attr!.InlineId, handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(InlineQuery) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Attributes/PreCheckoutAttribute.cs b/Telegram.Net/Attributes/PreCheckoutAttribute.cs index 924609c..e90ada1 100644 --- a/Telegram.Net/Attributes/PreCheckoutAttribute.cs +++ b/Telegram.Net/Attributes/PreCheckoutAttribute.cs @@ -22,29 +22,5 @@ public class PreCheckoutAttribute : Attribute public bool IsReusable => true; public PreCheckoutAttribute() { - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.PreCheckoutHandler = (handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(PreCheckoutQuery) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Attributes/UpdateAttribute.cs b/Telegram.Net/Attributes/UpdateAttribute.cs index 597d53d..f287a51 100644 --- a/Telegram.Net/Attributes/UpdateAttribute.cs +++ b/Telegram.Net/Attributes/UpdateAttribute.cs @@ -21,29 +21,5 @@ public class UpdateAttribute : Attribute public bool IsReusable => true; public UpdateAttribute() { - var methods = typeof(IUpdatePollingSerivce).GetMethods() - .Where(m => m.GetCustomAttribute(this.GetType()) != null); - - foreach (var method in methods) - { - if (IsValidHandlerMethod(method)) - { - var handler = (Func) - Delegate.CreateDelegate(typeof(Func), null, - method); - - TelegramHostedService.DefaultUpdateHandler.Add(handler); - } - } - } - - static bool IsValidHandlerMethod(MethodInfo method) - { - var parameters = method.GetParameters(); - return method.ReturnType == typeof(Task) && - parameters.Length == 3 && - parameters[0].ParameterType == typeof(ITelegramBotClient) && - parameters[1].ParameterType == typeof(Update) && - parameters[2].ParameterType == typeof(CancellationToken); } } \ No newline at end of file diff --git a/Telegram.Net/Interfaces/IUpdatePollingSerivce.cs b/Telegram.Net/Interfaces/IUpdatePollingService.cs similarity index 80% rename from Telegram.Net/Interfaces/IUpdatePollingSerivce.cs rename to Telegram.Net/Interfaces/IUpdatePollingService.cs index 929a654..92f9b21 100644 --- a/Telegram.Net/Interfaces/IUpdatePollingSerivce.cs +++ b/Telegram.Net/Interfaces/IUpdatePollingService.cs @@ -3,7 +3,7 @@ /// /// You should implement this interface in all your classes with update polling logic /// -public interface IUpdatePollingSerivce +public interface IUpdatePollingService { } \ No newline at end of file diff --git a/Telegram.Net/ServiceBindings.cs b/Telegram.Net/ServiceBindings.cs index 684733b..9a1d0a1 100644 --- a/Telegram.Net/ServiceBindings.cs +++ b/Telegram.Net/ServiceBindings.cs @@ -8,7 +8,7 @@ public static class ServiceBindings { public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config) { - isc.AddHostedService(k => new(config)); + isc.AddHostedService(k => new(config, isc)); return isc; } } \ No newline at end of file diff --git a/Telegram.Net/Services/TelegramHostedService.cs b/Telegram.Net/Services/TelegramHostedService.cs index d1afc91..dfc24f4 100644 --- a/Telegram.Net/Services/TelegramHostedService.cs +++ b/Telegram.Net/Services/TelegramHostedService.cs @@ -1,8 +1,11 @@ using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Payments; +using Telegram.Net.Attributes; using Telegram.Net.Interfaces; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously @@ -10,6 +13,7 @@ namespace Telegram.Net.Services; public class TelegramHostedService : IHostedService { + private IServiceCollection isc { get; } private TelegramBotClient Client { get; } private ITelegramBotConfig Config { get; } internal static Dictionary> CommandHandler { get; } = new(); @@ -19,16 +23,136 @@ public class TelegramHostedService : IHostedService internal static Func? PreCheckoutHandler { get; set; } internal static List> DefaultUpdateHandler { get; } = new(); - public TelegramHostedService(ITelegramBotConfig config) + public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc) { Client = new TelegramBotClient(config.Token); Config = config; - + this.isc = isc; } + private static bool IsValidHandlerMethod(MethodInfo method, Type parameterType) + { + var parameters = method.GetParameters(); + return method.ReturnType == typeof(Task) && + parameters.Length == 3 && + parameters[0].ParameterType == typeof(ITelegramBotClient) && + parameters[1].ParameterType == parameterType && + parameters[2].ParameterType == typeof(CancellationToken); + } + + private static Func CreateDelegate(MethodInfo method) + { + var delegateType = typeof(Func); + return (Delegate.CreateDelegate(delegateType, null, method) as Func)!; + } + + internal async Task AddAttributes(CancellationToken cancellationToken) + { + var mutex = new Mutex(); + await Task.Run(async () => + { + mutex.WaitOne(); + var implementations = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface); + + foreach (var implementation in implementations) + { + isc.AddSingleton(implementation); + } + + var methods = implementations + .SelectMany(t => t.GetMethods( + BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.DeclaredOnly)) + .Where(m => + m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null); + + foreach (var method in methods) + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + var commandAttr = method.GetCustomAttribute(); + if (commandAttr != null) + { + if (IsValidHandlerMethod(method, typeof(Message))) + { + var handler = CreateDelegate(method); + CommandHandler.Add(commandAttr.Command, handler); + } + continue; + } + + var callbackAttr = method.GetCustomAttribute(); + if (callbackAttr != null) + { + if (IsValidHandlerMethod(method, typeof(CallbackQuery))) + { + var handler = CreateDelegate(method); + CallbackQueryHandler.Add(callbackAttr.QueryId, handler); + } + continue; + } + + var editMessageAttr = method.GetCustomAttribute(); + if (editMessageAttr != null) + { + if (IsValidHandlerMethod(method, typeof(Message))) + { + var handler = CreateDelegate(method); + EditedMessageHandler.Add(handler); + } + continue; + } + + var inlineAttr = method.GetCustomAttribute(); + if (inlineAttr != null) + { + if (IsValidHandlerMethod(method, typeof(InlineQuery))) + { + var handler = CreateDelegate(method); + InlineHandler.Add(inlineAttr.InlineId, handler); + } + continue; + } + + var preCheckoutAttr = method.GetCustomAttribute(); + if (preCheckoutAttr != null) + { + if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery))) + { + var handler = CreateDelegate(method); + PreCheckoutHandler = handler; + } + continue; + } + + var updateAttr = method.GetCustomAttribute(); + if (updateAttr != null) + { + if (IsValidHandlerMethod(method, typeof(Update))) + { + var handler = CreateDelegate(method); + DefaultUpdateHandler.Add(handler); + } + continue; + } + } + mutex.ReleaseMutex(); + }, cancellationToken); + } + [SuppressMessage("ReSharper", "AsyncVoidLambda")] public async Task StartAsync(CancellationToken cancellationToken) { + await AddAttributes(cancellationToken); + + Client.StartReceiving( async (client, update, ctx) => { diff --git a/Telegram.Net/Telegram.Net.csproj b/Telegram.Net/Telegram.Net.csproj index dd6a97e..eae3820 100644 --- a/Telegram.Net/Telegram.Net.csproj +++ b/Telegram.Net/Telegram.Net.csproj @@ -4,6 +4,14 @@ net7.0 enable enable + 1.0.0 + yawaflua + Telegram.Net + Telegram.Bots extender pack, what provides to user attributes, which can used with services + Dmitrii Shimanskii + https://github.com/yawaflua/telegram.Net + https://github.com/yawaflua/telegram.Net + telegram diff --git a/global.json b/global.json new file mode 100644 index 0000000..aaac9e0 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/tests/Telegram.Tests/GlobalUsings.cs b/tests/Telegram.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/tests/Telegram.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/tests/Telegram.Tests/Telegram.Tests.csproj b/tests/Telegram.Tests/Telegram.Tests.csproj new file mode 100644 index 0000000..eadb99b --- /dev/null +++ b/tests/Telegram.Tests/Telegram.Tests.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + diff --git a/tests/Telegram.Tests/UnitTest1.cs b/tests/Telegram.Tests/UnitTest1.cs new file mode 100644 index 0000000..771c354 --- /dev/null +++ b/tests/Telegram.Tests/UnitTest1.cs @@ -0,0 +1,15 @@ +namespace Telegram.Tests; + +public class Tests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file