diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..8e4c1ab
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,25 @@
+name: .NET Tests
+
+on:
+ push:
+ branches: [ "*" ]
+ pull_request:
+ branches: [ "*" ]
+env:
+ "telegram_test_token": ${{secrets.TELEGRAM_TEST_TOKEN}}
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 7.0.x
+ - name: Restore dependencies
+ run: dotnet restore
+ - name: Build
+ run: dotnet build --no-restore /p:TreatWarningsAsErrors=false
+ - name: Test
+ run: dotnet test --no-build --verbosity normal -e telegram_test_token=${{secrets.TELEGRAM_TEST_TOKEN}}
\ No newline at end of file
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/Telegram.Examples.csproj b/Examples/Telegram.Examples/Telegram.Examples.csproj
index 1622ed8..2891280 100644
--- a/Examples/Telegram.Examples/Telegram.Examples.csproj
+++ b/Examples/Telegram.Examples/Telegram.Examples.csproj
@@ -5,6 +5,7 @@
net7.0
enable
enable
+ true
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/AssemblyInfo.cs b/Telegram.Net/AssemblyInfo.cs
new file mode 100644
index 0000000..bc47f6e
--- /dev/null
+++ b/Telegram.Net/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Telegram.Tests")]
\ No newline at end of file
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/ITelegramBotConfig.cs b/Telegram.Net/Interfaces/ITelegramBotConfig.cs
index d928e70..cdb7865 100644
--- a/Telegram.Net/Interfaces/ITelegramBotConfig.cs
+++ b/Telegram.Net/Interfaces/ITelegramBotConfig.cs
@@ -8,7 +8,7 @@ public interface ITelegramBotConfig
///
/// Token of telegram bot. You can take it from @BotFather
///
- public string Token { internal get; init; }
+ public string Token { get; set; }
///
/// Custom error handler for bot. You can add custom logger or anything.
///
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/Models/TelegramBotConfig.cs b/Telegram.Net/Models/TelegramBotConfig.cs
index 84fd5b7..0fbb672 100644
--- a/Telegram.Net/Models/TelegramBotConfig.cs
+++ b/Telegram.Net/Models/TelegramBotConfig.cs
@@ -11,7 +11,7 @@ public class TelegramBotConfig : ITelegramBotConfig
Token = token;
}
- public string Token { get; init; }
+ public string Token { get; set; }
public Func? errorHandler { get; init; }
public ReceiverOptions? ReceiverOptions { get; init; }
}
\ 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..3f05c4f 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,52 +13,171 @@ namespace Telegram.Net.Services;
public class TelegramHostedService : IHostedService
{
- private TelegramBotClient Client { get; }
+ private IServiceCollection isc { get; }
+ internal TelegramBotClient Client { get; set; }
private ITelegramBotConfig Config { get; }
- internal static Dictionary> CommandHandler { get; } = new();
- internal static List> EditedMessageHandler { get; } = new();
- internal static Dictionary> CallbackQueryHandler { get; } = new();
- internal static Dictionary> InlineHandler { get; } = new();
- internal static Func? PreCheckoutHandler { get; set; }
- internal static List> DefaultUpdateHandler { get; } = new();
+ internal Dictionary> CommandHandler { get; } = new();
+ internal List> EditedMessageHandler { get; } = new();
+ internal Dictionary> CallbackQueryHandler { get; } = new();
+ internal Dictionary> InlineHandler { get; } = new();
+ internal Func? PreCheckoutHandler { get; set; }
+ internal List> DefaultUpdateHandler { get; } = new();
- public TelegramHostedService(ITelegramBotConfig config)
+ public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc)
{
Client = new TelegramBotClient(config.Token);
Config = config;
+ this.isc = isc;
+ }
+ internal 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);
+ }
+ internal static Func CreateDelegate(MethodInfo method)
+ {
+ var delegateType = typeof(Func);
+ return (Delegate.CreateDelegate(delegateType, null, method) as Func)!;
+ }
+
+ internal async Task AddAttributes(CancellationToken cancellationToken)
+ {
+ await Task.Run(async () =>
+ {
+ 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.TryAdd(commandAttr.Command, handler);
+ }
+ continue;
+ }
+
+ var callbackAttr = method.GetCustomAttribute();
+ if (callbackAttr != null)
+ {
+ if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
+ {
+ var handler = CreateDelegate(method);
+ CallbackQueryHandler.TryAdd(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.TryAdd(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;
+ }
+ }
+ }, cancellationToken);
+ }
+
+
+ internal async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx)
+ {
+ switch (update)
+ {
+ case { Message: { } message }:
+ await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key)).Value(client, message, ctx);
+ break;
+ case { EditedMessage: { } message }:
+ EditedMessageHandler.ForEach(async k => await k(client, message, ctx));
+ break;
+ case { CallbackQuery: { } callbackQuery }:
+ await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key)).Value(client, callbackQuery, ctx);
+ break;
+ case { InlineQuery: { } inlineQuery }:
+ await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key)).Value(client, inlineQuery, ctx);
+ break;
+ case { PreCheckoutQuery: { } preCheckoutQuery }:
+ if (PreCheckoutHandler != null) await PreCheckoutHandler(client, preCheckoutQuery, ctx);
+ break;
+ default:
+ DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx));
+ break;
+ }
}
[SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken cancellationToken)
{
+ await AddAttributes(cancellationToken);
+
+
+
Client.StartReceiving(
- async (client, update, ctx) =>
- {
- switch (update)
- {
- case { Message: { } message }:
- await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key)).Value(client, message, cancellationToken);
- break;
- case { EditedMessage: { } message }:
- EditedMessageHandler.ForEach(async k => await k(client, message, cancellationToken));
- break;
- case { CallbackQuery: { } callbackQuery }:
- await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key)).Value(client, callbackQuery, cancellationToken);
- break;
- case { InlineQuery: { } inlineQuery }:
- await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key)).Value(client, inlineQuery, cancellationToken);
- break;
- case {PreCheckoutQuery: { } preCheckoutQuery}:
- if (PreCheckoutHandler != null)
- await PreCheckoutHandler(client, preCheckoutQuery, cancellationToken);
- break;
- default:
- DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx));
- break;
- }
-
- },
+ UpdateHandler,
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions,
cancellationToken);
diff --git a/Telegram.Net/Telegram.Net.csproj b/Telegram.Net/Telegram.Net.csproj
index dd6a97e..f612b35 100644
--- a/Telegram.Net/Telegram.Net.csproj
+++ b/Telegram.Net/Telegram.Net.csproj
@@ -1,9 +1,18 @@
+ true
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..0b9b510
--- /dev/null
+++ b/tests/Telegram.Tests/Telegram.Tests.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net7.0
+ enable
+ enable
+ true
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
diff --git a/tests/Telegram.Tests/TestTelegramHostedService.cs b/tests/Telegram.Tests/TestTelegramHostedService.cs
new file mode 100644
index 0000000..656485f
--- /dev/null
+++ b/tests/Telegram.Tests/TestTelegramHostedService.cs
@@ -0,0 +1,175 @@
+using Telegram.Bot;
+using Telegram.Bot.Polling;
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.InlineQueryResults;
+using Telegram.Bot.Types.Payments;
+using Telegram.Net.Attributes;
+using Telegram.Net.Interfaces;
+using Telegram.Net.Services;
+
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Telegram.Bot.Types.Enums;
+using Telegram.Bot.Types.ReplyMarkups;
+using Telegram.Net.Models;
+
+namespace Telegram.Tests
+{
+ [TestFixture]
+ public class TelegramHostedServiceTests
+ {
+ private TelegramBotConfig _configMock;
+ private ServiceCollection _services;
+
+ [SetUp]
+ public void Setup()
+ {
+ var conf = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("appsettings.json", false)
+ .AddEnvironmentVariables()
+ .Build();
+
+ _configMock = new TelegramBotConfig(conf.GetValue("telegram_test_token") ?? throw new Exception("Provide telegram token first"));
+ _services = new ServiceCollection();
+ }
+
+ public class TestHandler : IUpdatePollingService
+ {
+ [Command("/start")]
+ public async Task HandleStart(ITelegramBotClient client, Message message, CancellationToken ct)
+ => await client.SendTextMessageAsync(message.Chat.Id, "Started", cancellationToken: ct);
+
+ [Callback("test_callback")]
+ public async Task HandleCallback(ITelegramBotClient client, CallbackQuery query, CancellationToken ct)
+ => await client.AnswerCallbackQueryAsync(query.Id, "Callback handled", cancellationToken: ct);
+
+ [EditMessage]
+ public async Task HandleEdit(ITelegramBotClient client, Message message, CancellationToken ct)
+ => await client.EditMessageTextAsync(message.Chat.Id, message.MessageId, "Edited", cancellationToken: ct);
+
+ [Inline("search")]
+ public async Task HandleInline(ITelegramBotClient client, InlineQuery query, CancellationToken ct)
+ => await client.AnswerInlineQueryAsync(query.Id, new[] { new InlineQueryResultArticle("1", "Test", new InputTextMessageContent("Test")) }, cancellationToken: ct);
+
+ [PreCheckout]
+ public async Task HandlePayment(ITelegramBotClient client, PreCheckoutQuery query, CancellationToken ct)
+ => await client.AnswerPreCheckoutQueryAsync(query.Id, cancellationToken: ct);
+
+ [Update]
+ public async Task HandleUpdate(ITelegramBotClient client, Update update, CancellationToken ct)
+ => await client.SendTextMessageAsync(123, "Update handled", cancellationToken: ct);
+ }
+
+ [Test]
+ public void AddAttributes_RegistersCommandHandlersCorrectly()
+ {
+ _services.AddSingleton();
+ var service = new TelegramHostedService(_configMock, _services);
+
+ service.AddAttributes(CancellationToken.None).Wait();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(service.CommandHandler, Contains.Key("/start"));
+ Assert.That(service.CallbackQueryHandler, Contains.Key("test_callback"));
+ Assert.That(service.EditedMessageHandler, Has.Count.EqualTo(1));
+ Assert.That(service.InlineHandler, Contains.Key("search"));
+ Assert.That(service.PreCheckoutHandler, Is.Not.Null);
+ Assert.That(service.DefaultUpdateHandler, Has.Count.EqualTo(1));
+ });
+ }
+
+ [Test]
+ public void IsValidHandlerMethod_ValidMessageHandler_ReturnsTrue()
+ {
+ // Arrange
+ var method = typeof(TestHandler).GetMethod("HandleStart");
+
+ // Act
+ var result = TelegramHostedService.IsValidHandlerMethod(method, typeof(Message));
+
+ // Assert
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void IsValidHandlerMethod_InvalidParameters_ReturnsFalse()
+ {
+ // Arrange
+ var method = typeof(TestHandler).GetMethod("HandleStart");
+
+ // Act
+ var result = TelegramHostedService.IsValidHandlerMethod(method, typeof(CallbackQuery));
+
+ // Assert
+ Assert.That(result, Is.False);
+ }
+ }
+
+ [TestFixture]
+ public class IntegrationTests
+ {
+ private Mock _botClientMock;
+ private TelegramHostedService _hostedService;
+ private Mock _configMock;
+
+ [SetUp]
+ public void Setup()
+ {
+ var conf = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("appsettings.json", false)
+ .AddEnvironmentVariables()
+ .Build();
+
+ _configMock = new Mock();
+ _configMock.Setup(c => c.Token).Returns(conf.GetValue("telegram_test_token") ?? throw new Exception("Provide token first"));
+ _configMock.Setup(c => c.ReceiverOptions).Returns(new ReceiverOptions());
+
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ _botClientMock = new Mock();
+
+ _hostedService = new TelegramHostedService(_configMock.Object, services);
+ _hostedService.AddAttributes(CancellationToken.None).Wait();
+ }
+
+ [Test]
+ public async Task HandleMessage_ValidCommand_ExecutesHandler()
+ {
+ // Arrange
+ var message = new Message { Text = "/start", Chat = new Chat { Id = 123 } };
+ var update = new Update { Message = message };
+
+ // Act
+ await _hostedService.StartAsync(CancellationToken.None);
+ await InvokeUpdateHandler(update);
+
+ // Assert
+ _botClientMock.Verify();
+ }
+
+ [Test]
+ public async Task HandleCallback_ValidQuery_ExecutesHandler()
+ {
+ // Arrange
+ var callback = new CallbackQuery { Data = "test_callback", Id = "cb_id" };
+ var update = new Update { CallbackQuery = callback };
+
+ // Act
+ await _hostedService.StartAsync(CancellationToken.None);
+ await InvokeUpdateHandler(update);
+
+ // Assert
+ _botClientMock.Verify();
+ }
+
+ private async Task InvokeUpdateHandler(Update update)
+ {
+ await _hostedService.UpdateHandler(_botClientMock.Object, update, CancellationToken.None);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Telegram.Tests/appsettings.json b/tests/Telegram.Tests/appsettings.json
new file mode 100644
index 0000000..50a43b4
--- /dev/null
+++ b/tests/Telegram.Tests/appsettings.json
@@ -0,0 +1,3 @@
+{
+ "_telegram_test_token": "PROVIDE_YOUR_TOKEN"
+}