Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Telegram.Net/ServiceBindings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Net.Models;
using Telegram.Net.Services;

Expand All @@ -8,7 +10,8 @@ public static class ServiceBindings
{
public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config)
{
isc.AddHostedService<TelegramHostedService>(k => new(config, isc));
var logger = isc.BuildServiceProvider().GetRequiredService<ILogger<TelegramHostedService>>();
isc.AddHostedService<TelegramHostedService>(k => new(config, isc, logger));
return isc;
}
}
294 changes: 186 additions & 108 deletions Telegram.Net/Services/TelegramHostedService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments;
Expand All @@ -22,169 +24,245 @@
internal Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; } = new();
internal Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new();

public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc)
internal static ILogger<TelegramHostedService> _logger;

Check warning on line 27 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_logger' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 27 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_logger' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc, ILogger<TelegramHostedService> logger)

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Config' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Client' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'isc' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Config' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Client' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'isc' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
Client = new TelegramBotClient(config.Token);
Config = config;
this.isc = isc;
try
{
_logger = logger;
Client = new TelegramBotClient(config.Token);
Config = config;
this.isc = isc;
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception when creating TelegramHostedService: ");
}
}
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);
try
{
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);
}
catch (Exception ex)
{
_logger.LogError(ex, "Catched exception in parsing and checking params.");
return false;
}
}

internal 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>)!;
try
{
var delegateType = typeof(Func<ITelegramBotClient, T, CancellationToken, Task>);
return (Delegate.CreateDelegate(delegateType, null, method) as
Func<ITelegramBotClient, T, CancellationToken, Task>)!;
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception in CreateDelegate function: ");
return null;

Check warning on line 72 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

Check warning on line 72 in Telegram.Net/Services/TelegramHostedService.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

}

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<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)
try
{
var implementations = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface);

foreach (var implementation in implementations)
{
isc.AddScoped(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);

if (methods.Count() == 0)
{
_logger.LogWarning("Not founded methods with attributes.");
}

await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
foreach (var method in methods)
{
if (IsValidHandlerMethod(method, typeof(Message)))
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
{
var handler = CreateDelegate<Message>(method);
CommandHandler.TryAdd(commandAttr.Command, handler);
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
if (!CommandHandler.TryAdd(commandAttr.Command, handler))
throw new Exception($"Failed to add in commandHandler: {commandAttr.Command}");
}

continue;
}
continue;
}

var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
var handler = CreateDelegate<CallbackQuery>(method);
CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler);
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
{
var handler = CreateDelegate<CallbackQuery>(method);
if (!CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler))
throw new Exception($"Failed to add in callbacKQuery: {callbackAttr.QueryId}");;
}

continue;
}
continue;
}

var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (editMessageAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Message)))
var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (editMessageAttr != null)
{
var handler = CreateDelegate<Message>(method);
EditedMessageHandler.Add(handler);
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
EditedMessageHandler.Add(handler);
}

continue;
}
continue;
}

var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
if (inlineAttr != null)
{
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
if (inlineAttr != null)
{
var handler = CreateDelegate<InlineQuery>(method);
InlineHandler.TryAdd(inlineAttr.InlineId, handler);
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
{
var handler = CreateDelegate<InlineQuery>(method);
if (!InlineHandler.TryAdd(inlineAttr.InlineId, handler))
throw new Exception($"Failed to add in inlineHandler: {inlineAttr.InlineId}");;
}

continue;
}
continue;
}

var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>();
if (preCheckoutAttr != null)
{
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>();
if (preCheckoutAttr != null)
{
var handler = CreateDelegate<PreCheckoutQuery>(method);
PreCheckoutHandler = handler;
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
{
var handler = CreateDelegate<PreCheckoutQuery>(method);
PreCheckoutHandler = handler;
}

continue;
}
continue;
}

var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Update)))
var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
if (IsValidHandlerMethod(method, typeof(Update)))
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
}

continue;
}
continue;
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched new exception when added methods: ");
}
}, cancellationToken);
}


internal async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx)
{
switch (update)
try
{
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;
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;
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, new EventId(), ex, "Catched exception in UpdateHandler: ");
}
}

[SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken cancellationToken)
{
await AddAttributes(cancellationToken);
try
{
await AddAttributes(cancellationToken);



Client.StartReceiving(
UpdateHandler,
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions,
cancellationToken);
Client.StartReceiving(
UpdateHandler,
Config.errorHandler ?? ((_, ex, _) =>
{
_logger.LogError(ex, "Catched error in telegram bot working: ");
return Task.CompletedTask;
}),
Config.ReceiverOptions,
cancellationToken);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Failed to start. Catched exception: ");
}
}

public async Task StopAsync(CancellationToken cancellationToken)
{
await Client.DropPendingUpdates(cancellationToken);
try
{
await Client.DropPendingUpdates(cancellationToken);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Failed to stop. Exception: ");
}
}
}
Loading
Loading