Create tests and fix attributes

This commit is contained in:
Dmitri Shimanski
2025-03-19 12:46:50 +02:00
parent 5bfad37f53
commit 691029aa3f
17 changed files with 194 additions and 158 deletions

View File

@@ -1,14 +1,18 @@
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Net; using Telegram.Net;
var webHost = Host.CreateDefaultBuilder() var webHost = Host.CreateDefaultBuilder()
.ConfigureLogging(l => l.ClearProviders().AddConsole())
.ConfigureServices(k => .ConfigureServices(k =>
{ {
k.ConnectTelegram(new("YOUR-TOKEN") k.ConnectTelegram(new("TOKEN")
{ {
errorHandler = async (client, exception, ctx) => errorHandler = async (client, exception, ctx) =>
{ {
await Console.Out.WriteLineAsync(exception.Message); Console.WriteLine(exception);
} }
}); });
}); });
webHost.Build().Run();

View File

@@ -5,7 +5,7 @@ using Telegram.Net.Interfaces;
namespace Telegram.Examples.UpdatePolling; namespace Telegram.Examples.UpdatePolling;
public class Update : IUpdatePollingSerivce public class Update : IUpdatePollingService
{ {
[Update] [Update]
public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx) public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx)

View File

@@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Net", "Telegram.Ne
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Examples", "Examples\Telegram.Examples\Telegram.Examples.csproj", "{073E66F2-F82E-4378-B390-C2364DFDC491}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Examples", "Examples\Telegram.Examples\Telegram.Examples.csproj", "{073E66F2-F82E-4378-B390-C2364DFDC491}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Tests", "tests\Telegram.Tests\Telegram.Tests.csproj", "{1FF230D6-7DDC-48B6-8D56-2E14726FDD9E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{073E66F2-F82E-4378-B390-C2364DFDC491}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

View File

@@ -29,31 +29,5 @@ public class CallbackAttribute : Attribute
public CallbackAttribute(string queryId) public CallbackAttribute(string queryId)
{ {
this.QueryId = 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<CallbackAttribute>();
var handler = (Func<ITelegramBotClient, CallbackQuery, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, CallbackQuery, CancellationToken, Task>), 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);
} }
} }

View File

@@ -29,31 +29,5 @@ public class CommandAttribute : Attribute
public CommandAttribute(string command) public CommandAttribute(string command)
{ {
Command = 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<CommandAttribute>();
var handler = (Func<ITelegramBotClient, Message, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, Message, CancellationToken, Task>), 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);
} }
} }

View File

@@ -26,29 +26,5 @@ public class EditMessageAttribute : Attribute
/// </summary> /// </summary>
public EditMessageAttribute() 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<ITelegramBotClient, Message, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, Message, CancellationToken, Task>), 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);
} }
} }

View File

@@ -31,30 +31,5 @@ public class InlineAttribute : Attribute
public InlineAttribute(string inlineId) public InlineAttribute(string inlineId)
{ {
this.InlineId = 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<InlineAttribute>();
var handler = (Func<ITelegramBotClient, InlineQuery, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, InlineQuery, CancellationToken, Task>), 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);
} }
} }

View File

@@ -22,29 +22,5 @@ public class PreCheckoutAttribute : Attribute
public bool IsReusable => true; public bool IsReusable => true;
public PreCheckoutAttribute() 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<ITelegramBotClient, PreCheckoutQuery, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, PreCheckoutQuery, CancellationToken, Task>), 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);
} }
} }

View File

@@ -21,29 +21,5 @@ public class UpdateAttribute : Attribute
public bool IsReusable => true; public bool IsReusable => true;
public UpdateAttribute() 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<ITelegramBotClient, Update, CancellationToken, Task>)
Delegate.CreateDelegate(typeof(Func<ITelegramBotClient, Update, CancellationToken, Task>), 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);
} }
} }

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// You should implement this interface in all your classes with update polling logic /// You should implement this interface in all your classes with update polling logic
/// </summary> /// </summary>
public interface IUpdatePollingSerivce public interface IUpdatePollingService
{ {
} }

View File

@@ -8,7 +8,7 @@ public static class ServiceBindings
{ {
public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config) public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config)
{ {
isc.AddHostedService<TelegramHostedService>(k => new(config)); isc.AddHostedService<TelegramHostedService>(k => new(config, isc));
return isc; return isc;
} }
} }

View File

@@ -1,8 +1,11 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments; using Telegram.Bot.Types.Payments;
using Telegram.Net.Attributes;
using Telegram.Net.Interfaces; using Telegram.Net.Interfaces;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #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 public class TelegramHostedService : IHostedService
{ {
private IServiceCollection isc { get; }
private TelegramBotClient Client { get; } private TelegramBotClient Client { get; }
private ITelegramBotConfig Config { get; } private ITelegramBotConfig Config { get; }
internal static Dictionary<string, Func<ITelegramBotClient, Message, CancellationToken, Task>> CommandHandler { get; } = new(); internal static Dictionary<string, Func<ITelegramBotClient, Message, CancellationToken, Task>> CommandHandler { get; } = new();
@@ -19,16 +23,136 @@ public class TelegramHostedService : IHostedService
internal static Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; } internal static Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal static List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new(); internal static List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new();
public TelegramHostedService(ITelegramBotConfig config) public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc)
{ {
Client = new TelegramBotClient(config.Token); Client = new TelegramBotClient(config.Token);
Config = config; 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<ITelegramBotClient, T, CancellationToken, Task> CreateDelegate<T>(MethodInfo method)
{
var delegateType = typeof(Func<ITelegramBotClient, T, CancellationToken, Task>);
return (Delegate.CreateDelegate(delegateType, null, method) as Func<ITelegramBotClient, T, CancellationToken, Task>)!;
}
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<CommandAttribute>() != null ||
m.GetCustomAttribute<CallbackAttribute>() != null ||
m.GetCustomAttribute<EditMessageAttribute>() != null ||
m.GetCustomAttribute<InlineAttribute>() != null ||
m.GetCustomAttribute<PreCheckoutAttribute>() != null ||
m.GetCustomAttribute<UpdateAttribute>() != null);
foreach (var method in methods)
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
CommandHandler.Add(commandAttr.Command, handler);
}
continue;
}
var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
{
var handler = CreateDelegate<CallbackQuery>(method);
CallbackQueryHandler.Add(callbackAttr.QueryId, handler);
}
continue;
}
var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (editMessageAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
EditedMessageHandler.Add(handler);
}
continue;
}
var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
if (inlineAttr != null)
{
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
{
var handler = CreateDelegate<InlineQuery>(method);
InlineHandler.Add(inlineAttr.InlineId, handler);
}
continue;
}
var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>();
if (preCheckoutAttr != null)
{
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
{
var handler = CreateDelegate<PreCheckoutQuery>(method);
PreCheckoutHandler = handler;
}
continue;
}
var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Update)))
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
}
continue;
}
}
mutex.ReleaseMutex();
}, cancellationToken);
}
[SuppressMessage("ReSharper", "AsyncVoidLambda")] [SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
await AddAttributes(cancellationToken);
Client.StartReceiving( Client.StartReceiving(
async (client, update, ctx) => async (client, update, ctx) =>
{ {

View File

@@ -4,6 +4,14 @@
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>1.0.0</Version>
<Authors>yawaflua</Authors>
<Title>Telegram.Net</Title>
<Description>Telegram.Bots extender pack, what provides to user attributes, which can used with services</Description>
<Copyright>Dmitrii Shimanskii</Copyright>
<PackageProjectUrl>https://github.com/yawaflua/telegram.Net</PackageProjectUrl>
<RepositoryUrl>https://github.com/yawaflua/telegram.Net</RepositoryUrl>
<PackageTags>telegram</PackageTags>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}

View File

@@ -0,0 +1 @@
global using NUnit.Framework;

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
namespace Telegram.Tests;
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}