Create project

This commit is contained in:
Dmitri Shimanski
2025-03-19 11:12:13 +02:00
commit 1a8a999d5b
18 changed files with 659 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

View File

@@ -0,0 +1,14 @@
using Microsoft.Extensions.Hosting;
using Telegram.Net;
var webHost = Host.CreateDefaultBuilder()
.ConfigureServices(k =>
{
k.ConnectTelegram(new("YOUR-TOKEN")
{
errorHandler = async (client, exception, ctx) =>
{
await Console.Out.WriteLineAsync(exception.Message);
}
});
});

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<ProjectReference Include="..\..\Telegram.Net\Telegram.Net.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,40 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Attributes;
using Telegram.Net.Interfaces;
namespace Telegram.Examples.UpdatePolling;
public class Update : IUpdatePollingSerivce
{
[Update]
public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx)
{
if (update.Poll != null)
{
Console.WriteLine(update.Poll.IsClosed);
}
}
[Callback("act-")]
public async Task CallbackExample(ITelegramBotClient client, CallbackQuery query, CancellationToken ctx)
{
Console.WriteLine(query.Message!.Text);
}
[Command("/start")]
public async Task StartCommand(ITelegramBotClient client, Message message, CancellationToken ctx)
{
if (message.Text!.Contains(" ") && message.Text.Split(" ")[1] == "test")
await client.SendMessage(message.From!.Id, "Hello, I`m example bot. And this - command with subparam", cancellationToken: ctx);
else
await client.SendMessage(message.From!.Id, "Hello, I`m example bot.", cancellationToken: ctx);
}
[EditMessage]
public async Task EditMessageExmaple(ITelegramBotClient client, Message message, CancellationToken ctx)
{
Console.WriteLine($"new message text: {message.Text}");
}
}

90
README.md Normal file
View File

@@ -0,0 +1,90 @@
# Telegram Bot Attribute Handlers
This project provides a set of C# attributes to facilitate the handling of different types of Telegram bot updates using reflection.
## Features
- **Inline Query Handling** (`InlineAttribute`)
- **Edited Message Handling** (`EditMessageAttribute`)
- **Command Handling** (`CommandHandlerAttribute`)
- **Callback Query Handling** (`CallbackAttribute`)
- **PreCheckout Query Handling** (`PreCheckoutAttribute`)
- **General Update Handling** (`UpdateAttribute`)
- **Auto-generate telegram client**
## Installation
Ensure you have the required dependencies installed:
```sh
dotnet add package Telegram.Bot
```
## Usage
### Inline Query Handling
Use the `InlineAttribute` to register a method as an inline query handler.
```csharp
[Inline("example_query")]
public static async Task HandleInlineQuery(ITelegramBotClient bot, InlineQuery query, CancellationToken cancellationToken)
{
// Handle inline query
}
```
### Edited Message Handling
Use the `EditMessageAttribute` to register a method as a handler for edited messages.
```csharp
[EditMessage]
public static async Task HandleEditedMessage(ITelegramBotClient bot, Message message, CancellationToken cancellationToken)
{
// Handle edited message
}
```
### Command Handling
Use the `CommandHandlerAttribute` to register a method as a command handler.
You can provide only begin of command text. Like, `/start act-`
```csharp
[CommandHandler("/start")]
public static async Task StartCommand(ITelegramBotClient bot, Message message, CancellationToken cancellationToken)
{
// Handle start command
}
```
### Callback Query Handling
Use the `CallbackAttribute` to register a method as a callback query handler.
You can provide only begin of callback data text
```csharp
[Callback("button_click")]
public static async Task HandleCallbackQuery(ITelegramBotClient bot, CallbackQuery query, CancellationToken cancellationToken)
{
// Handle callback query
}
```
### PreCheckout Query Handling
Use the `PreCheckoutAttribute` to register a method as a pre-checkout query handler.
```csharp
[PreCheckout]
public static async Task HandlePreCheckoutQuery(ITelegramBotClient bot, PreCheckoutQuery query, CancellationToken cancellationToken)
{
// Handle pre-checkout query
}
```
### General Update Handling
Use the `UpdateAttribute` to register a method as a generic update handler.
```csharp
[Update]
public static async Task HandleUpdate(ITelegramBotClient bot, Update update, CancellationToken cancellationToken)
{
// Handle general update
}
```
## License
This project is open-source and available under the MIT License.

22
Telegram.Net.sln Normal file
View File

@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Net", "Telegram.Net\Telegram.Net.csproj", "{137609A1-3482-465E-9B76-7E3F78FFC906}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Examples", "Examples\Telegram.Examples\Telegram.Examples.csproj", "{073E66F2-F82E-4378-B390-C2364DFDC491}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{137609A1-3482-465E-9B76-7E3F78FFC906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{137609A1-3482-465E-9B76-7E3F78FFC906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{137609A1-3482-465E-9B76-7E3F78FFC906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{137609A1-3482-465E-9B76-7E3F78FFC906}.Release|Any CPU.Build.0 = Release|Any CPU
{073E66F2-F82E-4378-B390-C2364DFDC491}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,59 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for registering callback query handlers in a Telegram bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class CallbackAttribute : Attribute
{
public bool IsReusable => true;
public string QueryId { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CallbackAttribute"/> class,
/// registering methods marked with this attribute as callback query handlers.
/// <code>
/// [Callback("auth-")] // You can use id like with StartsWith
/// public async Task PreCheckout(ITelegramBotClient client, CallbackQuery callback, CancellationToken ctx){
/// Console.WriteLine(callback.Data);
/// }
/// </code>
/// </summary>
/// <param name="queryId">The unique identifier for the callback query.</param>
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<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

@@ -0,0 +1,59 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for registering command handlers in a Telegram bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute : Attribute
{
public bool IsReusable => true;
public string Command { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandAttribute"/> class,
/// registering methods marked with this attribute as command handlers.
/// <code>
/// [Command("/start")] // You can use it like with StartsWith
/// public async Task Start(ITelegramBotClient client, Message message, CancellationToken ctx){
/// Console.WriteLine(message.Text);
/// }
/// </code>
/// </summary>
/// <param name="command">The command to be handled.</param>
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<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

@@ -0,0 +1,54 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for registering handlers for edited messages in a Telegram bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class EditMessageAttribute : Attribute
{
public bool IsReusable => true;
/// <summary>
/// Initializes a new instance of the <see cref="EditMessageAttribute"/> class,
/// registering methods marked with this attribute as handlers for edited messages.
/// <code>
/// [EditMessage]
/// public async Task EditMessage(ITelegramBotClient client, Message message, CancellationToken ctx){
/// Console.WriteLine(message.Id);
/// }
/// </code>
/// </summary>
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

@@ -0,0 +1,60 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for registering inline query handlers in a Telegram bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class InlineAttribute : Attribute
{
public bool IsReusable => true;
public string InlineId { get; }
/// <summary>
/// Initializes a new instance of the <see cref="InlineAttribute"/> class,
/// registering methods marked with this attribute as inline query handlers.
/// <code>
/// [Inline("InlineId")] // You can use it like with StartsWith
/// public async Task Inline(ITelegramBotClient client, InlineQuery inline, CancellationToken ctx){
/// Console.WriteLine(inline.From.Id);
/// }
/// </code>
/// </summary>
/// <param name="inlineId">Unique identifier for the inline query.</param>
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<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

@@ -0,0 +1,50 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for pre checkout handler. Using:
/// <code>
/// [PreCheckout]
/// public async Task PreCheckout(ITelegramBotClient client, PreCheckoutQuery preCheckout, CancellationToken ctx){
/// Console.WriteLine(preCheckout.Id);
/// }
/// </code>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
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<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

@@ -0,0 +1,49 @@
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Interfaces;
using Telegram.Net.Services;
namespace Telegram.Net.Attributes;
/// <summary>
/// Attribute for default update handler. Using:
/// <code>
/// [Update]
/// public async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx){
/// Console.WriteLine(Update.Message?.Text);
/// }
/// </code>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
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<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

@@ -0,0 +1,20 @@
using Telegram.Bot;
using Telegram.Bot.Polling;
namespace Telegram.Net.Interfaces;
public interface ITelegramBotConfig
{
/// <summary>
/// Token of telegram bot. You can take it from @BotFather
/// </summary>
public string Token { internal get; init; }
/// <summary>
/// Custom error handler for bot. You can add custom logger or anything.
/// </summary>
public Func<ITelegramBotClient, Exception, CancellationToken, Task>? errorHandler { get; init; }
/// <summary>
/// Custom receiver options
/// </summary>
public ReceiverOptions? ReceiverOptions { get; init; }
}

View File

@@ -0,0 +1,9 @@
namespace Telegram.Net.Interfaces;
/// <summary>
/// You should implement this interface in all your classes with update polling logic
/// </summary>
public interface IUpdatePollingSerivce
{
}

View File

@@ -0,0 +1,17 @@
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Net.Interfaces;
namespace Telegram.Net.Models;
public class TelegramBotConfig : ITelegramBotConfig
{
public TelegramBotConfig(string token)
{
Token = token;
}
public string Token { get; init; }
public Func<ITelegramBotClient, Exception, CancellationToken, Task>? errorHandler { get; init; }
public ReceiverOptions? ReceiverOptions { get; init; }
}

View File

@@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
using Telegram.Net.Models;
using Telegram.Net.Services;
namespace Telegram.Net;
public static class ServiceBindings
{
public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config)
{
isc.AddHostedService<TelegramHostedService>(k => new(config));
return isc;
}
}

View File

@@ -0,0 +1,68 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Hosting;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments;
using Telegram.Net.Interfaces;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Telegram.Net.Services;
public class TelegramHostedService : IHostedService
{
private TelegramBotClient Client { get; }
private ITelegramBotConfig Config { get; }
internal static Dictionary<string, Func<ITelegramBotClient, Message, CancellationToken, Task>> CommandHandler { get; } = new();
internal static List<Func<ITelegramBotClient, Message, CancellationToken, Task>> EditedMessageHandler { get; } = new();
internal static Dictionary<string, Func<ITelegramBotClient, CallbackQuery,CancellationToken, Task>> CallbackQueryHandler { get; } = new();
internal static Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; } = new();
internal static Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal static List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new();
public TelegramHostedService(ITelegramBotConfig config)
{
Client = new TelegramBotClient(config.Token);
Config = config;
}
[SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken 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;
}
},
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions,
cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Client.DropPendingUpdates(cancellationToken);
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.3" />
<PackageReference Include="Telegram.Bot" Version="22.4.4" />
</ItemGroup>
</Project>