13 Commits

Author SHA1 Message Date
Dmitri Shimanski
529378c0bb Create CONTRIBUTING.md 2025-03-29 17:18:13 +03:00
Dmitri Shimanski
2371180bd4 Merge pull request #5 from yawaflua/develop
pre-1.0.3
Fix error, when many commands with one base command(/start, /startapp, etc) not working properly.
Fix versions of dependencies for better compatability
2025-03-29 03:14:29 +03:00
Dmitri Shimanski
a691064881 Change version logic from ">=" to "(X,)" 2025-03-28 16:44:18 +03:00
Dmitri Shimanski
0a7e3d4b49 Fix warnings and change versions of dependencies for better compatability 2025-03-28 16:35:38 +03:00
Dmitri Shimanski
7b7b2effca Fix many StartWith handler 2025-03-28 16:15:07 +03:00
Dmitri Shimanski
9b0578092b Update README.md 2025-03-21 01:08:34 +02:00
Dmitri Shimanski
38df51f4b3 Merge pull request #4 from yawaflua/develop
pre-v1.0.2 fix
2025-03-21 01:07:42 +02:00
Dmitri Shimanski
55079122ac Merge remote-tracking branch 'origin/develop' into develop 2025-03-21 01:06:07 +02:00
Dmitri Shimanski
c2e04598f6 Fix error with providing attributes 2025-03-21 01:05:55 +02:00
Dmitri Shimanski
4da17d8bf0 Merge branch 'master' into develop 2025-03-21 00:51:37 +02:00
Dmitri Shimanski
cf9a5c14ef Change version 2025-03-21 00:51:00 +02:00
Dmitri Shimanski
3094fcbabe Fix error with broken dependencies. 2025-03-21 00:49:01 +02:00
Dmitri Shimanski
59a94c9f97 Update README.md 2025-03-21 00:10:16 +02:00
8 changed files with 227 additions and 139 deletions

67
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,67 @@
# Contributing to Telegram.Net
Thank you for your interest in contributing to Telegram.Net! We welcome contributions from anyone in the community, regardless of experience level.
## How to Contribute
### Getting Started
1. Fork the repository at [https://github.com/yawaflua/Telegram.Net](https://github.com/yawaflua/Telegram.Net)
2. Clone your fork to your local machine
3. Set up the development environment with the required .NET SDK
### Making Changes
1. Create a new branch for your feature or bug fix:
```
git checkout -b feature/your-feature-name
```
2. Make your changes
3. Write or update tests for the changes you made using appropriate testing frameworks (e.g., MSTest, NUnit, or xUnit)
4. Ensure your code passes all tests and meets C# coding conventions
## Code Quality Requirements
### Test Coverage
- **All pull requests must include tests with at least 50% code coverage**
- Before submitting your PR, verify coverage using tools like:
```
# Example using Coverlet and ReportGenerator
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
dotnet tool run reportgenerator -reports:"**/coverage.opencover.xml" -targetdir:"coveragereport" -reporttypes:Html
```
### Coding Standards
- Follow C# coding conventions and the existing style in the project
- Use meaningful names for classes, methods, and variables
- Include XML documentation comments for public APIs
- Keep methods focused and reasonably sized
## Pull Request Process
1. Update documentation if you're changing or adding functionality
2. Ensure your code has adequate test coverage (minimum 50%)
3. Submit your pull request with a clear title and description
4. Reference any relevant issues in your PR description
## Review Process
- All submissions require review before being merged
- Maintainers may request changes or suggest improvements
- Be responsive to feedback on your pull request
## C# Specific Guidelines
- Target the same .NET version as the project
- Avoid excessive dependencies unless absolutely necessary
- Follow SOLID principles when applicable
- Use async/await patterns appropriately for asynchronous operations
- Be mindful of performance implications, especially for a networking library
## Questions?
If you have any questions about contributing to Telegram.Net, feel free to open an issue for discussion.
Thank you for helping improve Telegram.Net!

View File

@@ -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;
@@ -7,12 +8,11 @@ var webHost = Host.CreateDefaultBuilder()
.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")));
});
webHost.Build().Run();

View File

@@ -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>

View File

@@ -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;
@@ -7,6 +8,11 @@ namespace Telegram.Examples.UpdatePolling;
public class Update : IUpdatePollingService
{
private static IConfiguration _conf;
public Update(IConfiguration conf)
{
_conf = conf;
}
[Update]
public async Task UpdateExample(ITelegramBotClient client, Bot.Types.Update update, CancellationToken ctx)
{
@@ -31,6 +37,12 @@ public class Update : IUpdatePollingService
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)
{

View File

@@ -0,0 +1,4 @@
{
"ExampleMessage": "Test!",
"telegram_test_token": "PROVIDE_TOKEN"
}

View File

@@ -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.
@@ -86,5 +100,10 @@ public static async Task HandleUpdate(ITelegramBotClient bot, Update update, Can
}
```
### We know about this bugs:
- [ X ] Not working providing dependencies in class constructor and this gives an error.
~~Try to not use provided dependencies in class. We are should to fix it in v1.0.2.~~
Now just use static variables
## License
This project is open-source and available under the [Apache 2.0 License](LICENSE).

View File

@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -9,35 +8,38 @@ using Telegram.Bot.Types;
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;
[SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")]
public class TelegramHostedService : IHostedService
{
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();
private IServiceCollection ServiceCollection { get; } = null!;
internal TelegramBotClient Client { get; set; } = null!;
private ITelegramBotConfig Config { get; } = null!;
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 static ILogger<TelegramHostedService> _logger;
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>?> DefaultUpdateHandler { get; set; } = new();
internal static ILogger<TelegramHostedService> Logger = null!;
public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc, ILogger<TelegramHostedService> logger)
public TelegramHostedService(ITelegramBotConfig config, IServiceCollection serviceCollection, ILogger<TelegramHostedService> logger)
{
try
{
_logger = logger;
Logger = logger;
Client = new TelegramBotClient(config.Token);
Config = config;
this.isc = isc;
this.ServiceCollection = serviceCollection;
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception when creating TelegramHostedService: ");
Logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception when creating TelegramHostedService: ");
}
}
internal static bool IsValidHandlerMethod(MethodInfo method, Type parameterType)
@@ -53,12 +55,12 @@ public class TelegramHostedService : IHostedService
}
catch (Exception ex)
{
_logger.LogError(ex, "Catched exception in parsing and checking params.");
Logger.LogError(ex, "Catched exception in parsing and checking params.");
return false;
}
}
internal static Func<ITelegramBotClient, T, CancellationToken, Task> CreateDelegate<T>(MethodInfo method)
internal static Func<ITelegramBotClient, T, CancellationToken, Task>? CreateDelegate<T>(MethodInfo method)
{
try
{
@@ -68,127 +70,93 @@ public class TelegramHostedService : IHostedService
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception in CreateDelegate function: ");
Logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception in CreateDelegate function: ");
return null;
}
}
internal async Task AddAttributes(CancellationToken cancellationToken)
{
await Task.Run(async () =>
{
try
{
var implementations = AppDomain.CurrentDomain.GetAssemblies()
var attributeTypes = new HashSet<Type>
{
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);
.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();
foreach (var implementation in implementations)
if (methods.Count == 0)
{
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.");
Logger.LogWarning("No methods found with required attributes");
}
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var isp = ServiceCollection.BuildServiceProvider();
foreach (var method in methods)
{
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
var declaringType = method.DeclaringType!;
var constructor = declaringType.GetConstructors()[0];
var parameters = constructor.GetParameters()
.Select(param => isp.GetRequiredService(param.ParameterType))
.ToArray();
constructor.Invoke(parameters);
switch (method.GetCustomAttributes().First(t => attributeTypes.Contains(t.GetType())))
{
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 updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
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;
}
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched new exception when added methods: ");
Logger.Log(LogLevel.Critical, new EventId(), ex, "Catched new exception when added methods: ");
}
}, cancellationToken);
}
@@ -201,31 +169,46 @@ public class TelegramHostedService : IHostedService
switch (update)
{
case { Message: { } message }:
await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key))
.Value(client, message, ctx);
CommandHandler.Where(k => message.Text!.StartsWith(k.Key)).Select(async k =>
{
await k.Value!(client, message, ctx);
return k;
});
break;
case { EditedMessage: { } message }:
EditedMessageHandler.ForEach(async k => await k(client, message, ctx));
// ReSharper disable once AsyncVoidLambda
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);
CallbackQueryHandler.Where(k => callbackQuery.Data!.StartsWith(k.Key))
.Select(async k =>
{
await k.Value!(client, callbackQuery, ctx);
return k;
});
break;
case { InlineQuery: { } inlineQuery }:
await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key))
.Value(client, inlineQuery, ctx);
InlineHandler.Where(k => inlineQuery.Id.StartsWith(k.Key)).Select(async k =>
{
await k.Value!(client, inlineQuery, ctx);
return k;
});
break;
case { PreCheckoutQuery: { } preCheckoutQuery }:
if (PreCheckoutHandler != null) await PreCheckoutHandler(client, preCheckoutQuery, ctx);
break;
default:
DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx));
// ReSharper disable once AsyncVoidLambda
DefaultUpdateHandler.ForEach(async k => await k!(client, update, ctx));
break;
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, new EventId(), ex, "Catched exception in UpdateHandler: ");
if (ex is KeyNotFoundException)
Logger.Log(LogLevel.Warning, new EventId(), ex, "Key not found: ");
else
Logger.Log(LogLevel.Error, new EventId(), ex, "Caught exception in UpdateHandler: ");
}
}
@@ -242,7 +225,7 @@ public class TelegramHostedService : IHostedService
UpdateHandler,
Config.errorHandler ?? ((_, ex, _) =>
{
_logger.LogError(ex, "Catched error in telegram bot working: ");
Logger.LogError(ex, "Catched error in telegram bot working: ");
return Task.CompletedTask;
}),
Config.ReceiverOptions,
@@ -250,7 +233,7 @@ public class TelegramHostedService : IHostedService
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Failed to start. Catched exception: ");
Logger.Log(LogLevel.Critical, new EventId(), ex, "Failed to start. Catched exception: ");
}
}
@@ -262,7 +245,7 @@ public class TelegramHostedService : IHostedService
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Failed to stop. Exception: ");
Logger.LogCritical(ex, "Failed to stop. Exception: ");
}
}
}

View File

@@ -2,10 +2,10 @@
<PropertyGroup>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.0</Version>
<Version>1.0.3</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>
@@ -19,8 +19,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.3" />
<PackageReference Include="Telegram.Bot" Version="22.4.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="(6.0.0,)" />
<PackageReference Include="Telegram.Bot" Version="22.4.*" />
<None Include="..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\LICENSE" Pack="true" PackagePath=""/>
</ItemGroup>