diff --git a/Telegrator.Generators/ApiMarkdownGenerator.cs b/Telegrator.Generators/ApiMarkdownGenerator.cs index 93c25e5..9b1f7e5 100644 --- a/Telegrator.Generators/ApiMarkdownGenerator.cs +++ b/Telegrator.Generators/ApiMarkdownGenerator.cs @@ -1,10 +1,7 @@ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; -using System.Reflection.Metadata.Ecma335; using System.Text; -using System.Xml; using System.Xml.Linq; namespace Telegrator.Generators @@ -71,7 +68,7 @@ namespace Telegrator.Generators if (!string.IsNullOrWhiteSpace(typeSummary)) sourceBuilder.AppendFormat("> {0}\n\n", typeSummary); - // Writing members + // Writing fields if (type.TypeKind == TypeKind.Enum) { WriteEnumValues(sourceBuilder, type); @@ -105,7 +102,7 @@ namespace Telegrator.Generators sourceBuilder.AppendLine("**Constructors:**"); foreach (IMethodSymbol ctor in ctors) { - // Формируем строку вида ClassName(Param1, Param2) с generic-аргументами + // Formatting constructor signature string genericArgs = type.FormatGenericTypes(); string parameters = string.Join(", ", ctor.Parameters.Select(p => p.Type.GetShortName())); string signature = string.Format("{0}{1}({2})", type.Name, genericArgs, parameters); @@ -114,7 +111,9 @@ namespace Telegrator.Generators // Writing summary string? propSummary = ctor.ExtractSummary(); if (!string.IsNullOrWhiteSpace(propSummary)) - sourceBuilder.Append(" > ").Append(propSummary).AppendLine(); + sourceBuilder.Append(" > ").Append(propSummary); + + sourceBuilder.AppendLine(); } sourceBuilder.AppendLine(); @@ -122,19 +121,32 @@ namespace Telegrator.Generators private static void WriteEnumValues(StringBuilder sourceBuilder, INamedTypeSymbol type) { - var members = type.GetMembers().OfType().Where(f => f.HasConstantValue && f.DeclaredAccessibility == Accessibility.Public).ToList(); - if (members.Count == 0) + // Getting enum values + List fields = type + .GetMembers() + .OfType() + .Where(f => f.HasConstantValue && f.DeclaredAccessibility == Accessibility.Public) + .ToList(); + + // Checking for any + if (fields.Count == 0) return; + // Writing sourceBuilder.AppendLine("**Values:**"); - foreach (IFieldSymbol field in members) + foreach (IFieldSymbol field in fields) { + // Writing value sourceBuilder.Append("- `").Append(field.Name).Append("`"); + + // Writing summary string? summary = field.ExtractSummary(); if (!string.IsNullOrWhiteSpace(summary)) sourceBuilder.Append(" — ").Append(summary); + sourceBuilder.AppendLine(); } + sourceBuilder.AppendLine(); } @@ -187,9 +199,9 @@ namespace Telegrator.Generators sourceBuilder.AppendLine("**Methods:**"); foreach (IMethodSymbol method in methods) { - // Формируем generic-параметры для метода + // Formating method signature string genericArgs = method.FormatGenericTypes(); - string parameters = string.Join(", ", method.Parameters.Select(p => p.Type.GetShortName())); + string parameters = string.Join(", ", method.Parameters.Select(p => p.Type.GetShortName()).Where(p => !string.IsNullOrEmpty(p))); sourceBuilder.AppendFormat(" - `{0}{1}({2})`\n", method.Name, genericArgs, parameters); // Writing summary @@ -215,17 +227,20 @@ namespace Telegrator.Generators try { XDocument doc = XDocument.Parse(xmlDoc); - XElement? summary = doc.Root?.Element("summary"); + XElement? xSummary = doc.Root?.Element("summary"); - if (summary == null) + if (xSummary == null) return null; // Убираем лишние пробелы и переносы строк - return summary.Value.Trim().Replace("\n", " ").Replace(" ", " "); + string summary = xSummary.Value.Trim().Replace("\n", " "); + while (summary.Contains(" ")) + summary = summary.Replace(" ", " "); + + return summary; } catch { - // Игнорируем ошибки парсинга XML return null; } } diff --git a/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/Telegrator.Hosting/Polling/HostUpdateRouter.cs index c80008a..5683718 100644 --- a/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -18,7 +18,6 @@ namespace Telegrator.Hosting.Polling /// protected readonly ILogger Logger; - // Ehat a mess :/ /// public HostUpdateRouter( IHandlersProvider handlersProvider, @@ -28,7 +27,7 @@ namespace Telegrator.Hosting.Polling ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool) { Logger = logger; - ExceptionHandler = new HostExceptionHandler(logger); + ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); } /// @@ -41,21 +40,21 @@ namespace Telegrator.Hosting.Polling /// /// Default exception handler of this router /// - /// - private class HostExceptionHandler(ILogger logger) : IRouterExceptionHandler + /// + /// + /// + /// + public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) { - public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + if (exception is HandlerFaultedException handlerFaultedException) { - if (exception is HandlerFaultedException handlerFaultedException) - { - logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", - handlerFaultedException.HandlerInfo.ToString(), - handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); - return; - } - - logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString()); + Logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", + handlerFaultedException.HandlerInfo.ToString(), + handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); + return; } + + Logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString()); } } } diff --git a/Telegrator.Hosting/Telegrator.Hosting.csproj b/Telegrator.Hosting/Telegrator.Hosting.csproj index 2d4ad62..278fdef 100644 --- a/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,7 +15,7 @@ True True LICENSE - 1.0.1 + 1.0.2 diff --git a/Telegrator.Hosting/TypesExtensions.cs b/Telegrator.Hosting/TypesExtensions.cs index 3c35640..de8f8ce 100644 --- a/Telegrator.Hosting/TypesExtensions.cs +++ b/Telegrator.Hosting/TypesExtensions.cs @@ -43,7 +43,7 @@ namespace Telegrator.Hosting /// public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) { - services.AddLogging(builder => builder.AddConsole()); + services.AddLogging(builder => builder.AddConsole().AddDebug()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Telegrator/Annotations/MessageChatFilterAttributes.cs b/Telegrator/Annotations/MessageChatFilterAttributes.cs index ccb3620..70c7cc2 100644 --- a/Telegrator/Annotations/MessageChatFilterAttributes.cs +++ b/Telegrator/Annotations/MessageChatFilterAttributes.cs @@ -21,10 +21,22 @@ namespace Telegrator.Annotations /// /// Attribute for filtering messages sent in chats of a specific type. /// - /// The chat type to match - public class ChatTypeAttribute(ChatType type) - : MessageFilterAttribute(new MessageChatTypeFilter(type)) - { } + public class ChatTypeAttribute : MessageFilterAttribute + { + /// + /// Initialize new instance of to filter messages from chat from specific chats + /// + /// + public ChatTypeAttribute(ChatType type) + : base(new MessageChatTypeFilter(type)) { } + + /// + /// Initialize new instance of to filter messages from chat from specific chats (with flags) + /// + /// + public ChatTypeAttribute(ChatTypeFlags flags) + : base(new MessageChatTypeFilter(flags)) { } + } /// /// Attribute for filtering messages based on the chat title. diff --git a/Telegrator/Attributes/FilterAnnotation.cs b/Telegrator/Attributes/FilterAnnotation.cs new file mode 100644 index 0000000..cb89ff7 --- /dev/null +++ b/Telegrator/Attributes/FilterAnnotation.cs @@ -0,0 +1,49 @@ +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Telegrator.Filters; +using Telegrator.Filters.Components; + +namespace Telegrator.Attributes +{ + /// + /// Reactive way to implement a new of type + /// + /// + public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter where T : class + { + /// + public bool IsCollectible => false; + + /// + /// Initializes new instance of + /// + public FilterAnnotation() : base() + { + UpdateFilter = Filter.If(CanPass); + AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); + } + + /// + public abstract bool CanPass(FilterExecutionContext context); + } + + /// + public abstract class MessageFilterAnnotation() : FilterAnnotation() + { + /// + public override UpdateType[] AllowedTypes => [UpdateType.Message, UpdateType.EditedMessage, UpdateType.ChannelPost, UpdateType.EditedChannelPost, UpdateType.BusinessMessage, UpdateType.EditedBusinessMessage]; + + /// + public override Message? GetFilterringTarget(Update update) => update.Message; + } + + /// + public abstract class CallbackQueryFilterAnnotation() : FilterAnnotation() + { + /// + public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery]; + + /// + public override CallbackQuery? GetFilterringTarget(Update update) => update.CallbackQuery; + } +} diff --git a/Telegrator/Attributes/UpdateFilterAttribute.cs b/Telegrator/Attributes/UpdateFilterAttribute.cs index d578440..8cd145b 100644 --- a/Telegrator/Attributes/UpdateFilterAttribute.cs +++ b/Telegrator/Attributes/UpdateFilterAttribute.cs @@ -20,7 +20,17 @@ namespace Telegrator.Attributes /// /// Gets the compiled filter logic for the update target. /// - public Filter UpdateFilter { get; private set; } + public Filter UpdateFilter { get; protected set; } + + /// + /// Empty constructor for internal using + /// + internal UpdateFilterAttribute() + { + AnonymousFilter = null!; + UpdateFilter = null!; + _ = 0xBAD + 0xC0DE; + } /// /// Initializes the attribute with one or more filters for the update target. diff --git a/Telegrator/Enums.cs b/Telegrator/Enums.cs index 8fc0a98..6326246 100644 --- a/Telegrator/Enums.cs +++ b/Telegrator/Enums.cs @@ -36,4 +36,64 @@ /// Casino } + + /// + /// Flags version of + /// Type of the , from which the message or inline query was sent + /// + [Flags] + public enum ChatTypeFlags + { + /// + /// Normal one-to-one chat with a user or bot + /// + Private = 0x1, + + /// + /// Normal group chat + /// + Group = 0x2, + + /// + /// A channel + /// + Channel = 0x4, + + /// + /// A supergroup + /// + Supergroup = 0x8, + + /// + /// Value possible only in : private chat with the inline query sender + /// + Sender + } + + /// + /// Levels of debug writing + /// + [Flags] + public enum DebugLevel + { + /// + /// Write debug messages from filters execution + /// + Filters = 0x1, + + /// + /// Write debug messages from handlers providers execution + /// + Providers = 0x2, + + /// + /// Write debug messages from update router's execution + /// + Router = 0x4, + + /// + /// Write debug messages from handlers pool execution + /// + HandlersPool = 0x8 + } } diff --git a/Telegrator/Filters/Filter.cs b/Telegrator/Filters/Filter.cs index 99b486a..8417a9e 100644 --- a/Telegrator/Filters/Filter.cs +++ b/Telegrator/Filters/Filter.cs @@ -57,6 +57,12 @@ namespace Telegrator.Filters /// The filter execution context. /// True if the filter passes; otherwise, false. public abstract bool CanPass(FilterExecutionContext context); + + /// + /// Implicitly creates from function + /// + /// + public static implicit operator Filter(Func, bool> filter) => Filter.If(filter); } /// diff --git a/Telegrator/Filters/MessageChatFilters.cs b/Telegrator/Filters/MessageChatFilters.cs index 29908d8..6d0407b 100644 --- a/Telegrator/Filters/MessageChatFilters.cs +++ b/Telegrator/Filters/MessageChatFilters.cs @@ -1,5 +1,6 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.ReplyMarkups; using Telegrator.Filters.Components; namespace Telegrator.Filters @@ -54,13 +55,49 @@ namespace Telegrator.Filters /// /// Filters messages whose chat type matches the specified value. /// - public class MessageChatTypeFilter(ChatType type) : MessageChatFilter + public class MessageChatTypeFilter : MessageChatFilter { - private readonly ChatType Type = type; + private readonly ChatType? Type; + private readonly ChatTypeFlags? Flags; + + /// + /// Initialize new instance of + /// + /// + public MessageChatTypeFilter(ChatType type) + => Type = type; + + /// + /// Initialize new instance of with + /// + /// + public MessageChatTypeFilter(ChatTypeFlags type) + => Flags = type; /// protected override bool CanPassNext(FilterExecutionContext _) - => Chat.Type == Type; + { + if (Type.HasValue) + return Chat.Type == Type.Value; + + if (Flags != null) + { + ChatTypeFlags? asFlag = ToFlag(Chat.Type); + return asFlag.HasValue && Flags.Value.HasFlag(asFlag.Value); + } + + return false; + } + + private static ChatTypeFlags? ToFlag(ChatType type) => type switch + { + ChatType.Channel => ChatTypeFlags.Channel, + ChatType.Group => ChatTypeFlags.Group, + ChatType.Supergroup => ChatTypeFlags.Supergroup, + ChatType.Sender => ChatTypeFlags.Sender, + ChatType.Private => ChatTypeFlags.Private, + _ => null + }; } /// diff --git a/Telegrator/LeveledDebug.cs b/Telegrator/LeveledDebug.cs index 0d598b4..2a02515 100644 --- a/Telegrator/LeveledDebug.cs +++ b/Telegrator/LeveledDebug.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Telegram.Bot.Types.Enums; namespace Telegrator { @@ -91,31 +92,4 @@ namespace Telegrator Debug.WriteLine(message, args); } } - - /// - /// Levels of debug writing - /// - [Flags] - public enum DebugLevel - { - /// - /// Write debug messages from filters execution - /// - Filters = 0x1, - - /// - /// Write debug messages from handlers providers execution - /// - Providers = 0x2, - - /// - /// Write debug messages from update router's execution - /// - Router = 0x4, - - /// - /// Write debug messages from handlers pool execution - /// - HandlersPool = 0x8 - } } diff --git a/Telegrator/MadiatorCore/IHandlersCollection.cs b/Telegrator/MadiatorCore/IHandlersCollection.cs index d070bc0..9bfe793 100644 --- a/Telegrator/MadiatorCore/IHandlersCollection.cs +++ b/Telegrator/MadiatorCore/IHandlersCollection.cs @@ -32,11 +32,13 @@ namespace Telegrator.MadiatorCore /// The handler descriptor list for the given update type. public HandlerDescriptorList this[UpdateType updateType] { get; } + /* /// /// Collects all handlers domain-wide and returns a new . /// /// A new with all handlers collected. public IHandlersCollection CollectHandlersDomainWide(); + */ /// /// Adds a to the collection and returns the updated collection. diff --git a/Telegrator/MadiatorCore/IRouterExceptionHandler.cs b/Telegrator/MadiatorCore/IRouterExceptionHandler.cs index f421523..34f6ca8 100644 --- a/Telegrator/MadiatorCore/IRouterExceptionHandler.cs +++ b/Telegrator/MadiatorCore/IRouterExceptionHandler.cs @@ -1,5 +1,6 @@ using Telegram.Bot; using Telegram.Bot.Polling; +using Telegrator.Polling; namespace Telegrator.MadiatorCore { diff --git a/Telegrator/Polling/DefaultRouterExceptionHandler.cs b/Telegrator/Polling/DefaultRouterExceptionHandler.cs new file mode 100644 index 0000000..10033ac --- /dev/null +++ b/Telegrator/Polling/DefaultRouterExceptionHandler.cs @@ -0,0 +1,36 @@ +using Telegram.Bot; +using Telegram.Bot.Polling; +using Telegrator.MadiatorCore; + +namespace Telegrator.Polling +{ + /// + /// Delegate used to handle exception + /// + /// + /// + /// + /// + public delegate void RouterExceptionHandler(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken); + + /// + /// Realizes using function delegate + /// + /// + public sealed class DefaultRouterExceptionHandler(RouterExceptionHandler handler) : IRouterExceptionHandler + { + private readonly RouterExceptionHandler _handler = handler; + + /// + public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + { + try + { + _handler.Invoke(botClient, exception, source, cancellationToken); + } + finally + { + _ = 0xBAD + 0xC0DE; + } + }} +} diff --git a/Telegrator/Providers/HandlersCollection.cs b/Telegrator/Providers/HandlersCollection.cs index 38a4bd9..ecb0f4b 100644 --- a/Telegrator/Providers/HandlersCollection.cs +++ b/Telegrator/Providers/HandlersCollection.cs @@ -1,6 +1,5 @@ using System.Reflection; using Telegram.Bot.Types.Enums; -using Telegrator; using Telegrator.Annotations; using Telegrator.Attributes; using Telegrator.Configuration; @@ -60,25 +59,6 @@ namespace Telegrator.Providers get => InnerDictionary[updateType]; } - /// - /// - /// Collects all handlers from the entry assembly domain-wide. - /// Scans for types that implement handlers and adds them to the collection. - /// - /// This collection instance for method chaining. - /// Thrown when the entry assembly cannot be found. - public virtual IHandlersCollection CollectHandlersDomainWide() - { - Assembly? entryAssembly = Assembly.GetEntryAssembly() ?? throw new Exception(); - entryAssembly.GetExportedTypes() - .Where(type => type.GetCustomAttribute() == null) - .Where(type => type.IsHandlerRealization()) - .ForEach(type => AddHandler(type)); - - return this; - } - - /// /// /// Adds a handler descriptor to the collection. /// @@ -100,7 +80,6 @@ namespace Telegrator.Providers return this; } - /// /// /// Adds a handler type to the collection. /// @@ -112,7 +91,6 @@ namespace Telegrator.Providers return this; } - /// /// /// Adds a handler type to the collection. /// @@ -138,7 +116,6 @@ namespace Telegrator.Providers return this; } - /// /// /// Gets or creates a descriptor list for the specified update type. /// @@ -155,7 +132,6 @@ namespace Telegrator.Providers return list; } - /// /// /// Checks for intersecting command aliases and handles them according to configuration. /// diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index ffff2b1..ebcb80f 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -17,7 +17,7 @@ True True LICENSE - 1.0.1 + 1.0.2 diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index 95bcfa5..fcf1d15 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -1,7 +1,6 @@ using System.Collections; using System.Collections.ObjectModel; using System.Reflection; -using System.Runtime.CompilerServices; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Payments; @@ -166,8 +165,46 @@ namespace Telegrator /// Extension methods for handlers collections. /// Provides convenient methods for creating implicit handlers. /// - public static class HandlersCollectionExtensions + public static partial class HandlersCollectionExtensions { + private static readonly string[] skippingAssemblies = ["System", "Microsoft", "Telegrator"]; + + /// + /// Collects all handlers from the current app domain. + /// Scans for types that implement handlers and adds them to the collection. + /// + /// This collection instance for method chaining. + /// Thrown when the entry assembly cannot be found. + public static IHandlersCollection CollectHandlersDomainWide(this IHandlersCollection handlers) + { + AppDomain.CurrentDomain + .GetAssemblies() + .Where(ass => skippingAssemblies.All(skip => !ass.FullName.Contains(skip))) + .SelectMany(ass => ass.GetExportedTypes()) + .Where(type => type.GetCustomAttribute() == null) + .Where(type => type.IsHandlerRealization()) + .ForEach(type => handlers.AddHandler(type)); + + return handlers; + } + + /// + /// Collects all handlers from the calling this function assembly. + /// Scans for types that implement handlers and adds them to the collection. + /// + /// This collection instance for method chaining. + /// Thrown when the entry assembly cannot be found. + public static IHandlersCollection CollectHandlersAssemblyWide(this IHandlersCollection handlers) + { + Assembly.GetCallingAssembly() + .GetExportedTypes() + .Where(type => type.GetCustomAttribute() == null) + .Where(type => type.IsHandlerRealization()) + .ForEach(type => handlers.AddHandler(type)); + + return handlers; + } + /// /// Creates a handler builder for a specific update type. ///