diff --git a/Telegrator/Annotations/CommandArgumentAttributes.cs b/Telegrator/Annotations/CommandArgumentAttributes.cs
index a38f797..ceac359 100644
--- a/Telegrator/Annotations/CommandArgumentAttributes.cs
+++ b/Telegrator/Annotations/CommandArgumentAttributes.cs
@@ -3,22 +3,53 @@ using Telegrator.Filters;
namespace Telegrator.Annotations
{
+ ///
+ /// Attribute for filtering messages where a command argument starts with the specified content.
+ ///
+ /// The content that the command argument should start with.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index))
{ }
+ ///
+ /// Attribute for filtering messages where a command argument ends with the specified content.
+ ///
+ /// The content that the command argument should end with.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index))
{ }
+ ///
+ /// Attribute for filtering messages where a command argument contains the specified content.
+ ///
+ /// The content that the command argument should contain.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index))
{ }
+ ///
+ /// Attribute for filtering messages where a command argument equals the specified content.
+ ///
+ /// The content that the command argument should equal.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index))
{ }
+ ///
+ /// Attribute for filtering messages where a command argument matches a regular expression pattern.
+ ///
+ /// The regular expression pattern to match against the command argument.
+ /// The regex options to use for the pattern matching.
+ /// The timeout for the regex match operation.
+ /// The index of the argument to check (0-based).
public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, TimeSpan matchTimeout = default, int index = 0)
: MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, matchTimeout, index))
{ }
diff --git a/Telegrator/Filters/CommandArgumentFilter.cs b/Telegrator/Filters/CommandArgumentFilter.cs
index 1274e6a..13ef094 100644
--- a/Telegrator/Filters/CommandArgumentFilter.cs
+++ b/Telegrator/Filters/CommandArgumentFilter.cs
@@ -5,10 +5,23 @@ using Telegrator.Handlers;
namespace Telegrator.Filters
{
+ ///
+ /// Abstract base class for filters that operate on command arguments.
+ /// Provides functionality to extract and validate command arguments from message text.
+ ///
+ /// The index of the argument to filter (0-based).
public abstract class CommandArgumentFilterBase(int index) : Filter
{
+ ///
+ /// Gets the chosen argument at the specified index.
+ ///
protected string Target { get; private set; } = null!;
+ ///
+ /// Determines whether the filter can pass by extracting the command argument and validating it.
+ ///
+ /// The filter execution context containing the message.
+ /// True if the argument exists and passes validation; otherwise, false.
public override bool CanPass(FilterExecutionContext context)
{
CommandHandlerAttribute attr = context.CompletedFilters.Get(0);
@@ -21,57 +34,151 @@ namespace Telegrator.Filters
return CanPassNext(context);
}
+ ///
+ /// Determines whether the filter can pass for the given context.
+ ///
+ /// The filter execution context.
+ /// True if the filter passes; otherwise, false.
protected abstract bool CanPassNext(FilterExecutionContext context);
}
+ ///
+ /// Filter that checks if a command argument starts with a specified content.
+ ///
+ /// The content to check if the argument starts with.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
+ ///
+ /// The content to check if the argument starts with.
+ ///
protected readonly string Content = content;
+
+ ///
+ /// The string comparison type to use for the check.
+ ///
protected readonly StringComparison Comparison = comparison;
+ ///
+ /// Checks if the command argument starts with the specified content using the configured comparison.
+ ///
+ /// The filter execution context (unused).
+ /// True if the argument starts with the specified content; otherwise, false.
protected override bool CanPassNext(FilterExecutionContext _)
=> Target.StartsWith(Content, Comparison);
}
+ ///
+ /// Filter that checks if a command argument ends with a specified content.
+ ///
+ /// The content to check if the argument ends with.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
+ ///
+ /// The content to check if the argument ends with.
+ ///
protected readonly string Content = content;
+
+ ///
+ /// The string comparison type to use for the check.
+ ///
protected readonly StringComparison Comparison = comparison;
+ ///
+ /// Checks if the command argument ends with the specified content using the configured comparison.
+ ///
+ /// The filter execution context (unused).
+ /// True if the argument ends with the specified content; otherwise, false.
protected override bool CanPassNext(FilterExecutionContext _)
=> Target.EndsWith(Content, Comparison);
}
+ ///
+ /// Filter that checks if a command argument contains a specified content.
+ ///
+ /// The content to check if the argument contains.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
+ ///
+ /// The content to check if the argument contains.
+ ///
protected readonly string Content = content;
+
+ ///
+ /// The string comparison type to use for the check.
+ ///
protected readonly StringComparison Comparison = comparison;
+ ///
+ /// Checks if the command argument contains the specified content using the configured comparison.
+ ///
+ /// The filter execution context (unused).
+ /// True if the argument contains the specified content; otherwise, false.
protected override bool CanPassNext(FilterExecutionContext _)
=> Target.IndexOf(Content, Comparison) >= 0;
}
+ ///
+ /// Filter that checks if a command argument equals a specified content.
+ ///
+ /// The content to check if the argument equals.
+ /// The string comparison type to use for the check.
+ /// The index of the argument to check (0-based).
public class ArgumentEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index)
{
+ ///
+ /// The content to check if the argument equals.
+ ///
protected readonly string Content = content;
+
+ ///
+ /// The string comparison type to use for the check.
+ ///
protected readonly StringComparison Comparison = comparison;
+ ///
+ /// Checks if the command argument equals the specified content using the configured comparison.
+ ///
+ /// The filter execution context (unused).
+ /// True if the argument equals the specified content; otherwise, false.
protected override bool CanPassNext(FilterExecutionContext _)
=> Target.Equals(Content, Comparison);
}
- public class ArgumentRegexFilter : CommandArgumentFilterBase
+ ///
+ /// Filter that checks if a command argument matches a regular expression pattern.
+ ///
+ /// The regular expression to match against the argument.
+ /// The index of the argument to check (0-based).
+ public class ArgumentRegexFilter(Regex regex, int index = 0) : CommandArgumentFilterBase(index)
{
- private readonly Regex _regex;
+ private readonly Regex _regex = regex;
+ ///
+ /// Gets the match found by the regex.
+ ///
public Match Match { get; private set; } = null!;
- public ArgumentRegexFilter(Regex regex, int index = 0)
- : base(index) => _regex = regex;
-
+ ///
+ /// Initializes a new instance of with a regex pattern.
+ ///
+ /// The regular expression pattern to match.
+ /// The regex options to use.
+ /// The timeout for the regex match operation.
+ /// The index of the argument to check (0-based).
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) { }
+ ///
+ /// Checks if the command argument matches the regular expression pattern.
+ ///
+ /// The filter execution context.
+ /// True if the argument matches the regex pattern; otherwise, false.
protected override bool CanPassNext(FilterExecutionContext context)
{
Match = _regex.Match(Target);
diff --git a/Telegrator/Filters/MessageFilters.cs b/Telegrator/Filters/MessageFilters.cs
index fd7cb3e..5fcffd7 100644
--- a/Telegrator/Filters/MessageFilters.cs
+++ b/Telegrator/Filters/MessageFilters.cs
@@ -107,7 +107,7 @@ namespace Telegrator.Filters
///
public class DiceThrowedFilter : MessageFilterBase
{
- private readonly DiceType? Dice;
+ private readonly DiceType Dice;
private readonly int Value;
///
@@ -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;
diff --git a/Telegrator/Handlers/AbstractHandlerContainer.cs b/Telegrator/Handlers/AbstractHandlerContainer.cs
index 6709a3b..88ed8dc 100644
--- a/Telegrator/Handlers/AbstractHandlerContainer.cs
+++ b/Telegrator/Handlers/AbstractHandlerContainer.cs
@@ -33,6 +33,10 @@ namespace Telegrator.Handlers
///
public IAwaitingProvider AwaitingProvider { get; }
+ ///
+ /// Initializes new instance of
+ ///
+ ///
public AbstractHandlerContainer(DescribedHandlerInfo handlerInfo)
{
ActualUpdate = handlerInfo.HandlingUpdate.GetActualUpdateObject();
@@ -43,6 +47,15 @@ namespace Telegrator.Handlers
AwaitingProvider = handlerInfo.AwaitingProvider;
}
+ ///
+ /// Initializes new instance of
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public AbstractHandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider)
{
ActualUpdate = actualUpdate;
@@ -53,6 +66,11 @@ namespace Telegrator.Handlers
AwaitingProvider = awaitingProvider;
}
+ ///
+ /// Creates new container of specific update type from thos contatiner
+ ///
+ ///
+ ///
public AbstractHandlerContainer CreateChild() where QUpdate : class
{
return new AbstractHandlerContainer(
@@ -61,6 +79,12 @@ namespace Telegrator.Handlers
CompletedFilters, AwaitingProvider);
}
+ ///
+ /// Creates new container of specific update type from existing container
+ ///
+ ///
+ ///
+ ///
public static AbstractHandlerContainer From(IAbstractHandlerContainer other) where QUpdate : class
{
return new AbstractHandlerContainer(
diff --git a/Telegrator/Handlers/CommandHandler.cs b/Telegrator/Handlers/CommandHandler.cs
index 627e49c..4d130b0 100644
--- a/Telegrator/Handlers/CommandHandler.cs
+++ b/Telegrator/Handlers/CommandHandler.cs
@@ -16,6 +16,9 @@ namespace Telegrator.Handlers
///
public string ReceivedCommand { get; private set; } = null!;
+ ///
+ /// Message text splited by space characters
+ ///
public string[]? Arguments { get; internal set; } = null;
///
@@ -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('@');
diff --git a/Telegrator/Handlers/InlineQueryHandler.cs b/Telegrator/Handlers/InlineQueryHandler.cs
index 62038b1..7a12d7b 100644
--- a/Telegrator/Handlers/InlineQueryHandler.cs
+++ b/Telegrator/Handlers/InlineQueryHandler.cs
@@ -7,21 +7,41 @@ using Telegrator.Handlers.Components;
namespace Telegrator.Handlers
{
+ ///
+ /// Attribute that marks a handler to process inline queries.
+ ///
public class InlineQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.InlineQuery, importance)
{
+ ///
public override bool CanPass(FilterExecutionContext context) => context.Input.InlineQuery is { } | context.Input.ChosenInlineResult is { };
}
-
+
+ ///
+ /// Abstract base class for handlers that process inline queries.
+ ///
public abstract class InlineQueryHandler() : AbstractUpdateHandler(UpdateType.InlineQuery)
{
+ ///
+ /// Handler container for the current update.
+ ///
protected IAbstractHandlerContainer QueryContainer { get; private set; } = null!;
+ ///
+ /// Handler container for the current update.
+ ///
protected IAbstractHandlerContainer ChosenContainer { get; private set; } = null!;
+ ///
+ /// Incoming update of type .
+ ///
protected InlineQuery InputQuery { get; private set; } = null!;
+ ///
+ /// Incoming update of type .
+ ///
protected ChosenInlineResult InputChosen { get; private set; } = null!;
+ ///
public override async Task Execute(IAbstractHandlerContainer container, CancellationToken cancellation)
{
switch (container.HandlingUpdate.Type)
@@ -30,14 +50,14 @@ namespace Telegrator.Handlers
{
QueryContainer = AbstractHandlerContainer.From(container);
InputQuery = QueryContainer.ActualUpdate;
- return await Requested(QueryContainer, cancellation);
+ return await Requested(QueryContainer, cancellation).ConfigureAwait(false);
}
case UpdateType.ChosenInlineResult:
{
ChosenContainer = AbstractHandlerContainer.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
}
}
+ ///
+ /// Executes handler logic if received update is
+ ///
+ ///
+ ///
+ ///
public abstract Task Requested(IAbstractHandlerContainer container, CancellationToken cancellation);
+ ///
+ /// Executes handler logic if received update is
+ ///
+ ///
+ ///
+ ///
public abstract Task Chosen(IAbstractHandlerContainer container, CancellationToken cancellation);
+ ///
+ /// Answers inline query
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
protected async Task Answer(
IEnumerable results,
int? cacheTime = null,
diff --git a/Telegrator/Polling/UpdateHandlersPool.cs b/Telegrator/Polling/UpdateHandlersPool.cs
index 21a1451..5b3dd49 100644
--- a/Telegrator/Polling/UpdateHandlersPool.cs
+++ b/Telegrator/Polling/UpdateHandlersPool.cs
@@ -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 task = instance.Execute(handlerInfo);
+ HandlerEnqueued?.Invoke(handlerInfo);
+
+ await task.ConfigureAwait(false);
+ lastResult = task.Result;
ExecutingHandlersSemaphore?.Release(1);
}
diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs
index b577f7c..c190d24 100644
--- a/Telegrator/TypesExtensions.cs
+++ b/Telegrator/TypesExtensions.cs
@@ -42,6 +42,12 @@ namespace Telegrator
return message.Text.Substring(entity.Offset, entity.Length);
}
+ ///
+ /// Checkes if sent contains command. Automatically cuts bot name from it
+ ///
+ ///
+ ///
+ ///
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;
}
+ ///
+ /// Split message text into arguments, ignoring command instance. Splits by space character
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
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();
}
+ ///
+ /// Tries to split message text into arguments, ignoring command instance. Splits by space character. Exception-free version of
+ ///
+ ///
+ ///
+ ///
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
///
public static class AbstractHandlerContainerExtensions
{
+ ///
+ /// Changes bot's reaction to message
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static async Task React(
this IAbstractHandlerContainer container,
- IEnumerable 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);
+ ///
+ /// Changes bot's reaction to message
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static async Task React(
this IAbstractHandlerContainer container,
+ IEnumerable reactions,
bool isBig = false,
- CancellationToken cancellationToken = default,
- params IEnumerable reactions)
+ CancellationToken cancellationToken = default)
=> await container.Client.SetMessageReaction(
container.ActualUpdate.Chat,
container.ActualUpdate.Id,
@@ -365,6 +404,17 @@ namespace Telegrator
cacheTime: cacheTime,
cancellationToken: cancellationToken);
+ ///
+ /// Answers inline query
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
public static async Task AnswerInlineQuery(
this IAbstractHandlerContainer container,
IEnumerable results,