* Removed default filters comparison methods from 'FiltersFallbackReport'

* Added 'ReportInspector' for patterned filters diagnostic
* Moved some extension methods to corresponding namespaces and separated simple type extensions from complex types extensions
This commit is contained in:
2025-08-25 02:46:43 +04:00
parent 64950b413c
commit 70ee6eb9c1
14 changed files with 1077 additions and 1092 deletions
+4 -1
View File
@@ -9,7 +9,7 @@ namespace Telegrator.Attributes
/// Reactive way to implement a new <see cref="UpdateFilterAttribute{T}"/> of type <typeparamref name="T"/> /// Reactive way to implement a new <see cref="UpdateFilterAttribute{T}"/> of type <typeparamref name="T"/>
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public abstract class FilterAnnotation<T> : UpdateFilterAttribute<T>, IFilter<T> where T : class public abstract class FilterAnnotation<T> : UpdateFilterAttribute<T>, IFilter<T>, INamedFilter where T : class
{ {
/// <inheritdoc/> /// <inheritdoc/>
public virtual bool IsCollectible { get; } = false; public virtual bool IsCollectible { get; } = false;
@@ -17,6 +17,9 @@ namespace Telegrator.Attributes
/// <inheritdoc/> /// <inheritdoc/>
public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes(); public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes();
/// <inheritdoc/>
public string Name => GetType().Name;
/// <summary> /// <summary>
/// Initializes new instance of <see cref="FilterAnnotation{T}"/> /// Initializes new instance of <see cref="FilterAnnotation{T}"/>
/// </summary> /// </summary>
@@ -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
{
/// <summary>
/// Extension methods for handler builders.
/// Provides convenient methods for creating handlers and setting state keepers.
/// </summary>
public static partial class HandlerBuilderExtensions
{
/// <inheritdoc cref="HandlerBuilderBase.SetUpdateValidating(UpdateValidateAction)"/>
public static TBuilder SetUpdateValidating<TBuilder>(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetUpdateValidating(updateValidateAction);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetConcurreny(int)"/>
public static TBuilder SetConcurreny<TBuilder>(this TBuilder handlerBuilder, int concurrency)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetConcurreny(concurrency);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetPriority(int)"/>
public static TBuilder SetPriority<TBuilder>(this TBuilder handlerBuilder, int priority)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetPriority(priority);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetIndexer(int, int)"/>
public static TBuilder SetIndexer<TBuilder>(this TBuilder handlerBuilder, int concurrency, int priority)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetIndexer(concurrency, priority);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.AddFilter(IFilter{Update})"/>
public static TBuilder AddFilter<TBuilder>(this TBuilder handlerBuilder, IFilter<Update> filter)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.AddFilter(filter);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.AddFilters(IFilter{Update}[])"/>
public static TBuilder AddFilters<TBuilder>(this TBuilder handlerBuilder, params IFilter<Update>[] filters)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.AddFilters(filters);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(TState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, TState myState, IStateKeyResolver<TKey> keyResolver)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(SpecialState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TBuilder"></typeparam>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="handlerBuilder"></param>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public static TBuilder AddTargetedFilter<TBuilder, TFilterTarget>(this TBuilder handlerBuilder, Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter)
where TBuilder : HandlerBuilderBase
where TFilterTarget : class
{
handlerBuilder.AddTargetedFilter(getFilterringTarget, filter);
return handlerBuilder;
}
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TBuilder"></typeparam>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="handlerBuilder"></param>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public static TBuilder AddTargetedFilters<TBuilder, TFilterTarget>(this TBuilder handlerBuilder, Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters)
where TBuilder : HandlerBuilderBase
where TFilterTarget : class
{
handlerBuilder.AddTargetedFilters(getFilterringTarget, filters);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, new SenderIdResolver());
return handlerBuilder;
}
}
}
@@ -1,164 +0,0 @@
using Telegram.Bot.Types;
using Telegrator.Attributes.Components;
using Telegrator.Filters.Components;
using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Handlers.Components
{
/// <summary>
/// Represents a report of filter fallback information for debugging and error handling.
/// Contains detailed information about which filters failed and why during handler execution.
/// </summary>
/// <param name="descriptor">The handler descriptor that generated this report.</param>
/// <param name="context">The filter execution context.</param>
public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext<Update> context)
{
/// <summary>
/// Gets the handler descriptor associated with this fallback report.
/// </summary>
public HandlerDescriptor Descriptor { get; } = descriptor;
/// <summary>
/// Gets the filter execution context that generated this report.
/// </summary>
public FilterExecutionContext<Update> Context { get; } = context;
/// <summary>
/// Gets or sets the fallback information for the update validator filter.
/// </summary>
public FilterFallbackInfo? UpdateValidator { get; set; }
/// <summary>
/// Gets or sets the fallback information for the state keeper validator filter.
/// </summary>
public FilterFallbackInfo? StateKeeperValidator { get; set; }
/// <summary>
/// Gets the list of fallback information for update filters that failed.
/// </summary>
public List<FilterFallbackInfo> UpdateFilters { get; } = [];
/// <summary>
/// Checks if the failure is due to a specific filter.
/// </summary>
/// <param name="index"></param>
/// <param name="name"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Checks if the failure is due to a specific filter.
/// </summary>
/// <param name="names"></param>
/// <returns></returns>
public bool Only(string[] names)
{
return UpdateFilters
.Where(info => info.Failed)
.Select(info => info.Name)
.SequenceEqual(names);
}
/// <summary>
/// Checks if the failure is due to all filters except one.
/// </summary>
/// <param name="index"></param>
/// <param name="name"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Checks if the failure is due to all filters except one.
/// </summary>
/// <param name="names"></param>
/// <returns></returns>
public bool Except(string[] names)
{
return UpdateFilters
.Where(info => !info.Failed)
.Select(info => info.Name)
.SequenceEqual(names);
}
/// <summary>
/// Checks if the failure is due to aall attribute type, excluding one.
/// </summary>
/// <typeparam name="T">The attribute type to check for.</typeparam>
/// <param name="index">The index of the filter to check (default: 0).</param>
/// <returns>True if the failure is exclusively due to the specified attribute type; otherwise, false.</returns>
public bool ExceptAttribute<T>(int index = 0) where T : UpdateFilterAttributeBase
=> Except(nameof(T), index);
/// <summary>
/// Checks if the failure is due to a specific attribute type, excluding other failures.
/// </summary>
/// <typeparam name="T">The attribute type to check for.</typeparam>
/// <param name="index">The index of the filter to check (default: 0).</param>
/// <returns>True if the failure is exclusively due to the specified attribute type; otherwise, false.</returns>
public bool OnlyAttribute<T>(int index = 0) where T : UpdateFilterAttributeBase
=> Only(nameof(T), index);
}
/// <summary>
/// Contains information about a filter that failed during execution.
/// Provides details about the filter, its failure status, and any associated exception.
/// </summary>
/// <param name="name">The name of the filter.</param>
/// <param name="filter">The filter instance that failed.</param>
/// <param name="failed">Whether the filter failed.</param>
/// <param name="exception">The exception that occurred during filter execution, if any.</param>
public class FilterFallbackInfo(string name, IFilter<Update> filter, bool failed, Exception? exception)
{
/// <summary>
/// Gets the name of the filter.
/// </summary>
public string Name { get; } = name;
/// <summary>
/// Gets the filter instance that failed.
/// </summary>
public IFilter<Update> Filter { get; } = filter;
/// <summary>
/// Gets a value indicating whether the filter failed.
/// </summary>
public bool Failed { get; } = failed;
/// <summary>
/// Gets the exception that occurred during filter execution, if any.
/// </summary>
public Exception? Exception { get; } = exception;
}
/// <summary>
/// Specifies the reason for a filter fallback.
/// </summary>
public enum FallbackReason
{
/// <summary>
/// The filter target was null.
/// </summary>
NullTarget,
/// <summary>
/// The filter failed to pass.
/// </summary>
FailedFilter
}
}
@@ -4,6 +4,7 @@ using Telegram.Bot.Polling;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers.Diagnostics;
using Telegrator.MadiatorCore.Descriptors; using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Handlers.Components namespace Telegrator.Handlers.Components
@@ -0,0 +1,36 @@
using Telegram.Bot.Types;
using Telegrator.Filters.Components;
namespace Telegrator.Handlers.Diagnostics
{
/// <summary>
/// Contains information about a filter that failed during execution.
/// Provides details about the filter, its failure status, and any associated exception.
/// </summary>
/// <param name="name">The name of the filter.</param>
/// <param name="filter">The filter instance that failed.</param>
/// <param name="failed">Whether the filter failed.</param>
/// <param name="exception">The exception that occurred during filter execution, if any.</param>
public class FilterFallbackInfo(string name, IFilter<Update> filter, bool failed, Exception? exception)
{
/// <summary>
/// Gets the name of the filter.
/// </summary>
public string Name { get; } = name;
/// <summary>
/// Gets the filter instance that failed.
/// </summary>
public IFilter<Update> Filter { get; } = filter;
/// <summary>
/// Gets a value indicating whether the filter failed.
/// </summary>
public bool Failed { get; } = failed;
/// <summary>
/// Gets the exception that occurred during filter execution, if any.
/// </summary>
public Exception? Exception { get; } = exception;
}
}
@@ -0,0 +1,65 @@
using Telegram.Bot.Types;
using Telegrator.Filters.Components;
using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Handlers.Diagnostics
{
/// <summary>
/// Represents a report of filter fallback information for debugging and error handling.
/// Contains detailed information about which filters failed and why during handler execution.
/// </summary>
/// <param name="descriptor">The handler descriptor that generated this report.</param>
/// <param name="context">The filter execution context.</param>
public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext<Update> context)
{
/// <summary>
/// Gets the handler descriptor associated with this fallback report.
/// </summary>
public HandlerDescriptor Descriptor { get; } = descriptor;
/// <summary>
/// Gets the filter execution context that generated this report.
/// </summary>
public FilterExecutionContext<Update> Context { get; } = context;
/// <summary>
/// Gets or sets the fallback information for the update validator filter.
/// </summary>
public FilterFallbackInfo? UpdateValidator { get; set; }
/// <summary>
/// Gets or sets the fallback information for the state keeper validator filter.
/// </summary>
public FilterFallbackInfo? StateKeeperValidator { get; set; }
/// <summary>
/// Gets the list of fallback information for update filters that failed.
/// </summary>
public List<FilterFallbackInfo> UpdateFilters { get; } = [];
/// <summary>
/// Checks filter fail status by name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool this[string name] => UpdateFilters.FirstOrDefault(f => f.Name == name)?.Failed ?? false;
/// <summary>
/// Creates new instance of <see cref="ReportInspector"/> with default filter state as FAILED.
/// </summary>
/// <returns></returns>
public ReportInspector AllFailed()
{
return new ReportInspector(this, false);
}
/// <summary>
/// Creates new instance of <see cref="ReportInspector"/> with default filter state as PASSED.
/// </summary>
/// <returns></returns>
public ReportInspector AllPassed()
{
return new ReportInspector(this, true);
}
}
}
@@ -0,0 +1,73 @@
namespace Telegrator.Handlers.Diagnostics
{
/// <summary>
/// A class builder for pattern checking of <see cref="FiltersFallbackReport"/>
/// </summary>
/// <param name="report"></param>
/// <param name="defaulState"></param>
public sealed class ReportInspector(FiltersFallbackReport report, bool defaulState)
{
private readonly FiltersFallbackReport _report = report;
private readonly bool _defaulState = defaulState;
private readonly List<string> _ignore = [];
private readonly List<string> _excepts = [];
/// <summary>
/// Adds a filter to the exclusion list.
/// Excluded filters are compared oppositely with the default state.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ReportInspector Except(string name)
{
_excepts.Add(name);
return this;
}
/// <summary>
/// Adds a filter to the ignore list.
/// Ignored filters are not checked and do not affect the final result.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ReportInspector Whenever(string name)
{
_ignore.Add(name);
return this;
}
/// <summary>
/// It goes through the report and compares it with the specified filter pattern.
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Casts inspector by executing <see cref="Match"/>
/// </summary>
/// <param name="inspector"></param>
public static implicit operator bool(ReportInspector inspector) => inspector.Match();
}
}
@@ -0,0 +1,18 @@
using Telegrator.Attributes.Components;
namespace Telegrator.Handlers.Diagnostics
{
/// <summary>
/// Provides extension methods for <see cref="ReportInspector"/>
/// </summary>
public static partial class ReportInspectorExtensions
{
/// <inheritdoc cref="ReportInspector.Whenever(string)"/>
public static ReportInspector Whenever<TAttribute>(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase
=> inspector.Whenever(nameof(TAttribute));
/// <inheritdoc cref="ReportInspector.Except(string)"/>
public static ReportInspector Except<TAttribute>(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase
=> inspector.Except(nameof(TAttribute));
}
}
+284
View File
@@ -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
{
/// <summary>
/// Provides usefull helper methods for abstract handler containers
/// </summary>
public static class AbstractHandlerContainerExtensions
{
/// <summary>
/// Changes bot's reaction to message
/// </summary>
/// <param name="container"></param>
/// <param name="reaction"></param>
/// <param name="isBig"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task React(
this IAbstractHandlerContainer<Message> container,
ReactionType reaction,
bool isBig = false,
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
[reaction], isBig, cancellationToken);
/// <summary>
/// Changes bot's reaction to message
/// </summary>
/// <param name="container"></param>
/// <param name="reactions"></param>
/// <param name="isBig"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task React(
this IAbstractHandlerContainer<Message> container,
IEnumerable<ReactionType> reactions,
bool isBig = false,
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
reactions, isBig, cancellationToken);
/// <summary>
/// Sends a reply message to the current message.
/// </summary>
/// <param name="container"></param>
/// <param name="text">The text of the message to send.</param>
/// <param name="parseMode">The parse mode for the message text.</param>
/// <param name="replyMarkup">The reply markup for the message.</param>
/// <param name="linkPreviewOptions">Options for link preview generation.</param>
/// <param name="messageThreadId">The thread ID for forum topics.</param>
/// <param name="entities">The message entities to include.</param>
/// <param name="disableNotification">Whether to disable notification for the message.</param>
/// <param name="protectContent">Whether to protect the message content.</param>
/// <param name="messageEffectId">The message effect ID.</param>
/// <param name="businessConnectionId">The business connection ID.</param>
/// <param name="allowPaidBroadcast">Whether to allow paid broadcast.</param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The sent message.</returns>
public static async Task<Message> Reply(
this IAbstractHandlerContainer<Message> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
/// <summary>
/// Sends a response message to the current chat.
/// </summary>
/// <param name="container"></param>
/// <param name="text">The text of the message to send.</param>
/// <param name="parseMode">The parse mode for the message text.</param>
/// <param name="replyParameters">The reply parameters for the message.</param>
/// <param name="replyMarkup">The reply markup for the message.</param>
/// <param name="linkPreviewOptions">Options for link preview generation.</param>
/// <param name="messageThreadId">The thread ID for forum topics.</param>
/// <param name="entities">The message entities to include.</param>
/// <param name="disableNotification">Whether to disable notification for the message.</param>
/// <param name="protectContent">Whether to protect the message content.</param>
/// <param name="messageEffectId">The message effect ID.</param>
/// <param name="businessConnectionId">The business connection ID.</param>
/// <param name="allowPaidBroadcast">Whether to allow paid broadcast.</param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The sent message.</returns>
public static async Task<Message> Responce(
this IAbstractHandlerContainer<Message> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyParameters? replyParameters = null,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
/// <summary>
/// Responnces to message that this CallbackQuery was originated from
/// </summary>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="parseMode"></param>
/// <param name="replyParameters"></param>
/// <param name="replyMarkup"></param>
/// <param name="linkPreviewOptions"></param>
/// <param name="messageThreadId"></param>
/// <param name="entities"></param>
/// <param name="disableNotification"></param>
/// <param name="protectContent"></param>
/// <param name="messageEffectId"></param>
/// <param name="businessConnectionId"></param>
/// <param name="allowPaidBroadcast"></param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<Message> Responce(
this IAbstractHandlerContainer<CallbackQuery> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyParameters? replyParameters = null,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
}
/// <summary>
/// Edits message text that this CallbackQuery was originated from
/// </summary>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="parseMode"></param>
/// <param name="replyMarkup"></param>
/// <param name="entities"></param>
/// <param name="linkPreviewOptions"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<Message> EditMessage(
this IAbstractHandlerContainer<CallbackQuery> container,
string text,
ParseMode parseMode = ParseMode.None,
InlineKeyboardMarkup? replyMarkup = null,
IEnumerable<MessageEntity>? 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);
}
/// <summary>
/// Use this method to send answers to callback queries sent from <a href="https://core.telegram.org/bots/features#inline-keyboards">inline keyboards</a>.
/// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert
/// </summary>
/// <remarks>
/// 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 <a href="https://t.me/botfather">@BotFather</a> and accept the terms.
/// Otherwise, you may use links like <c>t.me/your_bot?start=XXXX</c> that open your bot with a parameter.
/// </remarks>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="showAlert"></param>
/// <param name="url"></param>
/// <param name="cacheTime"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task AnswerCallbackQuery(
this IAbstractHandlerContainer<CallbackQuery> 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);
/// <summary>
/// Answers inline query
/// </summary>
/// <param name="container"></param>
/// <param name="results"></param>
/// <param name="cacheTime"></param>
/// <param name="isPersonal"></param>
/// <param name="nextOffset"></param>
/// <param name="button"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task AnswerInlineQuery(
this IAbstractHandlerContainer<InlineQuery> container,
IEnumerable<InlineQueryResult> 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);
}
}
}
@@ -1,6 +1,6 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Diagnostics;
using Telegrator.Logging; using Telegrator.Logging;
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
+1
View File
@@ -6,6 +6,7 @@ using Telegram.Bot.Types.Enums;
using Telegrator.Configuration; using Telegrator.Configuration;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
using Telegrator.Handlers.Diagnostics;
using Telegrator.Logging; using Telegrator.Logging;
using Telegrator.MadiatorCore; using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors; using Telegrator.MadiatorCore.Descriptors;
+1
View File
@@ -3,6 +3,7 @@ using Telegrator.Aspects;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.MadiatorCore; using Telegrator.MadiatorCore;
using Telegrator.Handlers.Diagnostics;
namespace Telegrator namespace Telegrator
{ {
+347
View File
@@ -0,0 +1,347 @@
using System.Collections.ObjectModel;
using System.Reflection;
using Telegrator.Filters.Components;
using Telegrator.Handlers.Components;
using Telegrator.Providers;
namespace Telegrator
{
/// <summary>
/// Provides extension methods for working with collections.
/// </summary>
public static partial class ColletionsExtensions
{
/// <summary>
/// Creates a <see cref="ReadOnlyDictionary{TKey, TValue}"/> from an <see cref="IEnumerable{TValue}"/>
/// according to a specified key selector function.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="keySelector"></param>
/// <returns></returns>
public static ReadOnlyDictionary<TKey, TValue> ToReadOnlyDictionary<TKey, TValue>(this IEnumerable<TValue> source, Func<TValue, TKey> keySelector) where TKey : notnull
{
Dictionary<TKey, TValue> dictionary = source.ToDictionary(keySelector);
return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}
/// <summary>
/// Enumerates objects in a <paramref name="source"/> and executes an <paramref name="action"/> on each one
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="action"></param>
/// <returns></returns>
public static IEnumerable<TValue> ForEach<TValue>(this IEnumerable<TValue> source, Action<TValue> action)
{
foreach (TValue value in source)
action.Invoke(value);
return source;
}
/// <summary>
/// Sets the value of a key in a dictionary, or if the key does not exist, adds it
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Set<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value)
{
if (source.ContainsKey(key))
source[key] = value;
else
source.Add(key, value);
}
/// <summary>
/// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="defaultValue"></param>
public static void Set<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value, TValue defaultValue)
{
if (source.ContainsKey(key))
source[key] = value;
else
source.Add(key, defaultValue);
}
/// <summary>
/// Return the random object from <paramref name="source"/>
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TSource Random<TSource>(this IEnumerable<TSource> source)
=> source.Random(new Random());
/// <summary>
/// Return the random object from <paramref name="source"/>
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="source"></param>
/// <param name="random"></param>
/// <returns></returns>
public static TSource Random<TSource>(this IEnumerable<TSource> source, Random random)
=> source.ElementAt(random.Next(0, source.Count() - 1));
/// <summary>
/// Adds a range of elements to collection if they dont already exist using default equality comparer
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="list"></param>
/// <param name="elements"></param>
public static void UnionAdd<TSource>(this IList<TSource> list, params IEnumerable<TSource> elements)
{
foreach (TSource item in elements)
{
if (!list.Contains(item, EqualityComparer<TSource>.Default))
list.Add(item);
}
}
/// <summary>
/// Return index of first element that satisfies the condition
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
int index = 0;
foreach (T item in source)
{
if (predicate.Invoke(item))
return index;
index++;
}
return -1;
}
/// <summary>
/// Returns an enumerable that repeats the item multiple times.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
/// <param name="times"></param>
/// <returns></returns>
public static IEnumerable<T> Repeat<T>(this T item, int times)
=> Enumerable.Range(0, times).Select(_ => item);
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T? SingleSafe<T>(this IEnumerable<T> source)
=> source.Count() == 1 ? source.ElementAt(0) : default;
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static T? SingleSafe<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
source = source.Where(predicate);
return source.Count() == 1 ? source.ElementAt(0) : default;
}
}
/// <summary>
/// Provides extension methods for reflection and type inspection.
/// </summary>
public static partial class ReflectionExtensions
{
/// <summary>
/// Checks if a type implements the <see cref="ICustomDescriptorsProvider"/> interface.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns>True if the type implements ICustomDescriptorsProvider; otherwise, false.</returns>
public static bool IsCustomDescriptorsProvider(this Type type)
=> type.GetInterface(nameof(ICustomDescriptorsProvider)) != null;
/// <summary>
/// Checks if <paramref name="type"/> is a <see cref="IFilter{T}"/>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsFilterType(this Type type)
=> type.IsAssignableToGenericType(typeof(IFilter<>));
/// <summary>
/// Checks if <paramref name="type"/> is a descendant of <see cref="UpdateHandlerBase"/> class
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsHandlerAbstract(this Type type)
=> type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type);
/// <summary>
/// Checks if <paramref name="type"/> is an implementation of <see cref="UpdateHandlerBase"/> class or its descendants
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsHandlerRealization(this Type type)
=> !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type);
/// <summary>
/// Checks if <paramref name="type"/> has a parameterless constructor
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool HasParameterlessCtor(this Type type)
=> type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0);
/// <summary>
/// Checks is <paramref name="type"/> has public properties
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool HasPublicProperties(this Type type)
=> type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any();
/// <summary>
/// Determines whether an instance of a specified type can be assigned to an instance of the current type
/// </summary>
/// <param name="givenType"></param>
/// <param name="genericType"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// Provides extension methods for string manipulation.
/// </summary>
public static partial class StringExtensions
{
/// <summary>
/// Slices a <paramref name="source"/> string into a array of substrings of fixed <paramref name="length"/>
/// </summary>
/// <param name="source"></param>
/// <param name="length"></param>
/// <returns></returns>
public static IEnumerable<string> 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<char> chunk = source.AsSpan().Slice(start, toSlice);
yield return chunk.ToString();
}
}
/// <summary>
/// Return new string with first found letter set to upper case
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Return new string with first found letter set to lower case
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Checks if string contains a 'word'.
/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it.
/// </summary>
/// <param name="source"></param>
/// <param name="word"></param>
/// <param name="comparison"></param>
/// <param name="startIndex"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// Contains extension method for number types
/// </summary>
public static class NumbersExtensions
{
/// <summary>
/// Check if int value has int flag using bit compare
/// </summary>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag(this int value, int flag)
=> (value & flag) == flag;
/// <summary>
/// Check if int value has enum flag using bit compare
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag<T>(this int value, T flag) where T : Enum
=> value.HasFlag(Convert.ToInt32(flag));
}
}
+1 -926
View File
@@ -1,17 +1,9 @@
using System; using System.Reflection;
using System.Collections.ObjectModel;
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.InlineQueryResults;
using Telegram.Bot.Types.Payments; using Telegram.Bot.Types.Payments;
using Telegram.Bot.Types.ReplyMarkups;
using Telegrator.Annotations; using Telegrator.Annotations;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Attributes; using Telegrator.Attributes;
using Telegrator.Filters.Components;
using Telegrator.Handlers;
using Telegrator.Handlers.Building; using Telegrator.Handlers.Building;
using Telegrator.Handlers.Building.Components; using Telegrator.Handlers.Building.Components;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
@@ -20,8 +12,6 @@ using Telegrator.MadiatorCore.Descriptors;
using Telegrator.Providers; using Telegrator.Providers;
using Telegrator.StateKeeping; using Telegrator.StateKeeping;
using Telegrator.StateKeeping.Abstracts; using Telegrator.StateKeeping.Abstracts;
using Telegrator.StateKeeping.Components;
using static System.Net.Mime.MediaTypeNames;
namespace Telegrator namespace Telegrator
{ {
@@ -171,282 +161,6 @@ namespace Telegrator
=> StateKeeperAttribute<TKey, TState, TKeeper>.Shared; => StateKeeperAttribute<TKey, TState, TKeeper>.Shared;
} }
/// <summary>
/// Provides usefull helper methods for abstract handler containers
/// </summary>
public static class AbstractHandlerContainerExtensions
{
/// <summary>
/// Changes bot's reaction to message
/// </summary>
/// <param name="container"></param>
/// <param name="reaction"></param>
/// <param name="isBig"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task React(
this IAbstractHandlerContainer<Message> container,
ReactionType reaction,
bool isBig = false,
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
[reaction], isBig, cancellationToken);
/// <summary>
/// Changes bot's reaction to message
/// </summary>
/// <param name="container"></param>
/// <param name="reactions"></param>
/// <param name="isBig"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task React(
this IAbstractHandlerContainer<Message> container,
IEnumerable<ReactionType> reactions,
bool isBig = false,
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
reactions, isBig, cancellationToken);
/// <summary>
/// Sends a reply message to the current message.
/// </summary>
/// <param name="container"></param>
/// <param name="text">The text of the message to send.</param>
/// <param name="parseMode">The parse mode for the message text.</param>
/// <param name="replyMarkup">The reply markup for the message.</param>
/// <param name="linkPreviewOptions">Options for link preview generation.</param>
/// <param name="messageThreadId">The thread ID for forum topics.</param>
/// <param name="entities">The message entities to include.</param>
/// <param name="disableNotification">Whether to disable notification for the message.</param>
/// <param name="protectContent">Whether to protect the message content.</param>
/// <param name="messageEffectId">The message effect ID.</param>
/// <param name="businessConnectionId">The business connection ID.</param>
/// <param name="allowPaidBroadcast">Whether to allow paid broadcast.</param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The sent message.</returns>
public static async Task<Message> Reply(
this IAbstractHandlerContainer<Message> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
/// <summary>
/// Sends a response message to the current chat.
/// </summary>
/// <param name="container"></param>
/// <param name="text">The text of the message to send.</param>
/// <param name="parseMode">The parse mode for the message text.</param>
/// <param name="replyParameters">The reply parameters for the message.</param>
/// <param name="replyMarkup">The reply markup for the message.</param>
/// <param name="linkPreviewOptions">Options for link preview generation.</param>
/// <param name="messageThreadId">The thread ID for forum topics.</param>
/// <param name="entities">The message entities to include.</param>
/// <param name="disableNotification">Whether to disable notification for the message.</param>
/// <param name="protectContent">Whether to protect the message content.</param>
/// <param name="messageEffectId">The message effect ID.</param>
/// <param name="businessConnectionId">The business connection ID.</param>
/// <param name="allowPaidBroadcast">Whether to allow paid broadcast.</param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The sent message.</returns>
public static async Task<Message> Responce(
this IAbstractHandlerContainer<Message> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyParameters? replyParameters = null,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
/// <summary>
/// Responnces to message that this CallbackQuery was originated from
/// </summary>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="parseMode"></param>
/// <param name="replyParameters"></param>
/// <param name="replyMarkup"></param>
/// <param name="linkPreviewOptions"></param>
/// <param name="messageThreadId"></param>
/// <param name="entities"></param>
/// <param name="disableNotification"></param>
/// <param name="protectContent"></param>
/// <param name="messageEffectId"></param>
/// <param name="businessConnectionId"></param>
/// <param name="allowPaidBroadcast"></param>
/// <param name="directMessageTopicId"></param>
/// <param name="suggestedPostParameters"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<Message> Responce(
this IAbstractHandlerContainer<CallbackQuery> container,
string text,
ParseMode parseMode = ParseMode.None,
ReplyParameters? replyParameters = null,
ReplyMarkup? replyMarkup = null,
LinkPreviewOptions? linkPreviewOptions = null,
int? messageThreadId = null,
IEnumerable<MessageEntity>? 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);
}
/// <summary>
/// Edits message text that this CallbackQuery was originated from
/// </summary>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="parseMode"></param>
/// <param name="replyMarkup"></param>
/// <param name="entities"></param>
/// <param name="linkPreviewOptions"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<Message> EditMessage(
this IAbstractHandlerContainer<CallbackQuery> container,
string text,
ParseMode parseMode = ParseMode.None,
InlineKeyboardMarkup? replyMarkup = null,
IEnumerable<MessageEntity>? 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);
}
/// <summary>
/// Use this method to send answers to callback queries sent from <a href="https://core.telegram.org/bots/features#inline-keyboards">inline keyboards</a>.
/// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert
/// </summary>
/// <remarks>
/// 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 <a href="https://t.me/botfather">@BotFather</a> and accept the terms.
/// Otherwise, you may use links like <c>t.me/your_bot?start=XXXX</c> that open your bot with a parameter.
/// </remarks>
/// <param name="container"></param>
/// <param name="text"></param>
/// <param name="showAlert"></param>
/// <param name="url"></param>
/// <param name="cacheTime"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task AnswerCallbackQuery(
this IAbstractHandlerContainer<CallbackQuery> 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);
/// <summary>
/// Answers inline query
/// </summary>
/// <param name="container"></param>
/// <param name="results"></param>
/// <param name="cacheTime"></param>
/// <param name="isPersonal"></param>
/// <param name="nextOffset"></param>
/// <param name="button"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task AnswerInlineQuery(
this IAbstractHandlerContainer<InlineQuery> container,
IEnumerable<InlineQueryResult> 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);
}
}
/// <summary> /// <summary>
/// Extensions methods for Awaiter Handler Builders /// Extensions methods for Awaiter Handler Builders
/// </summary> /// </summary>
@@ -673,620 +387,6 @@ namespace Telegrator
} }
} }
/// <summary>
/// Extension methods for handler builders.
/// Provides convenient methods for creating handlers and setting state keepers.
/// </summary>
public static partial class HandlerBuilderExtensions
{
/// <inheritdoc cref="HandlerBuilderBase.SetUpdateValidating(UpdateValidateAction)"/>
public static TBuilder SetUpdateValidating<TBuilder>(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetUpdateValidating(updateValidateAction);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetConcurreny(int)"/>
public static TBuilder SetConcurreny<TBuilder>(this TBuilder handlerBuilder, int concurrency)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetConcurreny(concurrency);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetPriority(int)"/>
public static TBuilder SetPriority<TBuilder>(this TBuilder handlerBuilder, int priority)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetPriority(priority);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetIndexer(int, int)"/>
public static TBuilder SetIndexer<TBuilder>(this TBuilder handlerBuilder, int concurrency, int priority)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetIndexer(concurrency, priority);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.AddFilter(IFilter{Update})"/>
public static TBuilder AddFilter<TBuilder>(this TBuilder handlerBuilder, IFilter<Update> filter)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.AddFilter(filter);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.AddFilters(IFilter{Update}[])"/>
public static TBuilder AddFilters<TBuilder>(this TBuilder handlerBuilder, params IFilter<Update>[] filters)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.AddFilters(filters);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(TState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, TState myState, IStateKeyResolver<TKey> keyResolver)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(SpecialState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TBuilder"></typeparam>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="handlerBuilder"></param>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public static TBuilder AddTargetedFilter<TBuilder, TFilterTarget>(this TBuilder handlerBuilder, Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter)
where TBuilder : HandlerBuilderBase
where TFilterTarget : class
{
handlerBuilder.AddTargetedFilter(getFilterringTarget, filter);
return handlerBuilder;
}
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TBuilder"></typeparam>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="handlerBuilder"></param>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public static TBuilder AddTargetedFilters<TBuilder, TFilterTarget>(this TBuilder handlerBuilder, Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters)
where TBuilder : HandlerBuilderBase
where TFilterTarget : class
{
handlerBuilder.AddTargetedFilters(getFilterringTarget, filters);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, new SenderIdResolver());
return handlerBuilder;
}
}
/// <summary>
/// Provides extension methods for working with collections.
/// </summary>
public static partial class ColletionsExtensions
{
/// <summary>
/// Creates a <see cref="ReadOnlyDictionary{TKey, TValue}"/> from an <see cref="IEnumerable{TValue}"/>
/// according to a specified key selector function.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="keySelector"></param>
/// <returns></returns>
public static ReadOnlyDictionary<TKey, TValue> ToReadOnlyDictionary<TKey, TValue>(this IEnumerable<TValue> source, Func<TValue, TKey> keySelector) where TKey : notnull
{
Dictionary<TKey, TValue> dictionary = source.ToDictionary(keySelector);
return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}
/// <summary>
/// Enumerates objects in a <paramref name="source"/> and executes an <paramref name="action"/> on each one
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="action"></param>
/// <returns></returns>
public static IEnumerable<TValue> ForEach<TValue>(this IEnumerable<TValue> source, Action<TValue> action)
{
foreach (TValue value in source)
action.Invoke(value);
return source;
}
/// <summary>
/// Sets the value of a key in a dictionary, or if the key does not exist, adds it
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Set<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value)
{
if (source.ContainsKey(key))
source[key] = value;
else
source.Add(key, value);
}
/// <summary>
/// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="source"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="defaultValue"></param>
public static void Set<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value, TValue defaultValue)
{
if (source.ContainsKey(key))
source[key] = value;
else
source.Add(key, defaultValue);
}
/// <summary>
/// Return the random object from <paramref name="source"/>
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TSource Random<TSource>(this IEnumerable<TSource> source)
=> source.Random(new Random());
/// <summary>
/// Return the random object from <paramref name="source"/>
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="source"></param>
/// <param name="random"></param>
/// <returns></returns>
public static TSource Random<TSource>(this IEnumerable<TSource> source, Random random)
=> source.ElementAt(random.Next(0, source.Count() - 1));
/// <summary>
/// Adds a range of elements to collection if they dont already exist using default equality comparer
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="list"></param>
/// <param name="elements"></param>
public static void UnionAdd<TSource>(this IList<TSource> list, params IEnumerable<TSource> elements)
{
foreach (TSource item in elements)
{
if (!list.Contains(item, EqualityComparer<TSource>.Default))
list.Add(item);
}
}
/// <summary>
/// Return index of first element that satisfies the condition
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
int index = 0;
foreach (T item in source)
{
if (predicate.Invoke(item))
return index;
index++;
}
return -1;
}
/// <summary>
/// Returns an enumerable that repeats the item multiple times.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
/// <param name="times"></param>
/// <returns></returns>
public static IEnumerable<T> Repeat<T>(this T item, int times)
=> Enumerable.Range(0, times).Select(_ => item);
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T? SingleSafe<T>(this IEnumerable<T> source)
=> source.Count() == 1 ? source.ElementAt(0) : default;
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static T? SingleSafe<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
source = source.Where(predicate);
return source.Count() == 1 ? source.ElementAt(0) : default;
}
}
/// <summary>
/// Provides extension methods for reflection and type inspection.
/// </summary>
public static partial class ReflectionExtensions
{
/// <summary>
/// Checks if a type implements the <see cref="ICustomDescriptorsProvider"/> interface.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns>True if the type implements ICustomDescriptorsProvider; otherwise, false.</returns>
public static bool IsCustomDescriptorsProvider(this Type type)
=> type.GetInterface(nameof(ICustomDescriptorsProvider)) != null;
/// <summary>
/// Checks if <paramref name="type"/> is a <see cref="IFilter{T}"/>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsFilterType(this Type type)
=> type.IsAssignableToGenericType(typeof(IFilter<>));
/// <summary>
/// Checks if <paramref name="type"/> is a descendant of <see cref="UpdateHandlerBase"/> class
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsHandlerAbstract(this Type type)
=> type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type);
/// <summary>
/// Checks if <paramref name="type"/> is an implementation of <see cref="UpdateHandlerBase"/> class or its descendants
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsHandlerRealization(this Type type)
=> !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type);
/// <summary>
/// Checks if <paramref name="type"/> has a parameterless constructor
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool HasParameterlessCtor(this Type type)
=> type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0);
/*
/// <summary>
/// Invokes a "<paramref name="methodName"/>" method of an object
/// </summary>
/// <param name="obj"></param>
/// <param name="methodName"></param>
/// <param name="args"></param>
/// <returns></returns>
public static object? InvokeMethod(this object obj, string methodName, params object[]? args)
=> obj.GetType().GetMethod(methodName, BindAll).InvokeMethod(obj, args);
/// <summary>
/// Invokes a method of <paramref name="methodInfo"/>
/// </summary>
/// <param name="methodInfo"></param>
/// <param name="obj"></param>
/// <param name="args"></param>
/// <returns></returns>
public static object? InvokeMethod(this MethodInfo methodInfo, object obj, params object[]? args)
=> methodInfo.Invoke(obj, args);
/// <summary>
/// Invokes a static method of <paramref name="methodInfo"/>
/// </summary>
/// <param name="methodInfo"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static object? InvokeStaticMethod(this MethodInfo methodInfo, params object[]? parameters)
=> methodInfo.Invoke(null, parameters);
/// <summary>
/// Invokes a static "<paramref name="methodName"/>" method of an object
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="methodName"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static T? InvokeStaticMethod<T>(this object obj, string methodName, params object[]? parameters)
=> (T?)obj.GetType().GetMethod(methodName, BindAll).InvokeStaticMethod(parameters);
/// <summary>
/// Invokes a generic method of <paramref name="methodInfo"/> with generic types in <paramref name="genericParameters"/>
/// </summary>
/// <param name="methodInfo"></param>
/// <param name="obj"></param>
/// <param name="genericParameters"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static object InvokeGenericMethod(this MethodInfo methodInfo, object obj, Type[] genericParameters, params object[]? parameters)
=> methodInfo.MakeGenericMethod(genericParameters).Invoke(obj, parameters);
/// <summary>
/// Invokes a generic <paramref name="methodName"/> method with generic types in <paramref name="genericParameters"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="methodName"></param>
/// <param name="genericParameters"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static T? InvokeGenericMethod<T>(this object obj, string methodName, Type[] genericParameters, params object[]? parameters)
=> (T?)obj.GetType().GetMethod(methodName).InvokeGenericMethod(obj, genericParameters, parameters);
*/
/// <summary>
/// Checks is <paramref name="type"/> has public properties
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool HasPublicProperties(this Type type)
=> type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any();
/// <summary>
/// Determines whether an instance of a specified type can be assigned to an instance of the current type
/// </summary>
/// <param name="givenType"></param>
/// <param name="genericType"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// Provides extension methods for string manipulation.
/// </summary>
public static partial class StringExtensions
{
/// <summary>
/// Slices a <paramref name="source"/> string into a array of substrings of fixed <paramref name="length"/>
/// </summary>
/// <param name="source"></param>
/// <param name="length"></param>
/// <returns></returns>
public static IEnumerable<string> 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<char> chunk = source.AsSpan().Slice(start, toSlice);
yield return chunk.ToString();
}
}
/// <summary>
/// Return new string with first found letter set to upper case
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Return new string with first found letter set to lower case
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Checks if string contains a 'word'.
/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it.
/// </summary>
/// <param name="source"></param>
/// <param name="word"></param>
/// <param name="comparison"></param>
/// <param name="startIndex"></param>
/// <returns></returns>
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;
}
}
/// <summary> /// <summary>
/// Provides extension methods for working with Telegram Update objects. /// Provides extension methods for working with Telegram Update objects.
/// </summary> /// </summary>
@@ -1509,29 +609,4 @@ namespace Telegrator
}; };
} }
} }
/// <summary>
/// Contains extension method for number types
/// </summary>
public static class NumbersExtensions
{
/// <summary>
/// Check if int value has int flag using bit compare
/// </summary>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag(this int value, int flag)
=> (value & flag) == flag;
/// <summary>
/// Check if int value has enum flag using bit compare
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag<T>(this int value, T flag) where T : Enum
=> value.HasFlag(Convert.ToInt32(flag));
}
} }