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
16 changes: 8 additions & 8 deletions Examples/Telegram.Examples/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Net;
Expand All @@ -7,12 +8,11 @@
.ConfigureLogging(l => l.ClearProviders().AddConsole())
.ConfigureServices(k =>
{
k.ConnectTelegram(new("TOKEN")
{
errorHandler = async (client, exception, ctx) =>
{
Console.WriteLine(exception);
}
});
var _conf = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
k.AddSingleton(_conf);
k.ConnectTelegram(new(_conf.GetValue<string>("telegram_test_token")));

Check warning on line 16 in Examples/Telegram.Examples/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'token' in 'TelegramBotConfig.TelegramBotConfig(string token)'.

Check warning on line 16 in Examples/Telegram.Examples/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'token' in 'TelegramBotConfig.TelegramBotConfig(string token)'.
});
webHost.Build().Run();
3 changes: 3 additions & 0 deletions Examples/Telegram.Examples/Telegram.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<ProjectReference Include="..\..\Telegram.Net\Telegram.Net.csproj" />

<None CopyToOutputDirectory="Always" Include="appsettings.json"></None>
</ItemGroup>


</Project>
14 changes: 13 additions & 1 deletion Examples/Telegram.Examples/UpdatePolling/Update.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Telegram.Bot;
using Microsoft.Extensions.Configuration;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Net.Attributes;
using Telegram.Net.Interfaces;
Expand All @@ -7,8 +8,13 @@

public class Update : IUpdatePollingService
{
private static IConfiguration _conf;

Check warning on line 11 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

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

Check warning on line 11 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_conf' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
public Update(IConfiguration conf)
{
_conf = conf;
}
[Update]
public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx)

Check warning on line 17 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 17 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
if (update.Poll != null)
{
Expand All @@ -17,7 +23,7 @@
}

[Callback("act-")]
public async Task CallbackExample(ITelegramBotClient client, CallbackQuery query, CancellationToken ctx)

Check warning on line 26 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 26 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
Console.WriteLine(query.Message!.Text);
}
Expand All @@ -31,8 +37,14 @@
await client.SendMessage(message.From!.Id, "Hello, I`m example bot.", cancellationToken: ctx);
}

[Command("/test_conf")]
public async Task TestConfigurationBuilder(ITelegramBotClient client, Message message, CancellationToken cts)
{
await client.SendMessage(message.Chat.Id, _conf.GetValue<string>("ExampleMessage") ?? throw new Exception("Not found"));
}

[EditMessage]
public async Task EditMessageExmaple(ITelegramBotClient client, Message message, CancellationToken ctx)

Check warning on line 47 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 47 in Examples/Telegram.Examples/UpdatePolling/Update.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
Console.WriteLine($"new message text: {message.Text}");
}
Expand Down
4 changes: 4 additions & 0 deletions Examples/Telegram.Examples/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ExampleMessage": "Test!",
"telegram_test_token": "PROVIDE_TOKEN"
}
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ Ensure you have the required dependencies installed:

## Usage

### Provide dependencies in class
You can provide dependencies in class from constructor, and after it use it like `static`.
```csharp
public class Example : IUpdatePollingService
{
private static MyCoolService _service; // It should to be static!

public Example(MyCoolService service)
{
_service = service;
}
}
```

### Inline Query Handling
Use the `InlineAttribute` to register a method as an inline query handler.

Expand Down Expand Up @@ -91,4 +105,4 @@ public static async Task HandleUpdate(ITelegramBotClient bot, Update update, Can
Try to not use provided dependencies in class. We are should to fix it in v1.0.2.

## License
This project is open-source and available under the [Apache 2.0 License](LICENSE).
This project is open-source and available under the [Apache 2.0 License](LICENSE).
170 changes: 69 additions & 101 deletions Telegram.Net/Services/TelegramHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using Telegram.Bot.Types.Payments;
using Telegram.Net.Attributes;
using Telegram.Net.Interfaces;
using static System.Reflection.BindingFlags;

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

namespace Telegram.Net.Services;
Expand All @@ -18,15 +20,15 @@
private IServiceCollection isc { get; }
internal TelegramBotClient Client { get; set; }
private ITelegramBotConfig Config { get; }
internal Dictionary<string, Func<ITelegramBotClient, Message, CancellationToken, Task>> CommandHandler { get; } = new();
internal List<Func<ITelegramBotClient, Message, CancellationToken, Task>> EditedMessageHandler { get; } = new();
internal Dictionary<string, Func<ITelegramBotClient, CallbackQuery,CancellationToken, Task>> CallbackQueryHandler { get; } = new();
internal Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; } = new();
internal Dictionary<string, Func<ITelegramBotClient, Message, CancellationToken, Task>> CommandHandler { get; set; } = new();
internal List<Func<ITelegramBotClient, Message, CancellationToken, Task>> EditedMessageHandler { get; set; } = new();
internal Dictionary<string, Func<ITelegramBotClient, CallbackQuery,CancellationToken, Task>> CallbackQueryHandler { get; set; } = new();
internal Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; set; } = new();
internal Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new();
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; set; } = new();
internal static 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 field '_logger' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 29 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 31 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 31 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 31 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 31 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 31 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 31 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.
{
try
{
Expand Down Expand Up @@ -69,120 +71,86 @@
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception in CreateDelegate function: ");
return null;

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

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

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

foreach (var implementation in implementations)
var attributeTypes = new HashSet<Type>
{
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)
typeof(CommandAttribute),
typeof(CallbackAttribute),
typeof(EditMessageAttribute),
typeof(InlineAttribute),
typeof(PreCheckoutAttribute),
typeof(UpdateAttribute)
};

var methods = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface)
.SelectMany(t => t.GetMethods(Instance |
Public |
NonPublic |
DeclaredOnly))
.Where(m => m.GetCustomAttributes().Any(a => attributeTypes.Contains(a.GetType())))
.ToList();

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

await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var isp = isc.BuildServiceProvider();
foreach (var method in methods)
{
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
{
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;
}

var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
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;
}

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);
if (!InlineHandler.TryAdd(inlineAttr.InlineId, handler))
throw new Exception($"Failed to add in inlineHandler: {inlineAttr.InlineId}");;
}

continue;
}

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

continue;
}
var declaringType = method.DeclaringType!;
var constructor = declaringType.GetConstructors()[0];
var parameters = constructor.GetParameters()
.Select(param => isp.GetRequiredService(param.ParameterType))
.ToArray();

constructor.Invoke(parameters);

var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
switch (method.GetCustomAttributes().First(t => attributeTypes.Contains(t.GetType())))
{
if (IsValidHandlerMethod(method, typeof(Update)))
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
}

continue;
case CommandAttribute command when IsValidHandlerMethod(method, typeof(Message)):
var commandHandler = CreateDelegate<Message>(method);
if (!CommandHandler.TryAdd(command.Command, commandHandler))
throw new Exception($"Failed to add command: {command.Command}");
break;

case CallbackAttribute callback when IsValidHandlerMethod(method, typeof(CallbackQuery)):
var callbackHandler = CreateDelegate<CallbackQuery>(method);
if (!CallbackQueryHandler.TryAdd(callback.QueryId, callbackHandler))
throw new Exception($"Failed to add callback: {callback.QueryId}");
break;

case EditMessageAttribute _ when IsValidHandlerMethod(method, typeof(Message)):
EditedMessageHandler.Add(CreateDelegate<Message>(method));
break;

case InlineAttribute inline when IsValidHandlerMethod(method, typeof(InlineQuery)):
var inlineHandler = CreateDelegate<InlineQuery>(method);
if (!InlineHandler.TryAdd(inline.InlineId, inlineHandler))
throw new Exception($"Failed to add inline: {inline.InlineId}");
break;

case PreCheckoutAttribute _ when IsValidHandlerMethod(method, typeof(PreCheckoutQuery)):
PreCheckoutHandler = CreateDelegate<PreCheckoutQuery>(method);
break;

case UpdateAttribute _ when IsValidHandlerMethod(method, typeof(Update)):
DefaultUpdateHandler.Add(CreateDelegate<Update>(method));
break;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Telegram.Net/Telegram.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.0</Version>
<Version>1.0.2</Version>
<Authors>yawaflua</Authors>
<Title>yawaflua.Telegram.Net</Title>
<Description>Telegram.Bots extender pack, what provides to user attributes, which can used with services</Description>
Expand Down