diff --git a/Telegrator/Attributes/FilterAnnotation.cs b/Telegrator/Attributes/FilterAnnotation.cs index 40bd0b5..4a87767 100644 --- a/Telegrator/Attributes/FilterAnnotation.cs +++ b/Telegrator/Attributes/FilterAnnotation.cs @@ -9,7 +9,7 @@ namespace Telegrator.Attributes /// Reactive way to implement a new of type /// /// - public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter where T : class + public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter, INamedFilter where T : class { /// public virtual bool IsCollectible { get; } = false; @@ -17,6 +17,9 @@ namespace Telegrator.Attributes /// public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes(); + /// + public string Name => GetType().Name; + /// /// Initializes new instance of /// diff --git a/Telegrator/Handlers/Building/TypesExtensions.cs b/Telegrator/Handlers/Building/TypesExtensions.cs new file mode 100644 index 0000000..5e6bb5a --- /dev/null +++ b/Telegrator/Handlers/Building/TypesExtensions.cs @@ -0,0 +1,245 @@ +using Telegram.Bot.Types; +using Telegrator.Annotations.StateKeeping; +using Telegrator.Filters.Components; +using Telegrator.Handlers.Building.Components; +using Telegrator.StateKeeping; +using Telegrator.StateKeeping.Abstracts; +using Telegrator.StateKeeping.Components; + +namespace Telegrator.Handlers.Building +{ + /// + /// Extension methods for handler builders. + /// Provides convenient methods for creating handlers and setting state keepers. + /// + public static partial class HandlerBuilderExtensions + { + /// + public static TBuilder SetUpdateValidating(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetUpdateValidating(updateValidateAction); + return handlerBuilder; + } + + /// + public static TBuilder SetConcurreny(this TBuilder handlerBuilder, int concurrency) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetConcurreny(concurrency); + return handlerBuilder; + } + + /// + public static TBuilder SetPriority(this TBuilder handlerBuilder, int priority) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetPriority(priority); + return handlerBuilder; + } + + /// + public static TBuilder SetIndexer(this TBuilder handlerBuilder, int concurrency, int priority) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetIndexer(concurrency, priority); + return handlerBuilder; + } + + /// + public static TBuilder AddFilter(this TBuilder handlerBuilder, IFilter filter) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.AddFilter(filter); + return handlerBuilder; + } + + /// + public static TBuilder AddFilters(this TBuilder handlerBuilder, params IFilter[] filters) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.AddFilters(filters); + return handlerBuilder; + } + + /// + public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, TState myState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + where TKey : notnull + where TState : IEquatable + where TKeeper : StateKeeperBase, new() + { + handlerBuilder.SetStateKeeper(myState, keyResolver); + return handlerBuilder; + } + + /// + public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + where TKey : notnull + where TState : IEquatable + where TKeeper : StateKeeperBase, new() + { + handlerBuilder.SetStateKeeper(specialState, keyResolver); + return handlerBuilder; + } + + /// + /// Adds a targeted filter for a specific filter target type. + /// + /// + /// The type of the filter target. + /// + /// Function to get the filter target from an update. + /// The filter to add. + /// The builder instance. + public static TBuilder AddTargetedFilter(this TBuilder handlerBuilder, Func getFilterringTarget, IFilter filter) + where TBuilder : HandlerBuilderBase + where TFilterTarget : class + { + handlerBuilder.AddTargetedFilter(getFilterringTarget, filter); + return handlerBuilder; + } + + /// + /// Adds multiple targeted filters for a specific filter target type. + /// + /// + /// The type of the filter target. + /// + /// Function to get the filter target from an update. + /// The filters to add. + /// The builder instance. + public static TBuilder AddTargetedFilters(this TBuilder handlerBuilder, Func getFilterringTarget, params IFilter[] filters) + where TBuilder : HandlerBuilderBase + where TFilterTarget : class + { + handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); + return handlerBuilder; + } + + /// + /// Sets a numeric state keeper with a custom key resolver. + /// + /// The type of the handler builder. + /// The handler builder. + /// The numeric state value. + /// The key resolver for the state. + /// The handler builder for method chaining. + public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetStateKeeper(myState, keyResolver); + return handlerBuilder; + } + + /// + /// Sets a numeric state keeper with a special state and custom key resolver. + /// + /// The type of the handler builder. + /// The handler builder. + /// The special state value. + /// The key resolver for the state. + /// The handler builder for method chaining. + public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetStateKeeper(specialState, keyResolver); + return handlerBuilder; + } + + /// + /// Sets a numeric state keeper with the default sender ID resolver. + /// + /// The type of the handler builder. + /// The handler builder. + /// The numeric state value. + /// The handler builder for method chaining. + public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetStateKeeper(myState, new SenderIdResolver()); + return handlerBuilder; + } + + /// + /// Sets a numeric state keeper with a special state and the default sender ID resolver. + /// + /// The type of the handler builder. + /// The handler builder. + /// The special state value. + /// The handler builder for method chaining. + public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetStateKeeper(specialState, new SenderIdResolver()); + return handlerBuilder; + } + + /// + /// Sets an enum state keeper with a custom key resolver. + /// + /// The type of the handler builder. + /// The type of the enum state. + /// The handler builder. + /// The enum state value. + /// The key resolver for the state. + /// The handler builder for method chaining. + public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + where TEnum : Enum, IEquatable + { + handlerBuilder.SetStateKeeper>(myState, keyResolver); + return handlerBuilder; + } + + /// + /// Sets an enum state keeper with a special state and custom key resolver. + /// + /// The type of the handler builder. + /// The type of the enum state. + /// The handler builder. + /// The special state value. + /// The key resolver for the state. + /// The handler builder for method chaining. + public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) + where TBuilder : HandlerBuilderBase + where TEnum : Enum, IEquatable + { + handlerBuilder.SetStateKeeper>(specialState, keyResolver); + return handlerBuilder; + } + + /// + /// Sets an enum state keeper with the default sender ID resolver. + /// + /// The type of the handler builder. + /// The type of the enum state. + /// The handler builder. + /// The enum state value. + /// The handler builder for method chaining. + public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState) + where TBuilder : HandlerBuilderBase + where TEnum : Enum, IEquatable + { + handlerBuilder.SetStateKeeper>(myState, new SenderIdResolver()); + return handlerBuilder; + } + + /// + /// Sets an enum state keeper with a special state and the default sender ID resolver. + /// + /// The type of the handler builder. + /// The type of the enum state. + /// The handler builder. + /// The special state value. + /// The handler builder for method chaining. + public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState) + where TBuilder : HandlerBuilderBase + where TEnum : Enum, IEquatable + { + handlerBuilder.SetStateKeeper>(specialState, new SenderIdResolver()); + return handlerBuilder; + } + } +} diff --git a/Telegrator/Handlers/Components/FiltersFallbackReport.cs b/Telegrator/Handlers/Components/FiltersFallbackReport.cs deleted file mode 100644 index 9a0ab6b..0000000 --- a/Telegrator/Handlers/Components/FiltersFallbackReport.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Telegram.Bot.Types; -using Telegrator.Attributes.Components; -using Telegrator.Filters.Components; -using Telegrator.MadiatorCore.Descriptors; - -namespace Telegrator.Handlers.Components -{ - /// - /// Represents a report of filter fallback information for debugging and error handling. - /// Contains detailed information about which filters failed and why during handler execution. - /// - /// The handler descriptor that generated this report. - /// The filter execution context. - public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext context) - { - /// - /// Gets the handler descriptor associated with this fallback report. - /// - public HandlerDescriptor Descriptor { get; } = descriptor; - - /// - /// Gets the filter execution context that generated this report. - /// - public FilterExecutionContext Context { get; } = context; - - /// - /// Gets or sets the fallback information for the update validator filter. - /// - public FilterFallbackInfo? UpdateValidator { get; set; } - - /// - /// Gets or sets the fallback information for the state keeper validator filter. - /// - public FilterFallbackInfo? StateKeeperValidator { get; set; } - - /// - /// Gets the list of fallback information for update filters that failed. - /// - public List UpdateFilters { get; } = []; - - /// - /// Checks if the failure is due to a specific filter. - /// - /// - /// - /// - public bool Only(string name, int index = 0) - { - FilterFallbackInfo? info = UpdateFilters.SingleSafe(info => info.Failed); - if (info != null && info.Name != name) - return false; - - FilterFallbackInfo? target = UpdateFilters.ElementAtOrDefault(index); - return ReferenceEquals(target, info); - } - - /// - /// Checks if the failure is due to a specific filter. - /// - /// - /// - public bool Only(string[] names) - { - return UpdateFilters - .Where(info => info.Failed) - .Select(info => info.Name) - .SequenceEqual(names); - } - - /// - /// Checks if the failure is due to all filters except one. - /// - /// - /// - /// - public bool Except(string name, int index = 0) - { - FilterFallbackInfo? info = UpdateFilters.SingleSafe(info => !info.Failed); - if (info != null && info.Name != name) - return false; - - FilterFallbackInfo? target = UpdateFilters.ElementAtOrDefault(index); - return ReferenceEquals(target, info); - } - - /// - /// Checks if the failure is due to all filters except one. - /// - /// - /// - public bool Except(string[] names) - { - return UpdateFilters - .Where(info => !info.Failed) - .Select(info => info.Name) - .SequenceEqual(names); - } - - /// - /// Checks if the failure is due to aall attribute type, excluding one. - /// - /// The attribute type to check for. - /// The index of the filter to check (default: 0). - /// True if the failure is exclusively due to the specified attribute type; otherwise, false. - public bool ExceptAttribute(int index = 0) where T : UpdateFilterAttributeBase - => Except(nameof(T), index); - - /// - /// Checks if the failure is due to a specific attribute type, excluding other failures. - /// - /// The attribute type to check for. - /// The index of the filter to check (default: 0). - /// True if the failure is exclusively due to the specified attribute type; otherwise, false. - public bool OnlyAttribute(int index = 0) where T : UpdateFilterAttributeBase - => Only(nameof(T), index); - } - - /// - /// Contains information about a filter that failed during execution. - /// Provides details about the filter, its failure status, and any associated exception. - /// - /// The name of the filter. - /// The filter instance that failed. - /// Whether the filter failed. - /// The exception that occurred during filter execution, if any. - public class FilterFallbackInfo(string name, IFilter filter, bool failed, Exception? exception) - { - /// - /// Gets the name of the filter. - /// - public string Name { get; } = name; - - /// - /// Gets the filter instance that failed. - /// - public IFilter Filter { get; } = filter; - - /// - /// Gets a value indicating whether the filter failed. - /// - public bool Failed { get; } = failed; - - /// - /// Gets the exception that occurred during filter execution, if any. - /// - public Exception? Exception { get; } = exception; - } - - /// - /// Specifies the reason for a filter fallback. - /// - public enum FallbackReason - { - /// - /// The filter target was null. - /// - NullTarget, - - /// - /// The filter failed to pass. - /// - FailedFilter - } -} diff --git a/Telegrator/Handlers/Components/UpdateHandlerBase.cs b/Telegrator/Handlers/Components/UpdateHandlerBase.cs index e3970de..9cb79b9 100644 --- a/Telegrator/Handlers/Components/UpdateHandlerBase.cs +++ b/Telegrator/Handlers/Components/UpdateHandlerBase.cs @@ -4,6 +4,7 @@ using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegrator.Filters.Components; +using Telegrator.Handlers.Diagnostics; using Telegrator.MadiatorCore.Descriptors; namespace Telegrator.Handlers.Components diff --git a/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs b/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs new file mode 100644 index 0000000..73b874d --- /dev/null +++ b/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs @@ -0,0 +1,36 @@ +using Telegram.Bot.Types; +using Telegrator.Filters.Components; + +namespace Telegrator.Handlers.Diagnostics +{ + /// + /// Contains information about a filter that failed during execution. + /// Provides details about the filter, its failure status, and any associated exception. + /// + /// The name of the filter. + /// The filter instance that failed. + /// Whether the filter failed. + /// The exception that occurred during filter execution, if any. + public class FilterFallbackInfo(string name, IFilter filter, bool failed, Exception? exception) + { + /// + /// Gets the name of the filter. + /// + public string Name { get; } = name; + + /// + /// Gets the filter instance that failed. + /// + public IFilter Filter { get; } = filter; + + /// + /// Gets a value indicating whether the filter failed. + /// + public bool Failed { get; } = failed; + + /// + /// Gets the exception that occurred during filter execution, if any. + /// + public Exception? Exception { get; } = exception; + } +} diff --git a/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs b/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs new file mode 100644 index 0000000..8a4e3c1 --- /dev/null +++ b/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs @@ -0,0 +1,65 @@ +using Telegram.Bot.Types; +using Telegrator.Filters.Components; +using Telegrator.MadiatorCore.Descriptors; + +namespace Telegrator.Handlers.Diagnostics +{ + /// + /// Represents a report of filter fallback information for debugging and error handling. + /// Contains detailed information about which filters failed and why during handler execution. + /// + /// The handler descriptor that generated this report. + /// The filter execution context. + public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext context) + { + /// + /// Gets the handler descriptor associated with this fallback report. + /// + public HandlerDescriptor Descriptor { get; } = descriptor; + + /// + /// Gets the filter execution context that generated this report. + /// + public FilterExecutionContext Context { get; } = context; + + /// + /// Gets or sets the fallback information for the update validator filter. + /// + public FilterFallbackInfo? UpdateValidator { get; set; } + + /// + /// Gets or sets the fallback information for the state keeper validator filter. + /// + public FilterFallbackInfo? StateKeeperValidator { get; set; } + + /// + /// Gets the list of fallback information for update filters that failed. + /// + public List UpdateFilters { get; } = []; + + /// + /// Checks filter fail status by name + /// + /// + /// + public bool this[string name] => UpdateFilters.FirstOrDefault(f => f.Name == name)?.Failed ?? false; + + /// + /// Creates new instance of with default filter state as FAILED. + /// + /// + public ReportInspector AllFailed() + { + return new ReportInspector(this, false); + } + + /// + /// Creates new instance of with default filter state as PASSED. + /// + /// + public ReportInspector AllPassed() + { + return new ReportInspector(this, true); + } + } +} diff --git a/Telegrator/Handlers/Diagnostics/ReportInspector.cs b/Telegrator/Handlers/Diagnostics/ReportInspector.cs new file mode 100644 index 0000000..afcc8cc --- /dev/null +++ b/Telegrator/Handlers/Diagnostics/ReportInspector.cs @@ -0,0 +1,73 @@ +namespace Telegrator.Handlers.Diagnostics +{ + /// + /// A class builder for pattern checking of + /// + /// + /// + public sealed class ReportInspector(FiltersFallbackReport report, bool defaulState) + { + private readonly FiltersFallbackReport _report = report; + private readonly bool _defaulState = defaulState; + + private readonly List _ignore = []; + private readonly List _excepts = []; + + /// + /// Adds a filter to the exclusion list. + /// Excluded filters are compared oppositely with the default state. + /// + /// + /// + public ReportInspector Except(string name) + { + _excepts.Add(name); + return this; + } + + /// + /// Adds a filter to the ignore list. + /// Ignored filters are not checked and do not affect the final result. + /// + /// + /// + public ReportInspector Whenever(string name) + { + _ignore.Add(name); + return this; + } + + /// + /// It goes through the report and compares it with the specified filter pattern. + /// + /// + public bool Match() + { + foreach (FilterFallbackInfo info in _report.UpdateFilters) + { + if (_ignore.Contains(info.Name)) + continue; + + if (_excepts.Contains(info.Name)) + { + if (_defaulState == true && !info.Failed) + return false; + + if (_defaulState == false && info.Failed) + return false; + } + + if (!info.Failed != _defaulState) + return false; + } + + return true; + } + + /// + /// Casts inspector by executing + /// + /// + public static implicit operator bool(ReportInspector inspector) => inspector.Match(); + } +} diff --git a/Telegrator/Handlers/Diagnostics/TypesExtensions.cs b/Telegrator/Handlers/Diagnostics/TypesExtensions.cs new file mode 100644 index 0000000..eb6ee0e --- /dev/null +++ b/Telegrator/Handlers/Diagnostics/TypesExtensions.cs @@ -0,0 +1,18 @@ +using Telegrator.Attributes.Components; + +namespace Telegrator.Handlers.Diagnostics +{ + /// + /// Provides extension methods for + /// + public static partial class ReportInspectorExtensions + { + /// + public static ReportInspector Whenever(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase + => inspector.Whenever(nameof(TAttribute)); + + /// + public static ReportInspector Except(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase + => inspector.Except(nameof(TAttribute)); + } +} diff --git a/Telegrator/Handlers/TypesExtensions.cs b/Telegrator/Handlers/TypesExtensions.cs new file mode 100644 index 0000000..9d1ba9f --- /dev/null +++ b/Telegrator/Handlers/TypesExtensions.cs @@ -0,0 +1,284 @@ +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.InlineQueryResults; +using Telegram.Bot.Types.ReplyMarkups; + +namespace Telegrator.Handlers +{ + /// + /// Provides usefull helper methods for abstract handler containers + /// + public static class AbstractHandlerContainerExtensions + { + /// + /// Changes bot's reaction to message + /// + /// + /// + /// + /// + /// + public static async Task React( + this IAbstractHandlerContainer container, + ReactionType reaction, + bool isBig = false, + CancellationToken cancellationToken = default) + => await container.Client.SetMessageReaction( + container.ActualUpdate.Chat, + container.ActualUpdate.Id, + [reaction], isBig, cancellationToken); + + /// + /// Changes bot's reaction to message + /// + /// + /// + /// + /// + /// + public static async Task React( + this IAbstractHandlerContainer container, + IEnumerable reactions, + bool isBig = false, + CancellationToken cancellationToken = default) + => await container.Client.SetMessageReaction( + container.ActualUpdate.Chat, + container.ActualUpdate.Id, + reactions, isBig, cancellationToken); + + /// + /// Sends a reply message to the current message. + /// + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + public static async Task Reply( + this IAbstractHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await container.Client.SendMessage( + container.ActualUpdate.Chat, text, parseMode, container.ActualUpdate, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Sends a response message to the current chat. + /// + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply parameters for the message. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + public static async Task Responce( + this IAbstractHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await container.Client.SendMessage( + container.ActualUpdate.Chat, text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Responnces to message that this CallbackQuery was originated from + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task Responce( + this IAbstractHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + { + CallbackQuery query = container.ActualUpdate; + if (query.Message == null) + throw new Exception("Callback origin message not found!"); + + return await container.Client.SendMessage( + query.Message.Chat, text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + } + + /// + /// Edits message text that this CallbackQuery was originated from + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task EditMessage( + this IAbstractHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + InlineKeyboardMarkup? replyMarkup = null, + IEnumerable? entities = null, + LinkPreviewOptions? linkPreviewOptions = null, + CancellationToken cancellationToken = default) + { + CallbackQuery query = container.ActualUpdate; + if (query.Message == null) + throw new Exception("Callback origin message not found!"); + + return await container.Client.EditMessageText( + query.Message.Chat, + query.Message.MessageId, + text: text, + parseMode: parseMode, + replyMarkup: replyMarkup, + entities: entities, + linkPreviewOptions: linkPreviewOptions, + cancellationToken: cancellationToken); + } + + /// + /// Use this method to send answers to callback queries sent from inline keyboards. + /// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert + /// + /// + /// Alternatively, the user can be redirected to the specified Game URL. + /// For this option to work, you must first create a game for your bot via @BotFather and accept the terms. + /// Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task AnswerCallbackQuery( + this IAbstractHandlerContainer container, + string? text = null, + bool showAlert = false, + string? url = null, + int cacheTime = 0, + CancellationToken cancellationToken = default) + => await container.Client.AnswerCallbackQuery( + callbackQueryId: container.ActualUpdate.Id, + text: text, + showAlert: showAlert, + url: url, + cacheTime: cacheTime, + cancellationToken: cancellationToken); + + /// + /// Answers inline query + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task AnswerInlineQuery( + this IAbstractHandlerContainer container, + IEnumerable results, + int? cacheTime = null, + bool isPersonal = false, + string? nextOffset = null, + InlineQueryResultsButton? button = null, + CancellationToken cancellationToken = default) + { + string id = container.ActualUpdate.Id; + await container.Client.AnswerInlineQuery(id, results.Take(50), cacheTime, isPersonal, nextOffset, button, cancellationToken); + } + } +} diff --git a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs index ff9cffe..9da76af 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs @@ -1,6 +1,6 @@ using Telegram.Bot.Types; using Telegrator.Filters.Components; -using Telegrator.Handlers.Components; +using Telegrator.Handlers.Diagnostics; using Telegrator.Logging; namespace Telegrator.MadiatorCore.Descriptors diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index e6088a6..5091f0e 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -6,6 +6,7 @@ using Telegram.Bot.Types.Enums; using Telegrator.Configuration; using Telegrator.Filters.Components; using Telegrator.Handlers.Components; +using Telegrator.Handlers.Diagnostics; using Telegrator.Logging; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; diff --git a/Telegrator/Result.cs b/Telegrator/Result.cs index 98d9be1..86846b0 100644 --- a/Telegrator/Result.cs +++ b/Telegrator/Result.cs @@ -3,6 +3,7 @@ using Telegrator.Aspects; using Telegrator.Handlers.Components; using Telegrator.Filters.Components; using Telegrator.MadiatorCore; +using Telegrator.Handlers.Diagnostics; namespace Telegrator { diff --git a/Telegrator/SimpleTypesExtensions.cs b/Telegrator/SimpleTypesExtensions.cs new file mode 100644 index 0000000..06374c9 --- /dev/null +++ b/Telegrator/SimpleTypesExtensions.cs @@ -0,0 +1,347 @@ +using System.Collections.ObjectModel; +using System.Reflection; +using Telegrator.Filters.Components; +using Telegrator.Handlers.Components; +using Telegrator.Providers; + +namespace Telegrator +{ + /// + /// Provides extension methods for working with collections. + /// + public static partial class ColletionsExtensions + { + /// + /// Creates a from an + /// according to a specified key selector function. + /// + /// + /// + /// + /// + /// + public static ReadOnlyDictionary ToReadOnlyDictionary(this IEnumerable source, Func keySelector) where TKey : notnull + { + Dictionary dictionary = source.ToDictionary(keySelector); + return new ReadOnlyDictionary(dictionary); + } + + /// + /// Enumerates objects in a and executes an on each one + /// + /// + /// + /// + /// + public static IEnumerable ForEach(this IEnumerable source, Action action) + { + foreach (TValue value in source) + action.Invoke(value); + + return source; + } + + /// + /// Sets the value of a key in a dictionary, or if the key does not exist, adds it + /// + /// + /// + /// + /// + /// + public static void Set(this IDictionary source, TKey key, TValue value) + { + if (source.ContainsKey(key)) + source[key] = value; + else + source.Add(key, value); + } + + /// + /// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value. + /// + /// + /// + /// + /// + /// + /// + public static void Set(this IDictionary source, TKey key, TValue value, TValue defaultValue) + { + if (source.ContainsKey(key)) + source[key] = value; + else + source.Add(key, defaultValue); + } + + /// + /// Return the random object from + /// + /// + /// + /// + public static TSource Random(this IEnumerable source) + => source.Random(new Random()); + + /// + /// Return the random object from + /// + /// + /// + /// + /// + public static TSource Random(this IEnumerable source, Random random) + => source.ElementAt(random.Next(0, source.Count() - 1)); + + /// + /// Adds a range of elements to collection if they dont already exist using default equality comparer + /// + /// + /// + /// + public static void UnionAdd(this IList list, params IEnumerable elements) + { + foreach (TSource item in elements) + { + if (!list.Contains(item, EqualityComparer.Default)) + list.Add(item); + } + } + + /// + /// Return index of first element that satisfies the condition + /// + /// + /// + /// + /// + public static int IndexOf(this IEnumerable source, Func predicate) + { + int index = 0; + foreach (T item in source) + { + if (predicate.Invoke(item)) + return index; + + index++; + } + + return -1; + } + + /// + /// Returns an enumerable that repeats the item multiple times. + /// + /// + /// + /// + /// + public static IEnumerable Repeat(this T item, int times) + => Enumerable.Range(0, times).Select(_ => item); + + /// + /// Returns the only element of a sequence, or a default value if the sequence is empty. + /// This method returns default if there is more than one element in the sequence. + /// + /// + /// + /// + public static T? SingleSafe(this IEnumerable source) + => source.Count() == 1 ? source.ElementAt(0) : default; + + /// + /// Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. + /// This method return default if more than one element satisfies the condition. + /// + /// + /// + /// + /// + public static T? SingleSafe(this IEnumerable source, Func predicate) + { + source = source.Where(predicate); + return source.Count() == 1 ? source.ElementAt(0) : default; + } + } + + /// + /// Provides extension methods for reflection and type inspection. + /// + public static partial class ReflectionExtensions + { + /// + /// Checks if a type implements the interface. + /// + /// The type to check. + /// True if the type implements ICustomDescriptorsProvider; otherwise, false. + public static bool IsCustomDescriptorsProvider(this Type type) + => type.GetInterface(nameof(ICustomDescriptorsProvider)) != null; + + /// + /// Checks if is a + /// + /// + /// + public static bool IsFilterType(this Type type) + => type.IsAssignableToGenericType(typeof(IFilter<>)); + + /// + /// Checks if is a descendant of class + /// + /// + /// + public static bool IsHandlerAbstract(this Type type) + => type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type); + + /// + /// Checks if is an implementation of class or its descendants + /// + /// + /// + public static bool IsHandlerRealization(this Type type) + => !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type); + + /// + /// Checks if has a parameterless constructor + /// + /// + /// + public static bool HasParameterlessCtor(this Type type) + => type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0); + + /// + /// Checks is has public properties + /// + /// + /// + public static bool HasPublicProperties(this Type type) + => type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any(); + + /// + /// Determines whether an instance of a specified type can be assigned to an instance of the current type + /// + /// + /// + /// + public static bool IsAssignableToGenericType(this Type givenType, Type genericType) + { + if (givenType.GetInterfaces().Any(inter => inter.IsGenericType && inter.GetGenericTypeDefinition() == genericType)) + return true; + + if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + return true; + + if (givenType.BaseType == null) + return false; + + return givenType.BaseType.IsAssignableToGenericType(genericType); + } + } + + /// + /// Provides extension methods for string manipulation. + /// + public static partial class StringExtensions + { + /// + /// Slices a string into a array of substrings of fixed + /// + /// + /// + /// + public static IEnumerable SliceBy(this string source, int length) + { + for (int start = 0; start < source.Length; start += length + 1) + { + int tillEnd = source.Length - start; + int toSlice = tillEnd < length + 1 ? tillEnd : length + 1; + + ReadOnlySpan chunk = source.AsSpan().Slice(start, toSlice); + yield return chunk.ToString(); + } + } + + /// + /// Return new string with first found letter set to upper case + /// + /// + /// + public static string FirstLetterToUpper(this string target) + { + char[] chars = target.ToCharArray(); + int index = chars.IndexOf(char.IsLetter); + chars[index] = char.ToUpper(chars[index]); + return new string(chars); + } + + /// + /// Return new string with first found letter set to lower case + /// + /// + /// + public static string FirstLetterToLower(this string target) + { + char[] chars = target.ToCharArray(); + int index = chars.IndexOf(char.IsLetter); + chars[index] = char.ToLower(chars[index]); + return new string(chars); + } + + /// + /// Checks if string contains a 'word'. + /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. + /// + /// + /// + /// + /// + /// + public static bool ContainsWord(this string source, string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) + { + int index = source.IndexOf(word, startIndex, comparison); + if (index == -1) + return false; + + if (index > 0) + { + char prev = source[index - 1]; + if (char.IsLetter(prev)) + return false; + } + + if (index + word.Length < source.Length) + { + char post = source[index + word.Length]; + if (char.IsLetter(post)) + return false; + } + + return true; + } + } + + /// + /// Contains extension method for number types + /// + public static class NumbersExtensions + { + /// + /// Check if int value has int flag using bit compare + /// + /// + /// + /// + public static bool HasFlag(this int value, int flag) + => (value & flag) == flag; + + /// + /// Check if int value has enum flag using bit compare + /// + /// + /// + /// + /// + public static bool HasFlag(this int value, T flag) where T : Enum + => value.HasFlag(Convert.ToInt32(flag)); + } +} diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index 2d91ab6..06c4fa0 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -1,17 +1,9 @@ -using System; -using System.Collections.ObjectModel; -using System.Reflection; -using Telegram.Bot; +using System.Reflection; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegram.Bot.Types.InlineQueryResults; using Telegram.Bot.Types.Payments; -using Telegram.Bot.Types.ReplyMarkups; using Telegrator.Annotations; -using Telegrator.Annotations.StateKeeping; using Telegrator.Attributes; -using Telegrator.Filters.Components; -using Telegrator.Handlers; using Telegrator.Handlers.Building; using Telegrator.Handlers.Building.Components; using Telegrator.Handlers.Components; @@ -20,8 +12,6 @@ using Telegrator.MadiatorCore.Descriptors; using Telegrator.Providers; using Telegrator.StateKeeping; using Telegrator.StateKeeping.Abstracts; -using Telegrator.StateKeeping.Components; -using static System.Net.Mime.MediaTypeNames; namespace Telegrator { @@ -171,282 +161,6 @@ namespace Telegrator => StateKeeperAttribute.Shared; } - /// - /// Provides usefull helper methods for abstract handler containers - /// - public static class AbstractHandlerContainerExtensions - { - /// - /// Changes bot's reaction to message - /// - /// - /// - /// - /// - /// - public static async Task React( - this IAbstractHandlerContainer container, - ReactionType reaction, - bool isBig = false, - CancellationToken cancellationToken = default) - => await container.Client.SetMessageReaction( - container.ActualUpdate.Chat, - container.ActualUpdate.Id, - [reaction], isBig, cancellationToken); - - /// - /// Changes bot's reaction to message - /// - /// - /// - /// - /// - /// - public static async Task React( - this IAbstractHandlerContainer container, - IEnumerable reactions, - bool isBig = false, - CancellationToken cancellationToken = default) - => await container.Client.SetMessageReaction( - container.ActualUpdate.Chat, - container.ActualUpdate.Id, - reactions, isBig, cancellationToken); - - /// - /// Sends a reply message to the current message. - /// - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - public static async Task Reply( - this IAbstractHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await container.Client.SendMessage( - container.ActualUpdate.Chat, text, parseMode, container.ActualUpdate, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - - /// - /// Sends a response message to the current chat. - /// - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply parameters for the message. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - public static async Task Responce( - this IAbstractHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await container.Client.SendMessage( - container.ActualUpdate.Chat, text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - - /// - /// Responnces to message that this CallbackQuery was originated from - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task Responce( - this IAbstractHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - { - CallbackQuery query = container.ActualUpdate; - if (query.Message == null) - throw new Exception("Callback origin message not found!"); - - return await container.Client.SendMessage( - query.Message.Chat, text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - } - - /// - /// Edits message text that this CallbackQuery was originated from - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task EditMessage( - this IAbstractHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - InlineKeyboardMarkup? replyMarkup = null, - IEnumerable? entities = null, - LinkPreviewOptions? linkPreviewOptions = null, - CancellationToken cancellationToken = default) - { - CallbackQuery query = container.ActualUpdate; - if (query.Message == null) - throw new Exception("Callback origin message not found!"); - - return await container.Client.EditMessageText( - query.Message.Chat, - query.Message.MessageId, - text: text, - parseMode: parseMode, - replyMarkup: replyMarkup, - entities: entities, - linkPreviewOptions: linkPreviewOptions, - cancellationToken: cancellationToken); - } - - /// - /// Use this method to send answers to callback queries sent from inline keyboards. - /// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert - /// - /// - /// Alternatively, the user can be redirected to the specified Game URL. - /// For this option to work, you must first create a game for your bot via @BotFather and accept the terms. - /// Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task AnswerCallbackQuery( - this IAbstractHandlerContainer container, - string? text = null, - bool showAlert = false, - string? url = null, - int cacheTime = 0, - CancellationToken cancellationToken = default) - => await container.Client.AnswerCallbackQuery( - callbackQueryId: container.ActualUpdate.Id, - text: text, - showAlert: showAlert, - url: url, - cacheTime: cacheTime, - cancellationToken: cancellationToken); - - /// - /// Answers inline query - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task AnswerInlineQuery( - this IAbstractHandlerContainer container, - IEnumerable results, - int? cacheTime = null, - bool isPersonal = false, - string? nextOffset = null, - InlineQueryResultsButton? button = null, - CancellationToken cancellationToken = default) - { - string id = container.ActualUpdate.Id; - await container.Client.AnswerInlineQuery(id, results.Take(50), cacheTime, isPersonal, nextOffset, button, cancellationToken); - } - } - /// /// Extensions methods for Awaiter Handler Builders /// @@ -673,620 +387,6 @@ namespace Telegrator } } - /// - /// Extension methods for handler builders. - /// Provides convenient methods for creating handlers and setting state keepers. - /// - public static partial class HandlerBuilderExtensions - { - /// - public static TBuilder SetUpdateValidating(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetUpdateValidating(updateValidateAction); - return handlerBuilder; - } - - /// - public static TBuilder SetConcurreny(this TBuilder handlerBuilder, int concurrency) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetConcurreny(concurrency); - return handlerBuilder; - } - - /// - public static TBuilder SetPriority(this TBuilder handlerBuilder, int priority) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetPriority(priority); - return handlerBuilder; - } - - /// - public static TBuilder SetIndexer(this TBuilder handlerBuilder, int concurrency, int priority) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetIndexer(concurrency, priority); - return handlerBuilder; - } - - /// - public static TBuilder AddFilter(this TBuilder handlerBuilder, IFilter filter) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.AddFilter(filter); - return handlerBuilder; - } - - /// - public static TBuilder AddFilters(this TBuilder handlerBuilder, params IFilter[] filters) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.AddFilters(filters); - return handlerBuilder; - } - - /// - public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, TState myState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() - { - handlerBuilder.SetStateKeeper(myState, keyResolver); - return handlerBuilder; - } - - /// - public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() - { - handlerBuilder.SetStateKeeper(specialState, keyResolver); - return handlerBuilder; - } - - /// - /// Adds a targeted filter for a specific filter target type. - /// - /// - /// The type of the filter target. - /// - /// Function to get the filter target from an update. - /// The filter to add. - /// The builder instance. - public static TBuilder AddTargetedFilter(this TBuilder handlerBuilder, Func getFilterringTarget, IFilter filter) - where TBuilder : HandlerBuilderBase - where TFilterTarget : class - { - handlerBuilder.AddTargetedFilter(getFilterringTarget, filter); - return handlerBuilder; - } - - /// - /// Adds multiple targeted filters for a specific filter target type. - /// - /// - /// The type of the filter target. - /// - /// Function to get the filter target from an update. - /// The filters to add. - /// The builder instance. - public static TBuilder AddTargetedFilters(this TBuilder handlerBuilder, Func getFilterringTarget, params IFilter[] filters) - where TBuilder : HandlerBuilderBase - where TFilterTarget : class - { - handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with a custom key resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The numeric state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(myState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with a special state and custom key resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The special state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(specialState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with the default sender ID resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The numeric state value. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(myState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with a special state and the default sender ID resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The special state value. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(specialState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a custom key resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The enum state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(myState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a special state and custom key resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The special state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(specialState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with the default sender ID resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The enum state value. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(myState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a special state and the default sender ID resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The special state value. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(specialState, new SenderIdResolver()); - return handlerBuilder; - } - } - - /// - /// Provides extension methods for working with collections. - /// - public static partial class ColletionsExtensions - { - /// - /// Creates a from an - /// according to a specified key selector function. - /// - /// - /// - /// - /// - /// - public static ReadOnlyDictionary ToReadOnlyDictionary(this IEnumerable source, Func keySelector) where TKey : notnull - { - Dictionary dictionary = source.ToDictionary(keySelector); - return new ReadOnlyDictionary(dictionary); - } - - /// - /// Enumerates objects in a and executes an on each one - /// - /// - /// - /// - /// - public static IEnumerable ForEach(this IEnumerable source, Action action) - { - foreach (TValue value in source) - action.Invoke(value); - - return source; - } - - /// - /// Sets the value of a key in a dictionary, or if the key does not exist, adds it - /// - /// - /// - /// - /// - /// - public static void Set(this IDictionary source, TKey key, TValue value) - { - if (source.ContainsKey(key)) - source[key] = value; - else - source.Add(key, value); - } - - /// - /// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value. - /// - /// - /// - /// - /// - /// - /// - public static void Set(this IDictionary source, TKey key, TValue value, TValue defaultValue) - { - if (source.ContainsKey(key)) - source[key] = value; - else - source.Add(key, defaultValue); - } - - /// - /// Return the random object from - /// - /// - /// - /// - public static TSource Random(this IEnumerable source) - => source.Random(new Random()); - - /// - /// Return the random object from - /// - /// - /// - /// - /// - public static TSource Random(this IEnumerable source, Random random) - => source.ElementAt(random.Next(0, source.Count() - 1)); - - /// - /// Adds a range of elements to collection if they dont already exist using default equality comparer - /// - /// - /// - /// - public static void UnionAdd(this IList list, params IEnumerable elements) - { - foreach (TSource item in elements) - { - if (!list.Contains(item, EqualityComparer.Default)) - list.Add(item); - } - } - - /// - /// Return index of first element that satisfies the condition - /// - /// - /// - /// - /// - public static int IndexOf(this IEnumerable source, Func predicate) - { - int index = 0; - foreach (T item in source) - { - if (predicate.Invoke(item)) - return index; - - index++; - } - - return -1; - } - - /// - /// Returns an enumerable that repeats the item multiple times. - /// - /// - /// - /// - /// - public static IEnumerable Repeat(this T item, int times) - => Enumerable.Range(0, times).Select(_ => item); - - /// - /// Returns the only element of a sequence, or a default value if the sequence is empty. - /// This method returns default if there is more than one element in the sequence. - /// - /// - /// - /// - public static T? SingleSafe(this IEnumerable source) - => source.Count() == 1 ? source.ElementAt(0) : default; - - /// - /// Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. - /// This method return default if more than one element satisfies the condition. - /// - /// - /// - /// - /// - public static T? SingleSafe(this IEnumerable source, Func predicate) - { - source = source.Where(predicate); - return source.Count() == 1 ? source.ElementAt(0) : default; - } - } - - /// - /// Provides extension methods for reflection and type inspection. - /// - public static partial class ReflectionExtensions - { - /// - /// Checks if a type implements the interface. - /// - /// The type to check. - /// True if the type implements ICustomDescriptorsProvider; otherwise, false. - public static bool IsCustomDescriptorsProvider(this Type type) - => type.GetInterface(nameof(ICustomDescriptorsProvider)) != null; - - /// - /// Checks if is a - /// - /// - /// - public static bool IsFilterType(this Type type) - => type.IsAssignableToGenericType(typeof(IFilter<>)); - - /// - /// Checks if is a descendant of class - /// - /// - /// - public static bool IsHandlerAbstract(this Type type) - => type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type); - - /// - /// Checks if is an implementation of class or its descendants - /// - /// - /// - public static bool IsHandlerRealization(this Type type) - => !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type); - - /// - /// Checks if has a parameterless constructor - /// - /// - /// - public static bool HasParameterlessCtor(this Type type) - => type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0); - - /* - /// - /// Invokes a "" method of an object - /// - /// - /// - /// - /// - public static object? InvokeMethod(this object obj, string methodName, params object[]? args) - => obj.GetType().GetMethod(methodName, BindAll).InvokeMethod(obj, args); - - /// - /// Invokes a method of - /// - /// - /// - /// - /// - public static object? InvokeMethod(this MethodInfo methodInfo, object obj, params object[]? args) - => methodInfo.Invoke(obj, args); - - /// - /// Invokes a static method of - /// - /// - /// - /// - public static object? InvokeStaticMethod(this MethodInfo methodInfo, params object[]? parameters) - => methodInfo.Invoke(null, parameters); - - /// - /// Invokes a static "" method of an object - /// - /// - /// - /// - /// - /// - public static T? InvokeStaticMethod(this object obj, string methodName, params object[]? parameters) - => (T?)obj.GetType().GetMethod(methodName, BindAll).InvokeStaticMethod(parameters); - - /// - /// Invokes a generic method of with generic types in - /// - /// - /// - /// - /// - /// - public static object InvokeGenericMethod(this MethodInfo methodInfo, object obj, Type[] genericParameters, params object[]? parameters) - => methodInfo.MakeGenericMethod(genericParameters).Invoke(obj, parameters); - - /// - /// Invokes a generic method with generic types in - /// - /// - /// - /// - /// - /// - /// - public static T? InvokeGenericMethod(this object obj, string methodName, Type[] genericParameters, params object[]? parameters) - => (T?)obj.GetType().GetMethod(methodName).InvokeGenericMethod(obj, genericParameters, parameters); - */ - - /// - /// Checks is has public properties - /// - /// - /// - public static bool HasPublicProperties(this Type type) - => type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any(); - - /// - /// Determines whether an instance of a specified type can be assigned to an instance of the current type - /// - /// - /// - /// - public static bool IsAssignableToGenericType(this Type givenType, Type genericType) - { - if (givenType.GetInterfaces().Any(inter => inter.IsGenericType && inter.GetGenericTypeDefinition() == genericType)) - return true; - - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) - return true; - - if (givenType.BaseType == null) - return false; - - return givenType.BaseType.IsAssignableToGenericType(genericType); - } - } - - /// - /// Provides extension methods for string manipulation. - /// - public static partial class StringExtensions - { - /// - /// Slices a string into a array of substrings of fixed - /// - /// - /// - /// - public static IEnumerable SliceBy(this string source, int length) - { - for (int start = 0; start < source.Length; start += length + 1) - { - int tillEnd = source.Length - start; - int toSlice = tillEnd < length + 1 ? tillEnd : length + 1; - - ReadOnlySpan chunk = source.AsSpan().Slice(start, toSlice); - yield return chunk.ToString(); - } - } - - /// - /// Return new string with first found letter set to upper case - /// - /// - /// - public static string FirstLetterToUpper(this string target) - { - char[] chars = target.ToCharArray(); - int index = chars.IndexOf(char.IsLetter); - chars[index] = char.ToUpper(chars[index]); - return new string(chars); - } - - /// - /// Return new string with first found letter set to lower case - /// - /// - /// - public static string FirstLetterToLower(this string target) - { - char[] chars = target.ToCharArray(); - int index = chars.IndexOf(char.IsLetter); - chars[index] = char.ToLower(chars[index]); - return new string(chars); - } - - /// - /// Checks if string contains a 'word'. - /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. - /// - /// - /// - /// - /// - /// - public static bool ContainsWord(this string source, string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) - { - int index = source.IndexOf(word, startIndex, comparison); - if (index == -1) - return false; - - if (index > 0) - { - char prev = source[index - 1]; - if (char.IsLetter(prev)) - return false; - } - - if (index + word.Length < source.Length) - { - char post = source[index + word.Length]; - if (char.IsLetter(post)) - return false; - } - - return true; - } - } - /// /// Provides extension methods for working with Telegram Update objects. /// @@ -1509,29 +609,4 @@ namespace Telegrator }; } } - - /// - /// Contains extension method for number types - /// - public static class NumbersExtensions - { - /// - /// Check if int value has int flag using bit compare - /// - /// - /// - /// - public static bool HasFlag(this int value, int flag) - => (value & flag) == flag; - - /// - /// Check if int value has enum flag using bit compare - /// - /// - /// - /// - /// - public static bool HasFlag(this int value, T flag) where T : Enum - => value.HasFlag(Convert.ToInt32(flag)); - } }