Added missing XML summaries

This commit is contained in:
2025-08-12 00:42:43 +04:00
parent 3b7908756d
commit 8299551d69
8 changed files with 285 additions and 21 deletions
@@ -3,22 +3,53 @@ using Telegrator.Filters;
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute for filtering messages where a command argument starts with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should start with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument ends with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should end with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument contains the specified content.
/// </summary>
/// <param name="content">The content that the command argument should contain.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument equals the specified content.
/// </summary>
/// <param name="content">The content that the command argument should equal.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument matches a regular expression pattern.
/// </summary>
/// <param name="pattern">The regular expression pattern to match against the command argument.</param>
/// <param name="options">The regex options to use for the pattern matching.</param>
/// <param name="matchTimeout">The timeout for the regex match operation.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, TimeSpan matchTimeout = default, int index = 0)
: MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, matchTimeout, index))
{ }
+113 -6
View File
@@ -5,10 +5,23 @@ using Telegrator.Handlers;
namespace Telegrator.Filters
{
/// <summary>
/// Abstract base class for filters that operate on command arguments.
/// Provides functionality to extract and validate command arguments from message text.
/// </summary>
/// <param name="index">The index of the argument to filter (0-based).</param>
public abstract class CommandArgumentFilterBase(int index) : Filter<Message>
{
/// <summary>
/// Gets the chosen argument at the specified index.
/// </summary>
protected string Target { get; private set; } = null!;
/// <summary>
/// Determines whether the filter can pass by extracting the command argument and validating it.
/// </summary>
/// <param name="context">The filter execution context containing the message.</param>
/// <returns>True if the argument exists and passes validation; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Message> context)
{
CommandHandlerAttribute attr = context.CompletedFilters.Get<CommandHandlerAttribute>(0);
@@ -21,57 +34,151 @@ namespace Telegrator.Filters
return CanPassNext(context);
}
/// <summary>
/// Determines whether the filter can pass for the given context.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
protected abstract bool CanPassNext(FilterExecutionContext<Message> context);
}
/// <summary>
/// Filter that checks if a command argument starts with a specified content.
/// </summary>
/// <param name="content">The content to check if the argument starts with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
/// <summary>
/// The content to check if the argument starts with.
/// </summary>
protected readonly string Content = content;
/// <summary>
/// The string comparison type to use for the check.
/// </summary>
protected readonly StringComparison Comparison = comparison;
/// <summary>
/// Checks if the command argument starts with the specified content using the configured comparison.
/// </summary>
/// <param name="_">The filter execution context (unused).</param>
/// <returns>True if the argument starts with the specified content; otherwise, false.</returns>
protected override bool CanPassNext(FilterExecutionContext<Message> _)
=> Target.StartsWith(Content, Comparison);
}
/// <summary>
/// Filter that checks if a command argument ends with a specified content.
/// </summary>
/// <param name="content">The content to check if the argument ends with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
/// <summary>
/// The content to check if the argument ends with.
/// </summary>
protected readonly string Content = content;
/// <summary>
/// The string comparison type to use for the check.
/// </summary>
protected readonly StringComparison Comparison = comparison;
/// <summary>
/// Checks if the command argument ends with the specified content using the configured comparison.
/// </summary>
/// <param name="_">The filter execution context (unused).</param>
/// <returns>True if the argument ends with the specified content; otherwise, false.</returns>
protected override bool CanPassNext(FilterExecutionContext<Message> _)
=> Target.EndsWith(Content, Comparison);
}
/// <summary>
/// Filter that checks if a command argument contains a specified content.
/// </summary>
/// <param name="content">The content to check if the argument contains.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
/// <summary>
/// The content to check if the argument contains.
/// </summary>
protected readonly string Content = content;
/// <summary>
/// The string comparison type to use for the check.
/// </summary>
protected readonly StringComparison Comparison = comparison;
/// <summary>
/// Checks if the command argument contains the specified content using the configured comparison.
/// </summary>
/// <param name="_">The filter execution context (unused).</param>
/// <returns>True if the argument contains the specified content; otherwise, false.</returns>
protected override bool CanPassNext(FilterExecutionContext<Message> _)
=> Target.IndexOf(Content, Comparison) >= 0;
}
/// <summary>
/// Filter that checks if a command argument equals a specified content.
/// </summary>
/// <param name="content">The content to check if the argument equals.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
/// <summary>
/// The content to check if the argument equals.
/// </summary>
protected readonly string Content = content;
/// <summary>
/// The string comparison type to use for the check.
/// </summary>
protected readonly StringComparison Comparison = comparison;
/// <summary>
/// Checks if the command argument equals the specified content using the configured comparison.
/// </summary>
/// <param name="_">The filter execution context (unused).</param>
/// <returns>True if the argument equals the specified content; otherwise, false.</returns>
protected override bool CanPassNext(FilterExecutionContext<Message> _)
=> Target.Equals(Content, Comparison);
}
public class ArgumentRegexFilter : CommandArgumentFilterBase
/// <summary>
/// Filter that checks if a command argument matches a regular expression pattern.
/// </summary>
/// <param name="regex">The regular expression to match against the argument.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentRegexFilter(Regex regex, int index = 0) : CommandArgumentFilterBase(index)
{
private readonly Regex _regex;
private readonly Regex _regex = regex;
/// <summary>
/// Gets the match found by the regex.
/// </summary>
public Match Match { get; private set; } = null!;
public ArgumentRegexFilter(Regex regex, int index = 0)
: base(index) => _regex = regex;
/// <summary>
/// Initializes a new instance of <see cref="ArgumentRegexFilter"/> with a regex pattern.
/// </summary>
/// <param name="pattern">The regular expression pattern to match.</param>
/// <param name="options">The regex options to use.</param>
/// <param name="matchTimeout">The timeout for the regex match operation.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public ArgumentRegexFilter(string pattern, RegexOptions options = RegexOptions.None, TimeSpan matchTimeout = default, int index = 0)
: base(index) => _regex = new Regex(pattern, options, matchTimeout);
: this(new Regex(pattern, options, matchTimeout), index) { }
/// <summary>
/// Checks if the command argument matches the regular expression pattern.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the argument matches the regex pattern; otherwise, false.</returns>
protected override bool CanPassNext(FilterExecutionContext<Message> context)
{
Match = _regex.Match(Target);
+2 -2
View File
@@ -107,7 +107,7 @@ namespace Telegrator.Filters
/// </summary>
public class DiceThrowedFilter : MessageFilterBase
{
private readonly DiceType? Dice;
private readonly DiceType Dice;
private readonly int Value;
/// <summary>
@@ -132,7 +132,7 @@ namespace Telegrator.Filters
if (Target.Dice == null)
return false;
if (Dice != null && Target.Dice.Emoji != GetEmojyForDiceType(Dice))
if (Target.Dice.Emoji != GetEmojyForDiceType(Dice))
return false;
return Target.Dice.Value == Value;
@@ -33,6 +33,10 @@ namespace Telegrator.Handlers
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// Initializes new instance of <see cref="AbstractHandlerContainer{TUpdate}"/>
/// </summary>
/// <param name="handlerInfo"></param>
public AbstractHandlerContainer(DescribedHandlerInfo handlerInfo)
{
ActualUpdate = handlerInfo.HandlingUpdate.GetActualUpdateObject<TUpdate>();
@@ -43,6 +47,15 @@ namespace Telegrator.Handlers
AwaitingProvider = handlerInfo.AwaitingProvider;
}
/// <summary>
/// Initializes new instance of <see cref="AbstractHandlerContainer{TUpdate}"/>
/// </summary>
/// <param name="actualUpdate"></param>
/// <param name="handlingUpdate"></param>
/// <param name="client"></param>
/// <param name="extraData"></param>
/// <param name="filters"></param>
/// <param name="awaitingProvider"></param>
public AbstractHandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary<string, object> extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider)
{
ActualUpdate = actualUpdate;
@@ -53,6 +66,11 @@ namespace Telegrator.Handlers
AwaitingProvider = awaitingProvider;
}
/// <summary>
/// Creates new container of specific update type from thos contatiner
/// </summary>
/// <typeparam name="QUpdate"></typeparam>
/// <returns></returns>
public AbstractHandlerContainer<QUpdate> CreateChild<QUpdate>() where QUpdate : class
{
return new AbstractHandlerContainer<QUpdate>(
@@ -61,6 +79,12 @@ namespace Telegrator.Handlers
CompletedFilters, AwaitingProvider);
}
/// <summary>
/// Creates new container of specific update type from existing container
/// </summary>
/// <typeparam name="QUpdate"></typeparam>
/// <param name="other"></param>
/// <returns></returns>
public static AbstractHandlerContainer<TUpdate> From<QUpdate>(IAbstractHandlerContainer<QUpdate> other) where QUpdate : class
{
return new AbstractHandlerContainer<TUpdate>(
+7 -1
View File
@@ -16,6 +16,9 @@ namespace Telegrator.Handlers
/// </summary>
public string ReceivedCommand { get; private set; } = null!;
/// <summary>
/// Message text splited by space characters
/// </summary>
public string[]? Arguments { get; internal set; } = null;
/// <summary>
@@ -32,7 +35,10 @@ namespace Telegrator.Handlers
if (commandEntity.Type != MessageEntityType.BotCommand)
return false;
ReceivedCommand = message.Text.Substring(commandEntity.Offset + 1, commandEntity.Length - 1);
if (commandEntity.Offset != 0)
return false;
ReceivedCommand = message.Text.Substring(1, commandEntity.Length - 1);
if (ReceivedCommand.Contains('@'))
{
string[] split = ReceivedCommand.Split('@');
+44 -2
View File
@@ -7,21 +7,41 @@ using Telegrator.Handlers.Components;
namespace Telegrator.Handlers
{
/// <summary>
/// Attribute that marks a handler to process inline queries.
/// </summary>
public class InlineQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute<InlineQueryHandler>(UpdateType.InlineQuery, importance)
{
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context) => context.Input.InlineQuery is { } | context.Input.ChosenInlineResult is { };
}
/// <summary>
/// Abstract base class for handlers that process inline queries.
/// </summary>
public abstract class InlineQueryHandler() : AbstractUpdateHandler<Update>(UpdateType.InlineQuery)
{
/// <summary>
/// Handler container for the current <see cref="InlineQuery"/> update.
/// </summary>
protected IAbstractHandlerContainer<InlineQuery> QueryContainer { get; private set; } = null!;
/// <summary>
/// Handler container for the current <see cref="ChosenInlineResult"/> update.
/// </summary>
protected IAbstractHandlerContainer<ChosenInlineResult> ChosenContainer { get; private set; } = null!;
/// <summary>
/// Incoming update of type <see cref="InlineQuery"/>.
/// </summary>
protected InlineQuery InputQuery { get; private set; } = null!;
/// <summary>
/// Incoming update of type <see cref="ChosenInlineResult"/>.
/// </summary>
protected ChosenInlineResult InputChosen { get; private set; } = null!;
/// <inheritdoc/>
public override async Task<Result> Execute(IAbstractHandlerContainer<Update> container, CancellationToken cancellation)
{
switch (container.HandlingUpdate.Type)
@@ -30,14 +50,14 @@ namespace Telegrator.Handlers
{
QueryContainer = AbstractHandlerContainer<InlineQuery>.From(container);
InputQuery = QueryContainer.ActualUpdate;
return await Requested(QueryContainer, cancellation);
return await Requested(QueryContainer, cancellation).ConfigureAwait(false);
}
case UpdateType.ChosenInlineResult:
{
ChosenContainer = AbstractHandlerContainer<ChosenInlineResult>.From(container);
InputChosen = ChosenContainer.ActualUpdate;
return await Chosen(ChosenContainer, cancellation);
return await Chosen(ChosenContainer, cancellation).ConfigureAwait(false);
}
default:
@@ -45,10 +65,32 @@ namespace Telegrator.Handlers
}
}
/// <summary>
/// Executes handler logic if received update is <see cref="UpdateType.InlineQuery"/>
/// </summary>
/// <param name="container"></param>
/// <param name="cancellation"></param>
/// <returns></returns>
public abstract Task<Result> Requested(IAbstractHandlerContainer<InlineQuery> container, CancellationToken cancellation);
/// <summary>
/// Executes handler logic if received update is <see cref="UpdateType.ChosenInlineResult"/>
/// </summary>
/// <param name="container"></param>
/// <param name="cancellation"></param>
/// <returns></returns>
public abstract Task<Result> Chosen(IAbstractHandlerContainer<ChosenInlineResult> container, CancellationToken cancellation);
/// <summary>
/// Answers inline query
/// </summary>
/// <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>
protected async Task Answer(
IEnumerable<InlineQueryResult> results,
int? cacheTime = null,
+6 -2
View File
@@ -72,7 +72,7 @@ namespace Telegrator.Polling
if (ExecutingHandlersSemaphore != null)
{
await ExecutingHandlersSemaphore.WaitAsync();
await ExecutingHandlersSemaphore.WaitAsync().ConfigureAwait(false);
}
try
@@ -82,7 +82,11 @@ namespace Telegrator.Polling
using (UpdateHandlerBase instance = handlerInfo.HandlerInstance)
{
lastResult = await instance.Execute(handlerInfo);
Task<Result> task = instance.Execute(handlerInfo);
HandlerEnqueued?.Invoke(handlerInfo);
await task.ConfigureAwait(false);
lastResult = task.Result;
ExecutingHandlersSemaphore?.Release(1);
}
+57 -7
View File
@@ -42,6 +42,12 @@ namespace Telegrator
return message.Text.Substring(entity.Offset, entity.Length);
}
/// <summary>
/// Checkes if sent <see cref="Message"/> contains command. Automatically cuts bot name from it
/// </summary>
/// <param name="message"></param>
/// <param name="command"></param>
/// <returns></returns>
public static bool IsCommand(this Message message, out string? command)
{
command = null;
@@ -52,7 +58,10 @@ namespace Telegrator
if (commandEntity.Type != MessageEntityType.BotCommand)
return false;
command = message.Text.Substring(commandEntity.Offset + 1, commandEntity.Length - 1);
if (commandEntity.Offset != 0)
return false;
command = message.Text.Substring(1, commandEntity.Length - 1);
if (command.Contains('@'))
{
string[] split = command.Split('@');
@@ -62,9 +71,17 @@ namespace Telegrator
return true;
}
/// <summary>
/// Split message text into arguments, ignoring command instance. Splits by space character
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
/// <exception cref="InvalidDataException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="MissingMemberException"></exception>
public static string[] SplitArgs(this Message message)
{
if (!message.IsCommand(out string? command))
if (!message.IsCommand(out _))
throw new InvalidDataException("Message does not contain a command");
if (message is not { Text.Length: > 0 })
@@ -76,10 +93,16 @@ namespace Telegrator
return message.Text.Split([' '], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray();
}
/// <summary>
/// Tries to split message text into arguments, ignoring command instance. Splits by space character. Exception-free version of <see cref="SplitArgs(Message)"/>
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
/// <returns></returns>
public static bool TrySplitArgs(this Message message, out string[]? args)
{
args = null;
if (!message.IsCommand(out string? command))
if (!message.IsCommand(out _))
return false;
if (message is not { Text.Length: > 0 })
@@ -150,21 +173,37 @@ namespace Telegrator
/// </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,
IEnumerable<ReactionType> reactions,
ReactionType reaction,
bool isBig = false,
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
reactions, isBig, cancellationToken);
[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,
params IEnumerable<ReactionType> reactions)
CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
@@ -365,6 +404,17 @@ namespace Telegrator
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,