From 3a88feb5f125fcac0c0269def233632d87fd1227 Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sun, 10 Aug 2025 02:48:45 +0400 Subject: [PATCH] * Improved filter fallback search logic * Fixed Environment variable filter logic * Changed some logs to trace level * Added new Message extension methods --- .../Polling/HostUpdateRouter.cs | 2 +- Telegrator.sln | 8 +++ Telegrator/Attributes/MightAwaitAttribute.cs | 4 +- Telegrator/Filters/CommandArgumentFilter.cs | 20 +++++++ Telegrator/Filters/EnvironmentFilters.cs | 8 +-- Telegrator/Handlers/CommandHandler.cs | 2 + .../Components/FiltersFallbackReport.cs | 30 +++------- Telegrator/Logging/Alligator.cs | 21 +++++++ Telegrator/Polling/UpdateHandlersPool.cs | 14 +---- Telegrator/Polling/UpdateRouter.cs | 59 +++++++++---------- Telegrator/Providers/HandlersProvider.cs | 13 ++-- Telegrator/TypesExtensions.cs | 50 ++++++++++++++++ 12 files changed, 158 insertions(+), 73 deletions(-) create mode 100644 Telegrator/Filters/CommandArgumentFilter.cs diff --git a/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/Telegrator.Hosting/Polling/HostUpdateRouter.cs index be569d1..aa16728 100644 --- a/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -33,7 +33,7 @@ namespace Telegrator.Hosting.Polling /// public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { - Logger.LogInformation("Received update of type \"{type}\"", update.Type); + //Logger.LogInformation("Received update of type \"{type}\"", update.Type); return base.HandleUpdateAsync(botClient, update, cancellationToken); } diff --git a/Telegrator.sln b/Telegrator.sln index f9bed94..88c8e42 100644 --- a/Telegrator.sln +++ b/Telegrator.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Analyzers", "Tel EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting.Web", "Telegrator.Hosting.Web\Telegrator.Hosting.Web.csproj", "{98AB490F-6A36-CCFF-F6E6-B029D1665965}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SosalBot", "..\SosalBot\SosalBot\SosalBot.csproj", "{D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AnalyzersDebug|Any CPU = AnalyzersDebug|Any CPU @@ -63,6 +65,12 @@ Global {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Debug|Any CPU.Build.0 = Debug|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.ActiveCfg = Release|Any CPU {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.Build.0 = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6AA4D47-0DCE-520E-5779-A14EA9CB1DEC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Telegrator/Attributes/MightAwaitAttribute.cs b/Telegrator/Attributes/MightAwaitAttribute.cs index 3a3fbc1..685550d 100644 --- a/Telegrator/Attributes/MightAwaitAttribute.cs +++ b/Telegrator/Attributes/MightAwaitAttribute.cs @@ -3,7 +3,9 @@ namespace Telegrator.Attributes { /// - /// Attribute that says if this handler cn await some of await types, that is not listed by its handler base + /// Attribute that says if this handler can await some of await types, that is not listed by its handler base. + /// Used for automatic collecting allowed to receiving 's. + /// If you don't use it, you won't be able to await the updates inside handler. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class MightAwaitAttribute : Attribute diff --git a/Telegrator/Filters/CommandArgumentFilter.cs b/Telegrator/Filters/CommandArgumentFilter.cs new file mode 100644 index 0000000..57924a5 --- /dev/null +++ b/Telegrator/Filters/CommandArgumentFilter.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Telegram.Bot.Types; +using Telegrator.Filters.Components; +using Telegrator.Handlers; + +namespace Telegrator.Filters +{ + public class CommandArgumentFilter : Filter + { + public override bool CanPass(FilterExecutionContext context) + { + CommandHandlerAttribute attr = context.CompletedFilters.Get(0); + string[] args = attr.Arguments ??= context.Input.SplitArgs(); + + return alliases.Contains(ReceivedCommand, StringComparer.InvariantCultureIgnoreCase); + } + } +} diff --git a/Telegrator/Filters/EnvironmentFilters.cs b/Telegrator/Filters/EnvironmentFilters.cs index 65c92bb..d4c4439 100644 --- a/Telegrator/Filters/EnvironmentFilters.cs +++ b/Telegrator/Filters/EnvironmentFilters.cs @@ -113,11 +113,11 @@ namespace Telegrator.Filters { string? envValue = Environment.GetEnvironmentVariable(_variable); - if (envValue == null && _value == null) - return true; - if (envValue == null) - return false; + return _value == null; + + if (_value == "{NOT_NULL}") + return true; return envValue.Equals(_value, _comparison); } diff --git a/Telegrator/Handlers/CommandHandler.cs b/Telegrator/Handlers/CommandHandler.cs index 36783f6..627e49c 100644 --- a/Telegrator/Handlers/CommandHandler.cs +++ b/Telegrator/Handlers/CommandHandler.cs @@ -16,6 +16,8 @@ namespace Telegrator.Handlers /// public string ReceivedCommand { get; private set; } = null!; + public string[]? Arguments { get; internal set; } = null; + /// /// Checks if the update contains a valid bot command and extracts the command text. /// diff --git a/Telegrator/Handlers/Components/FiltersFallbackReport.cs b/Telegrator/Handlers/Components/FiltersFallbackReport.cs index a44cd6e..b1fb1db 100644 --- a/Telegrator/Handlers/Components/FiltersFallbackReport.cs +++ b/Telegrator/Handlers/Components/FiltersFallbackReport.cs @@ -1,5 +1,4 @@ -using Telegram.Bot; -using Telegram.Bot.Types; +using Telegram.Bot.Types; using Telegrator.Attributes.Components; using Telegrator.Filters.Components; using Telegrator.MadiatorCore.Descriptors; @@ -39,23 +38,6 @@ namespace Telegrator.Handlers.Components /// public List UpdateFilters { get; } = []; - /* - public FilterFallbackInfo OfType(int index = 0) where T : IFilter - { - return UpdateFilters.Where(info => info.Failed is T).ElementAt(index); - } - - public FilterFallbackInfo OfAttribute(int index = 0) where T : UpdateFilterAttributeBase - { - return UpdateFilters.Where(info => info.Name == typeof(T).Name).ElementAt(index); - } - - public FilterFallbackInfo OfName(string name, int index = 0) - { - return UpdateFilters.Where(info => info.Name == name).ElementAt(index); - } - */ - /// /// Checks if the failure is due to a specific attribute type, excluding other failures. /// @@ -65,11 +47,17 @@ namespace Telegrator.Handlers.Components public bool ExceptAttribute(int index = 0) where T : UpdateFilterAttributeBase { string name = typeof(T).Name; + IEnumerable failed = UpdateFilters.Where(info => info.Failed); - if (failed.Count() > 1) + if (failed.Count() != 1) return false; - return failed.SingleOrDefault()?.Name == name; + FilterFallbackInfo info = failed.ElementAt(0); + if (info.Name != name) + return false; + + FilterFallbackInfo? target = UpdateFilters.ElementAtOrDefault(index); + return target == info; } } diff --git a/Telegrator/Logging/Alligator.cs b/Telegrator/Logging/Alligator.cs index 03e2e4a..8dea892 100644 --- a/Telegrator/Logging/Alligator.cs +++ b/Telegrator/Logging/Alligator.cs @@ -105,6 +105,16 @@ namespace Telegrator.Logging Log(LogLevel.Trace, message); } + /// + /// Logs a trace message to all registered adapters. + /// + /// The message to log. + /// + public static void LogTrace(string message, params object[] args) + { + Log(LogLevel.Trace, string.Format(message, args)); + } + /// /// Logs a debug message to all registered adapters. /// @@ -190,5 +200,16 @@ namespace Telegrator.Logging { Log(LogLevel.Error, exception.Message, exception); } + + /// + /// Logs an error message to all registered adapters. + /// + /// The message to log. + /// Optional exception. + /// + public static void LogError(string message, Exception? exception = null, params object[] args) + { + Log(LogLevel.Error, string.Format(message, args), exception); + } } } \ No newline at end of file diff --git a/Telegrator/Polling/UpdateHandlersPool.cs b/Telegrator/Polling/UpdateHandlersPool.cs index c9f7899..21a1451 100644 --- a/Telegrator/Polling/UpdateHandlersPool.cs +++ b/Telegrator/Polling/UpdateHandlersPool.cs @@ -77,7 +77,7 @@ namespace Telegrator.Polling try { - Alligator.LogDebug("Described handler '{0}'", handlerInfo.DisplayString); + Alligator.LogDebug("Described handler '{0}' (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); HandlerExecuting?.Invoke(handlerInfo); using (UpdateHandlerBase instance = handlerInfo.HandlerInstance) @@ -88,7 +88,7 @@ namespace Telegrator.Polling if (lastResult.RouteNext) { - Alligator.LogDebug("Handler requested route continuation"); + Alligator.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); } } catch (NotImplementedException) @@ -102,7 +102,7 @@ namespace Telegrator.Polling } catch (Exception ex) { - Alligator.LogError("Failed to process handler!", ex); + Alligator.LogError("Failed to process handler '{0}' (Update {1})", exception: ex, handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); } if (lastResult != null && !lastResult.RouteNext) @@ -124,14 +124,6 @@ namespace Telegrator.Polling ExecutingHandlersSemaphore = null!; } - /* - if (AwaitingHandlersQueuedEvent != null) - { - AwaitingHandlersQueuedEvent.Dispose(); - AwaitingHandlersQueuedEvent = null!; - } - */ - if (SyncObj != null) SyncObj = null!; diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index 9209c8c..c46e23f 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -100,7 +100,6 @@ namespace Telegrator.Polling public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { // Logging - Alligator.LogDebug("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); LogUpdate(update); try @@ -115,18 +114,18 @@ namespace Telegrator.Polling // Chicking if awaiting handlers has exclusive routing if (Options.ExclusiveAwaitingHandlerRouting) { - Alligator.LogDebug("Receiving Update ({0}) completed with only awaiting handlers", update.Id); + Alligator.LogTrace("Receiving Update ({0}) completed with only awaiting handlers", update.Id); return; } } // Queuing reagular handlers for execution await HandlersPool.Enqueue(GetHandlers(HandlersProvider, botClient, update, cancellationToken)); - Alligator.LogDebug("Receiving Update ({0}) finished", update.Id); + Alligator.LogTrace("Receiving Update ({0}) finished", update.Id); } catch (OperationCanceledException) { - Alligator.LogDebug("Receiving Update ({0}) cancelled", update.Id); + Alligator.LogTrace("Receiving Update ({0}) cancelled", update.Id); } catch (Exception ex) { @@ -146,16 +145,16 @@ namespace Telegrator.Polling /// A collection of described handler information for the update protected virtual IEnumerable GetHandlers(IHandlersProvider provider, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { - Alligator.LogDebug("Requested handlers for UpdateType.{0}", update.Type); + Alligator.LogTrace("Requested handlers for UpdateType.{0}", update.Type); if (!provider.TryGetDescriptorList(update.Type, out HandlerDescriptorList? descriptors)) { - Alligator.LogDebug("No registered, providing Any"); + Alligator.LogTrace("No registered, providing Any"); provider.TryGetDescriptorList(UpdateType.Unknown, out descriptors); } if (descriptors == null || descriptors.Count == 0) { - Alligator.LogDebug("No handlers provided"); + Alligator.LogTrace("No handlers provided"); return []; } @@ -178,7 +177,7 @@ namespace Telegrator.Polling /// A collection of described handler information protected virtual IEnumerable DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { - Alligator.LogDebug("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); + Alligator.LogTrace("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); foreach (HandlerDescriptor descriptor in descriptors.Reverse()) { cancellationToken.ThrowIfCancellationRequested(); @@ -192,7 +191,7 @@ namespace Telegrator.Polling yield return describedHandler; } - Alligator.LogDebug("Describing for Update ({0}) finished", update.Id); + Alligator.LogTrace("Describing for Update ({0}) finished", update.Id); } /// @@ -245,41 +244,41 @@ namespace Telegrator.Polling /// protected static void LogUpdate(Update update) { - switch (update.Type) + if (Alligator.MinimalLevel > LogLevel.Trace) + return; + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); + + switch (update) { - case UpdateType.Message: + case { Message: { } message }: { - Message msg = update.Message ?? throw new NullReferenceException(); - StringBuilder sb = new StringBuilder("Update.Message"); + if (message is { From: { } from }) + sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); - if (msg.From != null) - sb.AppendFormat(" from {0} ({1})", msg.From.Username, msg.From.Id); + if (message is { Text: { } text }) + sb.AppendFormat(" with text '{0}'", text); - if (msg.Text != null) - sb.AppendFormat(" with text '{0}'", msg.Text); + if (message is { Sticker: { } sticker }) + sb.AppendFormat(" with sticker '{0}'", sticker.Emoji); - if (msg.Sticker != null) - sb.AppendFormat(" with sticker '{0}'", msg.Sticker.Emoji); - - Alligator.LogDebug(sb.ToString()); break; } - case UpdateType.CallbackQuery: + case { CallbackQuery: { } callback }: { - CallbackQuery cq = update.CallbackQuery ?? throw new NullReferenceException(); - StringBuilder sb = new StringBuilder("Update.CallbackQuery"); + if (callback is { From: { } from }) + sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); - if (cq.From != null) - sb.AppendFormat(" from {0} ({1})", cq.From.Username, cq.From.Id); + if (callback is { Data: { } data }) + sb.AppendFormat(" with data '{0}'", data); - if (cq.From != null) - sb.AppendFormat(" with data '{0}'", cq.Data); - - Alligator.LogDebug(sb.ToString()); break; } } + + Alligator.LogTrace(sb.ToString()); } private class BreakDescribingException : Exception { } diff --git a/Telegrator/Providers/HandlersProvider.cs b/Telegrator/Providers/HandlersProvider.cs index 6e22d42..b6f716e 100644 --- a/Telegrator/Providers/HandlersProvider.cs +++ b/Telegrator/Providers/HandlersProvider.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.Runtime.CompilerServices; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegrator.Handlers.Components; @@ -41,7 +40,7 @@ namespace Telegrator.Providers AllowedTypes = handlers.AllowedTypes; HandlersDictionary = handlers.Values.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); Options = options ?? throw new ArgumentNullException(nameof(options)); - Alligator.LogDebug("{0} created!", GetType().Name); + Alligator.LogTrace("{0} created!", GetType().Name); } /// @@ -55,7 +54,7 @@ namespace Telegrator.Providers AllowedTypes = Update.AllTypes; HandlersDictionary = handlers.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); Options = options ?? throw new ArgumentNullException(nameof(options)); - Alligator.LogDebug("{0} created!", GetType().Name); + Alligator.LogTrace("{0} created!", GetType().Name); } /// @@ -64,22 +63,26 @@ namespace Telegrator.Providers { try { + // Checking handler instance status cancellationToken.ThrowIfCancellationRequested(); bool useSingleton = UseSingleton(descriptor); + // Returning singleton instance if (useSingleton && descriptor.SingletonInstance != null) return descriptor.SingletonInstance; + // Creating instance UpdateHandlerBase instance = GetHandlerInstanceInternal(descriptor); if (useSingleton) descriptor.TrySetInstance(instance); + // Lazy initialization execution descriptor.LazyInitialization?.Invoke(instance); return instance; } - catch + catch (Exception ex) { - Alligator.LogDebug("Failed to create instance of {0}", descriptor.ToString()); + Alligator.LogError("Failed to create instance of '{0}'", exception: ex, descriptor.ToString()); throw; } } diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index d45c233..716af16 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -41,6 +41,56 @@ namespace Telegrator return message.Text.Substring(entity.Offset, entity.Length); } + + public static bool IsCommand(this Message message, out string? command) + { + command = null; + if (message is not { Entities.Length: > 0, Text.Length: > 0 }) + return false; + + MessageEntity commandEntity = message.Entities[0]; + if (commandEntity.Type != MessageEntityType.BotCommand) + return false; + + command = message.Text.Substring(commandEntity.Offset + 1, commandEntity.Length - 1); + if (command.Contains('@')) + { + string[] split = command.Split('@'); + command = split[0]; + } + + return true; + } + + public static string[] SplitArgs(this Message message) + { + if (!message.IsCommand(out string? command)) + throw new InvalidDataException("Message does not contain a command"); + + if (message is not { Text.Length: > 0 }) + throw new ArgumentNullException("Command text cannot be null or empty"); + + if (!message.Text.Contains(' ')) + throw new MissingMemberException("Command dont contains arguments"); + + return message.Text.Split([' '], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); + } + + public static bool TrySplitArgs(this Message message, out string[]? args) + { + args = null; + if (!message.IsCommand(out string? command)) + return false; + + if (message is not { Text.Length: > 0 }) + return false; + + if (!message.Text.Contains(' ')) + return false; + + args = message.Text.Split([' '], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); + return true; + } } ///