Добавьте файлы проекта.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Attributes;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on command aliases.
|
||||
/// Allows handlers to respond to multiple command variations using a single attribute.
|
||||
/// </summary>
|
||||
public class CommandAlliasAttribute : UpdateFilterAttribute<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the allowed update types for this filter.
|
||||
/// </summary>
|
||||
public override UpdateType[] AllowedTypes => [UpdateType.Message];
|
||||
|
||||
/// <summary>
|
||||
/// The description of the command (defaults to "no description provided").
|
||||
/// </summary>
|
||||
private string _description = "no description provided";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of command aliases that this filter will match.
|
||||
/// </summary>
|
||||
public string[] Alliases
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of the command.
|
||||
/// Must be between 0 and 256 characters in length.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the description length is outside the allowed range.</exception>
|
||||
public string Description
|
||||
{
|
||||
get => _description;
|
||||
set => _description = value is { Length: <= 256 and >= 0 }
|
||||
? value : throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CommandAlliasAttribute with the specified command aliases.
|
||||
/// </summary>
|
||||
/// <param name="alliases">The command aliases to match against.</param>
|
||||
public CommandAlliasAttribute(params string[] alliases)
|
||||
: base(new CommandAlliasFilter(alliases)) => Alliases = alliases;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filtering target (Message) from the update.
|
||||
/// </summary>
|
||||
/// <param name="update">The Telegram update.</param>
|
||||
/// <returns>The message from the update, or null if not present.</returns>
|
||||
public override Message? GetFilterringTarget(Update update) => update.Message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base attribute for filtering updates based on environment conditions.
|
||||
/// Can process all types of updates and provides environment-specific filtering logic.
|
||||
/// </summary>
|
||||
/// <param name="filters">The environment filters to apply</param>
|
||||
public abstract class EnvironmentFilterAttribute(params IFilter<Update>[] filters) : UpdateFilterAttribute<Update>(filters)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the allowed update types that this filter can process.
|
||||
/// Environment filters can process all update types.
|
||||
/// </summary>
|
||||
public override UpdateType[] AllowedTypes => Update.AllTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the update as the filtering target.
|
||||
/// Environment filters work with the entire update object.
|
||||
/// </summary>
|
||||
/// <param name="update">The Telegram update</param>
|
||||
/// <returns>The update object itself</returns>
|
||||
public override Update? GetFilterringTarget(Update update)
|
||||
=> update;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates that occur in debug environment.
|
||||
/// Only allows updates when the application is running in debug mode.
|
||||
/// </summary>
|
||||
public class IsDebugEnvironmentAttribute()
|
||||
: EnvironmentFilterAttribute(new IsDebugEnvironmentFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates that occur in release environment.
|
||||
/// Only allows updates when the application is running in release mode.
|
||||
/// </summary>
|
||||
public class IsReleaseEnvironmentAttribute()
|
||||
: EnvironmentFilterAttribute(new IsReleaseEnvironmentFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates based on environment variable values.
|
||||
/// </summary>
|
||||
public class EnvironmentVariableAttribute : EnvironmentFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter based on an environment variable with a specific value and comparison method.
|
||||
/// </summary>
|
||||
/// <param name="variable">The name of the environment variable</param>
|
||||
/// <param name="value">The expected value of the environment variable</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public EnvironmentVariableAttribute(string variable, string? value, StringComparison comparison)
|
||||
: base(new EnvironmentVariableFilter(variable, value, comparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter based on an environment variable with a specific value.
|
||||
/// </summary>
|
||||
/// <param name="variable">The name of the environment variable</param>
|
||||
/// <param name="value">The expected value of the environment variable</param>
|
||||
public EnvironmentVariableAttribute(string variable, string? value)
|
||||
: base(new EnvironmentVariableFilter(variable, value)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter based on the existence of an environment variable.
|
||||
/// </summary>
|
||||
/// <param name="variable">The name of the environment variable</param>
|
||||
public EnvironmentVariableAttribute(string variable)
|
||||
: base(new EnvironmentVariableFilter(variable)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter based on an environment variable with a specific comparison method.
|
||||
/// </summary>
|
||||
/// <param name="variable">The name of the environment variable</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public EnvironmentVariableAttribute(string variable, StringComparison comparison)
|
||||
: base(new EnvironmentVariableFilter(variable, comparison)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that contain mentions.
|
||||
/// Allows handlers to respond only to messages that mention the bot or specific users.
|
||||
/// </summary>
|
||||
public class MentionedAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MentionedAttribute that matches any mention.
|
||||
/// </summary>
|
||||
public MentionedAttribute()
|
||||
: base(new MessageHasEntityFilter(MessageEntityType.Mention, 0, null), new MentionedFilter()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MentionedAttribute that matches mentions at a specific offset.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset position where the mention should occur.</param>
|
||||
public MentionedAttribute(int offset)
|
||||
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MentionedAttribute that matches a specific mention.
|
||||
/// </summary>
|
||||
/// <param name="mention">The specific mention text to match.</param>
|
||||
public MentionedAttribute(string mention)
|
||||
: base(new MessageHasEntityFilter(MessageEntityType.Mention), new MentionedFilter(mention)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MentionedAttribute that matches a specific mention at a specific offset.
|
||||
/// </summary>
|
||||
/// <param name="mention">The specific mention text to match.</param>
|
||||
/// <param name="offset">The offset position where the mention should occur.</param>
|
||||
public MentionedAttribute(string mention, int offset)
|
||||
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter(mention)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent in forum chats.
|
||||
/// </summary>
|
||||
public class ChatIsForumAttribute()
|
||||
: MessageFilterAttribute(new MessageChatIsForumFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent in a specific chat by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The chat ID to match</param>
|
||||
public class ChatIdAttribute(long id)
|
||||
: MessageFilterAttribute(new MessageChatIdFilter(id))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent in chats of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="type">The chat type to match</param>
|
||||
public class ChatTypeAttribute(ChatType type)
|
||||
: MessageFilterAttribute(new MessageChatTypeFilter(type))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat title.
|
||||
/// </summary>
|
||||
public class ChatTitleAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with a specific title and comparison method.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public ChatTitleAttribute(string? title, StringComparison comparison)
|
||||
: base(new MessageChatTitleFilter(title, comparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with a specific title.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match</param>
|
||||
public ChatTitleAttribute(string? title)
|
||||
: base(new MessageChatTitleFilter(title)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat username.
|
||||
/// </summary>
|
||||
public class ChatUsernameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with a specific username and comparison method.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public ChatUsernameAttribute(string? userName, StringComparison comparison)
|
||||
: base(new MessageChatUsernameFilter(userName, comparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with a specific username.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match</param>
|
||||
public ChatUsernameAttribute(string? userName)
|
||||
: base(new MessageChatUsernameFilter(userName, StringComparison.InvariantCulture)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat name (first name and optionally last name).
|
||||
/// </summary>
|
||||
public class ChatNameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public ChatNameAttribute(string? firstName, string? lastName, StringComparison comparison)
|
||||
: base(new MessageChatNameFilter(firstName, lastName, comparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from chats with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
public ChatNameAttribute(string? firstName, string? lastName)
|
||||
: base(new MessageChatNameFilter(firstName, lastName)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base attribute for filtering message-based updates.
|
||||
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to apply to messages</param>
|
||||
public abstract class MessageFilterAttribute(params IFilter<Message>[] filters) : UpdateFilterAttribute<Message>(filters)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the allowed update types that this filter can process.
|
||||
/// </summary>
|
||||
public override UpdateType[] AllowedTypes =>
|
||||
[
|
||||
UpdateType.Message,
|
||||
UpdateType.EditedMessage,
|
||||
UpdateType.ChannelPost,
|
||||
UpdateType.EditedChannelPost,
|
||||
UpdateType.BusinessMessage,
|
||||
UpdateType.EditedBusinessMessage
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the message from various types of updates.
|
||||
/// </summary>
|
||||
/// <param name="update">The Telegram update</param>
|
||||
/// <returns>The message from the update, or null if not present</returns>
|
||||
public override Message? GetFilterringTarget(Update update)
|
||||
{
|
||||
return update switch
|
||||
{
|
||||
{ Message: { } message } => message,
|
||||
{ EditedMessage: { } message } => message,
|
||||
{ ChannelPost: { } message } => message,
|
||||
{ EditedChannelPost: { } message } => message,
|
||||
{ BusinessMessage: { } message } => message,
|
||||
{ EditedBusinessMessage: { } message } => message,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on regular expression patterns.
|
||||
/// </summary>
|
||||
public class MessageRegexAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a regex pattern and options.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The regular expression pattern to match</param>
|
||||
/// <param name="regexOptions">The regex options for matching</param>
|
||||
public MessageRegexAttribute(string pattern, RegexOptions regexOptions = default)
|
||||
: base(new MessageRegexFilter(pattern, regexOptions)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a precompiled regex.
|
||||
/// </summary>
|
||||
/// <param name="regex">The precompiled regular expression</param>
|
||||
public MessageRegexAttribute(Regex regex)
|
||||
: base(new MessageRegexFilter(regex)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that contain dice throws with specific values.
|
||||
/// </summary>
|
||||
public class DiceThrowedAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter dice throws with a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The dice value to match</param>
|
||||
public DiceThrowedAttribute(int value)
|
||||
: base(new DiceThrowedFilter(value)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter dice throws with a specific type and value.
|
||||
/// </summary>
|
||||
/// <param name="diceType">The type of dice</param>
|
||||
/// <param name="value">The dice value to match</param>
|
||||
public DiceThrowedAttribute(DiceType diceType, int value)
|
||||
: base(new DiceThrowedFilter(diceType, value)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that are automatically forwarded.
|
||||
/// </summary>
|
||||
public class IsAutomaticFormwardMessageAttribute()
|
||||
: MessageFilterAttribute(new IsAutomaticFormwardMessageFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent while the user was offline.
|
||||
/// </summary>
|
||||
public class IsFromOfflineMessageAttribute()
|
||||
: MessageFilterAttribute(new IsFromOfflineMessageFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering service messages (e.g., user joined, left, etc.).
|
||||
/// </summary>
|
||||
public class IsServiceMessageMessageAttribute()
|
||||
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering topic messages in forum chats.
|
||||
/// </summary>
|
||||
public class IsTopicMessageMessageAttribute()
|
||||
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on their entities (mentions, links, etc.).
|
||||
/// </summary>
|
||||
public class MessageHasEntityAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages with a specific entity type.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
public MessageHasEntityAttribute(MessageEntityType type)
|
||||
: base(new MessageHasEntityFilter(type)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages with a specific entity type at a specific position.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="offset">The starting position of the entity</param>
|
||||
/// <param name="length">The length of the entity (optional)</param>
|
||||
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length)
|
||||
: base(new MessageHasEntityFilter(type, offset, length)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages with a specific entity type and content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="content">The content that the entity should contain</param>
|
||||
/// <param name="stringComparison">The string comparison method</param>
|
||||
public MessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
|
||||
: base(new MessageHasEntityFilter(type, content, stringComparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages with a specific entity type, position, and content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="offset">The starting position of the entity</param>
|
||||
/// <param name="length">The length of the entity (optional)</param>
|
||||
/// <param name="content">The content that the entity should contain</param>
|
||||
/// <param name="stringComparison">The string comparison method</param>
|
||||
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
|
||||
: base(new MessageHasEntityFilter(type, offset, length, content, stringComparison)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the sender's username.
|
||||
/// </summary>
|
||||
public class FromUsernameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a specific username.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to match</param>
|
||||
public FromUsernameAttribute(string username)
|
||||
: base(new FromUsernameFilter(username)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a specific username with custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public FromUsernameAttribute(string username, StringComparison comparison)
|
||||
: base(new FromUsernameFilter(username, comparison)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the sender's name (first name and optionally last name).
|
||||
/// </summary>
|
||||
public class FromUserAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a user with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public FromUserAttribute(string firstName, string? lastName, StringComparison comparison)
|
||||
: base(new FromUserFilter(firstName, lastName, comparison)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a user with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match</param>
|
||||
public FromUserAttribute(string firstName, string? lastName)
|
||||
: base(new FromUserFilter(firstName, lastName, StringComparison.InvariantCulture)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a user with a specific first name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
public FromUserAttribute(string firstName)
|
||||
: base(new FromUserFilter(firstName, null, StringComparison.InvariantCulture)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages from a user with a specific first name and custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
public FromUserAttribute(string firstName, StringComparison comparison)
|
||||
: base(new FromUserFilter(firstName, null, comparison)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages from a specific user ID.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to match</param>
|
||||
public class FromUserIdAttribute(long userId)
|
||||
: MessageFilterAttribute(new FromUserIdFilter(userId))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent by not bots (users).
|
||||
/// </summary>
|
||||
public class NotFromBotAttribute()
|
||||
: MessageFilterAttribute(new FromBotFilter().Not())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent by bots.
|
||||
/// </summary>
|
||||
public class FromBotAttribute()
|
||||
: MessageFilterAttribute(new FromBotFilter())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages sent by premium users.
|
||||
/// </summary>
|
||||
public class FromPremiumUserAttribute()
|
||||
: MessageFilterAttribute(new FromPremiumUserFilter())
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the text starts with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the message text should start with</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
public class TextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
|
||||
: MessageFilterAttribute(new TextStartsWithFilter(content, comparison))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the text ends with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the message text should end with</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
public class TextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
|
||||
: MessageFilterAttribute(new TextEndsWithFilter(content, comparison))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the text contains the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the message text should contain</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
public class TextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
|
||||
: MessageFilterAttribute(new TextContainsFilter(content, comparison))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the text equals the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the message text should equal</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
public class TextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
|
||||
: MessageFilterAttribute(new TextEqualsFilter(content, comparison))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that contain any non-empty text.
|
||||
/// </summary>
|
||||
public class HasTextAttribute()
|
||||
: MessageFilterAttribute(new TextNotNullOrEmptyFilter())
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that are replies to messages containing mentions.
|
||||
/// Allows handlers to respond to messages that reply to messages with specific mentions.
|
||||
/// </summary>
|
||||
public class RepliedMentionedAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RepliedMentionedAttribute that matches replies to any mention.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of the reply chain to check (default: 1).</param>
|
||||
public RepliedMentionedAttribute(int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(MessageEntityType.Mention, 0, null, replyDepth), new RepliedMentionedFilter(replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RepliedMentionedAttribute that matches replies to mentions at a specific offset.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset position where the mention should occur in the replied message.</param>
|
||||
/// <param name="replyDepth">The depth of the reply chain to check (default: 1).</param>
|
||||
public RepliedMentionedAttribute(int offset, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(MessageEntityType.Mention, offset, null, replyDepth), new RepliedMentionedFilter(replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RepliedMentionedAttribute that matches replies to a specific mention.
|
||||
/// </summary>
|
||||
/// <param name="mention">The specific mention text to match in the replied message.</param>
|
||||
/// <param name="replyDepth">The depth of the reply chain to check (default: 1).</param>
|
||||
public RepliedMentionedAttribute(string mention, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(MessageEntityType.Mention, replyDepth), new RepliedMentionedFilter(mention, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the RepliedMentionedAttribute that matches replies to a specific mention at a specific offset.
|
||||
/// </summary>
|
||||
/// <param name="mention">The specific mention text to match in the replied message.</param>
|
||||
/// <param name="offset">The offset position where the mention should occur in the replied message.</param>
|
||||
/// <param name="replyDepth">The depth of the reply chain to check (default: 1).</param>
|
||||
public RepliedMentionedAttribute(string mention, int offset, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(MessageEntityType.Mention, offset, null, replyDepth), new RepliedMentionedFilter(mention, replyDepth)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent in a forum chat.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedChatIsForumAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedMessageChatIsForumFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent in a specific chat by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The chat ID to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedChatIdAttribute(long id, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedMessageChatIdFilter(id, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent in a chat of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="type">The chat type to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedChatTypeAttribute(ChatType type, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedMessageChatTypeFilter(type, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat title of the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedChatTitleAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with a specific title and comparison method.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatTitleAttribute(string? title, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatTitleFilter(title, comparison, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with a specific title.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatTitleAttribute(string? title, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatTitleFilter(title, StringComparison.InvariantCulture, replyDepth)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat username of the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedChatUsernameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with a specific username and comparison method.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatUsernameAttribute(string? userName, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatUsernameFilter(userName, comparison, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with a specific username.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatUsernameAttribute(string? userName, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatUsernameFilter(userName, replyDepth)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the chat name of the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedChatNameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatNameAttribute(string? firstName, string? lastName, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatNameFilter(firstName, lastName, comparison, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a chat with specific first and last names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedChatNameAttribute(string? firstName, string? lastName, int replyDepth = 1)
|
||||
: base(new RepliedMessageChatNameFilter(firstName, lastName, StringComparison.InvariantCulture, replyDepth)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages that are replies to other messages.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class MessageRepliedAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new MessageRepliedFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message contains dice throws with specific values.
|
||||
/// </summary>
|
||||
public class RepliedDiceThrowedAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message contains a dice throw with a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The dice value to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedDiceThrowedAttribute(int value, int replyDepth = 1)
|
||||
: base(new RepliedDiceThrowedFilter(value, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message contains a dice throw with a specific type and value.
|
||||
/// </summary>
|
||||
/// <param name="diceType">The type of dice</param>
|
||||
/// <param name="value">The dice value to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedDiceThrowedAttribute(DiceType diceType, int value, int replyDepth = 1)
|
||||
: base(new RepliedDiceThrowedFilter(diceType, value, replyDepth)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was automatically forwarded.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedIsAutomaticFormwardMessageAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedIsAutomaticFormwardMessageFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent while the user was offline.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedIsFromOfflineMessageAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedIsFromOfflineMessageFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message is a service message.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedIsServiceMessageMessageAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedIsServiceMessageMessageFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message is a topic message in forum chats.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedIsTopicMessageMessageAttribut(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedIsServiceMessageMessageFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on entities in the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedMessageHasEntityAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message has a specific entity type.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedMessageHasEntityAttribute(MessageEntityType type, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(type, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message has a specific entity type at a specific position.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="offset">The starting position of the entity</param>
|
||||
/// <param name="length">The length of the entity (optional)</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedMessageHasEntityAttribute(MessageEntityType type, int offset, int? length, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(type, offset, length, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message has a specific entity type with specific content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="content">The content that the entity should contain</param>
|
||||
/// <param name="stringComparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedMessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(type, content, stringComparison, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message has a specific entity type at a specific position with specific content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to match</param>
|
||||
/// <param name="offset">The starting position of the entity</param>
|
||||
/// <param name="length">The length of the entity (optional)</param>
|
||||
/// <param name="content">The content that the entity should contain</param>
|
||||
/// <param name="stringComparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedMessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture, int replyDepth = 1)
|
||||
: base(new RepliedMessageHasEntityFilter(type, offset, length, content, stringComparison, replyDepth)) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the username of the sender of the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedFromUsernameAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a specific username.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUsernameAttribute(string username, int replyDepth = 1)
|
||||
: base(new RepliedUsernameFilter(username, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a specific username with custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUsernameAttribute(string username, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedUsernameFilter(username, comparison, replyDepth)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the name of the sender of the replied-to message.
|
||||
/// </summary>
|
||||
public class RepliedFromUserAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a user with specific names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match (optional)</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUserAttribute(string firstName, string? lastName, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedUserFilter(firstName, lastName, comparison, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a user with specific names.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="lastName">The last name to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUserAttribute(string firstName, string lastName, int replyDepth = 1)
|
||||
: base(new RepliedUserFilter(firstName, lastName, StringComparison.InvariantCulture, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a user with a specific first name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUserAttribute(string firstName, int replyDepth = 1)
|
||||
: base(new RepliedUserFilter(firstName, null, StringComparison.InvariantCulture, replyDepth)) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute to filter messages where the replied-to message is from a user with a specific first name and custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to match</param>
|
||||
/// <param name="comparison">The string comparison method</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public RepliedFromUserAttribute(string firstName, StringComparison comparison, int replyDepth = 1)
|
||||
: base(new RepliedUserFilter(firstName, null, comparison, replyDepth)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages based on the user ID of the sender of the replied-to message.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to match</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedUserIdAttribute(long userId, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedUserIdFilter(userId, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent by a bot.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class ReplyFromBotAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new ReplyFromBotFilter(replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages where the replied-to message was sent by a premium user.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class ReplyFromPremiumUserAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new ReplyFromPremiumUserFilter(replyDepth))
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates where the replied-to message's text starts with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the replied message's text should start with</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedTextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedTextStartsWithFilter(content, comparison, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates where the replied-to message's text ends with the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the replied message's text should end with</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedTextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedTextEndsWithFilter(content, comparison, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates where the replied-to message's text contains the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the replied message's text should contain</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedTextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedTextContainsFilter(content, comparison, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates where the replied-to message's text equals the specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The string that the replied message's text should equal</param>
|
||||
/// <param name="comparison">The string comparison type</param>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedTextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedTextEqualsFilter(content, comparison, replyDepth))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for filtering updates where the replied-to message contains any non-empty text.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">How many levels up the reply chain to check (default: 1)</param>
|
||||
public class RepliedHasTextAttribute(int replyDepth = 1)
|
||||
: MessageFilterAttribute(new RepliedTextNotNullOrEmptyFilter(replyDepth))
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering messages with reply to messages of this bot.
|
||||
/// </summary>
|
||||
public class RepliedToMeAttribute()
|
||||
: MessageFilterAttribute(new RepliedToMeFilter())
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for managing enum-based states in Telegram bot handlers.
|
||||
/// Provides a convenient way to associate enum values with state management functionality.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
|
||||
public class EnumStateAttribute<TEnum> : StateKeeperAttribute<long, TEnum, EnumStateKeeper<TEnum>> where TEnum : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a special state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to be managed.</param>
|
||||
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
|
||||
public EnumStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The specific enum state to be managed.</param>
|
||||
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
|
||||
public EnumStateAttribute(TEnum myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a special state and default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to be managed.</param>
|
||||
public EnumStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The specific enum state to be managed.</param>
|
||||
public EnumStateAttribute(TEnum myState)
|
||||
: this(myState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for associating a handler or method with a numeric (integer) state keeper.
|
||||
/// Provides constructors for flexible state and key resolver configuration.
|
||||
/// </summary>
|
||||
public class NumericStateAttribute : StateKeeperAttribute<long, int, NumericStateKeeper>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public NumericStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific numeric state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The integer state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public NumericStateAttribute(int myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
public NumericStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific numeric state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The integer state to associate</param>
|
||||
public NumericStateAttribute(int myState)
|
||||
: this(myState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents special states for state keeping logic.
|
||||
/// </summary>
|
||||
public enum SpecialState
|
||||
{
|
||||
/// <summary>
|
||||
/// No special state.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Indicates that no state is present.
|
||||
/// </summary>
|
||||
NoState,
|
||||
/// <summary>
|
||||
/// Indicates that any state is acceptable.
|
||||
/// </summary>
|
||||
AnyState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for associating a handler or method with a string-based state keeper.
|
||||
/// Provides various constructors for flexible state and key resolver configuration.
|
||||
/// </summary>
|
||||
public class StringStateAttribute : StateKeeperAttribute<long, string, StringStateKeeper>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public StringStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public StringStateAttribute(string myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
public StringStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
public StringStateAttribute(string myState)
|
||||
: base(myState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state, a custom key resolver, and a set of possible states.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
/// <param name="states">The set of possible string states</param>
|
||||
public StringStateAttribute(string myState, IStateKeyResolver<long> keyResolver, params string[] states)
|
||||
: base(new StringStateKeeper(states), myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state, a custom key resolver, and a set of possible states.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
/// <param name="states">The set of possible string states</param>
|
||||
public StringStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver, params string[] states)
|
||||
: base(new StringStateKeeper(states), specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state, the default sender ID resolver, and a set of possible states.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
/// <param name="states">The set of possible string states</param>
|
||||
public StringStateAttribute(string myState, params string[] states)
|
||||
: base(new StringStateKeeper(states), myState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state, the default sender ID resolver, and a set of possible states.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="states">The set of possible string states</param>
|
||||
public StringStateAttribute(SpecialState specialState, params string[] states)
|
||||
: base(new StringStateKeeper(states), specialState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering message with command "start" in bot's private chats.
|
||||
/// Allows handlers to respond to "welcome" bot commands.
|
||||
/// </summary>
|
||||
public class WelcomeAttribute : MessageFilterAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates new instance of <see cref="WelcomeAttribute"/>
|
||||
/// </summary>
|
||||
public WelcomeAttribute() : base(new MessageChatTypeFilter(ChatType.Private), new CommandAlliasFilter("start"))
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Attributes.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the state in which the <see cref="UpdateHandlerBase"/> can be executed
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public abstract class StateKeeperAttributeBase : Attribute, IFilter<Update>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool IsCollectible => this.HasPublicProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance <see cref="StateKeeperBase{TKey, TState}"/>
|
||||
/// </summary>
|
||||
/// <param name="stateKeeperType"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected StateKeeperAttributeBase(Type stateKeeperType)
|
||||
{
|
||||
if (!stateKeeperType.IsAssignableToGenericType(typeof(StateKeeperBase<,>)))
|
||||
throw new ArgumentException(stateKeeperType + " is not a StateKeeperBase", nameof(stateKeeperType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Realizes a <see cref="IFilter{T}"/> for validation of the current <see cref="StateKeeperBase{TKey, TState}"/> in the polling routing
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanPass(FilterExecutionContext<Update> context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Attributes.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="IFilter{T}"/> to <see cref="Update"/> validation for entry into execution of the <see cref="UpdateHandlerBase"/>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public abstract class UpdateFilterAttributeBase : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UpdateType"/>'s that <see cref="UpdateHandlerBase"/> processing
|
||||
/// </summary>
|
||||
public abstract UpdateType[] AllowedTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing
|
||||
/// </summary>
|
||||
public abstract Filter<Update> AnonymousFilter { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filter modifiers that affect how this filter is combined with others.
|
||||
/// </summary>
|
||||
public FilterModifier Modifiers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected internal UpdateFilterAttributeBase()
|
||||
{
|
||||
if (AllowedTypes.Length == 0)
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the logic of filter modifiers. Exceptionally internal implementation</summary>
|
||||
/// <param name="previous"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool ProcessModifiers(UpdateFilterAttributeBase? previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Attributes.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="UpdateType"/>'s and validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public abstract class UpdateHandlerAttributeBase : Attribute, IFilter<Update>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool IsCollectible => this.HasPublicProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of <see cref="UpdateHandlerBase"/> that this attribute can be attached to
|
||||
/// </summary>
|
||||
public Type[] ExpectingHandlerType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="UpdateType"/> that handlers processes
|
||||
/// </summary>
|
||||
public UpdateType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets concurrency of this <see cref="UpdateHandlerBase"/> in same <see cref="UpdateType"/> pool
|
||||
/// </summary>
|
||||
public int Concurrency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
|
||||
/// </summary>
|
||||
/// <param name="expectingHandlerType"></param>
|
||||
/// <param name="updateType"></param>
|
||||
/// <param name="concurrency"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int concurrency = 0)
|
||||
{
|
||||
if (expectingHandlerType == null)
|
||||
throw new ArgumentNullException(nameof(expectingHandlerType));
|
||||
|
||||
if (expectingHandlerType.Any(type => !type.IsHandlerAbstract()))
|
||||
throw new ArgumentException("One of expectingHandlerType is not a handler type", nameof(expectingHandlerType));
|
||||
|
||||
if (updateType == UpdateType.Unknown)
|
||||
throw new Exception();
|
||||
|
||||
ExpectingHandlerType = expectingHandlerType;
|
||||
Type = updateType;
|
||||
Concurrency = concurrency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="DescriptorIndexer"/> of this <see cref="UpdateHandlerAttributeBase"/> from <see cref="Concurrency"/> and <see cref="Priority"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DescriptorIndexer GetIndexer()
|
||||
=> new DescriptorIndexer(0, this);
|
||||
|
||||
/// <summary>
|
||||
/// Validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanPass(FilterExecutionContext<Update> context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that prevents a class from being automatically collected by the handler collection system.
|
||||
/// When applied to a class, it will be excluded from domain-wide handler collection operations.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class DontCollectAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of filter modifiers that can be applied to update filters.
|
||||
/// Defines how filters should be combined and applied in filter chains.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FilterModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// No modifier applied. Filter is applied as-is.
|
||||
/// </summary>
|
||||
None = 1,
|
||||
|
||||
/// <summary>
|
||||
/// OR modifier. This filter or the next filter in the chain should match.
|
||||
/// </summary>
|
||||
OrNext = 2,
|
||||
|
||||
/// <summary>
|
||||
/// NOT modifier. The inverse of this filter should match.
|
||||
/// </summary>
|
||||
Not = 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that says if this handler cn await some of await types, that is not listed by its handler base
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class MightAwaitAttribute : Attribute
|
||||
{
|
||||
private readonly UpdateType[] _updateTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Update types that may be awaited
|
||||
/// </summary>
|
||||
public UpdateType[] UpdateTypes => _updateTypes;
|
||||
|
||||
/// <summary>
|
||||
/// main ctor of <see cref="MightAwaitAttribute"/>
|
||||
/// </summary>
|
||||
/// <param name="updateTypes"></param>
|
||||
public MightAwaitAttribute(params UpdateType[] updateTypes)
|
||||
=> _updateTypes = updateTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract attribute for associating a handler or method with a state keeper.
|
||||
/// Provides logic for state-based filtering and state management.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used for state keeping (e.g., chat ID).</typeparam>
|
||||
/// <typeparam name="TState">The type of the state value (e.g., string, int).</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper implementation.</typeparam>
|
||||
public abstract class StateKeeperAttribute<TKey, TState, TKeeper> : StateKeeperAttributeBase where TKey : notnull where TState : notnull where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the singleton instance of the state keeper for this attribute type.
|
||||
/// </summary>
|
||||
public static TKeeper StateKeeper { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value associated with this attribute instance.
|
||||
/// </summary>
|
||||
public TState MyState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special state mode for this attribute instance.
|
||||
/// </summary>
|
||||
public SpecialState SpecialState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The state value to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(TState myState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = myState;
|
||||
SpecialState = SpecialState.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state mode</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = StateKeeper.DefaultState;
|
||||
SpecialState = specialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a custom state keeper, a specific state, and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="keeper">The state keeper instance</param>
|
||||
/// <param name="myState">The state value to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(TKeeper keeper, TState myState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
StateKeeper ??= keeper;
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = myState;
|
||||
SpecialState = SpecialState.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a custom state keeper, a special state, and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="keeper">The state keeper instance</param>
|
||||
/// <param name="specialState">The special state mode</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(TKeeper keeper, SpecialState specialState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
StateKeeper ??= keeper;
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = StateKeeper.DefaultState;
|
||||
SpecialState = specialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current update context passes the state filter.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context</param>
|
||||
/// <returns>True if the state matches the filter; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context)
|
||||
{
|
||||
if (SpecialState == SpecialState.AnyState)
|
||||
return true;
|
||||
|
||||
if (!StateKeeper.TryGetState(context.Input, out TState? state))
|
||||
return SpecialState == SpecialState.NoState;
|
||||
|
||||
if (state == null)
|
||||
return false;
|
||||
|
||||
return MyState.Equals(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base attribute for defining update filters for a specific type of update target.
|
||||
/// Provides logic for filter composition, modifier processing, and target extraction.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the update target to filter (e.g., Message, Update).</typeparam>
|
||||
public abstract class UpdateFilterAttribute<T> : UpdateFilterAttributeBase where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the compiled anonymous filter for this attribute.
|
||||
/// </summary>
|
||||
public override Filter<Update> AnonymousFilter { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compiled filter logic for the update target.
|
||||
/// </summary>
|
||||
public Filter<T> UpdateFilter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with one or more filters for the update target.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to compose</param>
|
||||
protected UpdateFilterAttribute(params IFilter<T>[] filters)
|
||||
{
|
||||
UpdateFilter = CompiledFilter<T>.Compile(filters);
|
||||
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a precompiled filter for the update target.
|
||||
/// </summary>
|
||||
/// <param name="updateFilter">The compiled filter</param>
|
||||
protected UpdateFilterAttribute(Filter<T> updateFilter)
|
||||
{
|
||||
UpdateFilter = updateFilter;
|
||||
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes filter modifiers and combines this filter with the previous one if needed.
|
||||
/// </summary>
|
||||
/// <param name="previous">The previous filter attribute in the chain</param>
|
||||
/// <returns>True if the OrNext modifier is set; otherwise, false.</returns>
|
||||
public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous)
|
||||
{
|
||||
if (Modifiers.HasFlag(FilterModifier.Not))
|
||||
AnonymousFilter = AnonymousFilter.Not();
|
||||
|
||||
if (previous is not null)
|
||||
{
|
||||
if (previous.Modifiers.HasFlag(FilterModifier.OrNext))
|
||||
{
|
||||
AnonymousFilter = previous.AnonymousFilter.Or(AnonymousFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return Modifiers.HasFlag(FilterModifier.OrNext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the filtering target of type <typeparamref name="T"/> from the given update.
|
||||
/// </summary>
|
||||
/// <param name="update">The Telegram update</param>
|
||||
/// <returns>The target object to filter, or null if not applicable</returns>
|
||||
public abstract T? GetFilterringTarget(Update update);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base attribute for marking update handler classes.
|
||||
/// Provides a type-safe way to associate handler types with specific update types and concurrency settings.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the update handler that this attribute is applied to.</typeparam>
|
||||
/// <param name="updateType">The type of update that this handler can process.</param>
|
||||
/// <param name="concurrency">The concurrency level for this handler (default: 0 for unlimited).</param>
|
||||
public abstract class UpdateHandlerAttribute<T>(UpdateType updateType, int concurrency = 0)
|
||||
: UpdateHandlerAttributeBase([typeof(T)], updateType, concurrency) where T : UpdateHandlerBase
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Telegrator.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for configuring handler collection behavior.
|
||||
/// Defines options that control how handlers are collected and processed during initialization.
|
||||
/// </summary>
|
||||
public interface IHandlersCollectingOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default)
|
||||
/// </summary>
|
||||
public bool DescendDescriptorIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to exclude intersecting command aliases.
|
||||
/// </summary>
|
||||
public bool ExceptIntersectingCommandAliases { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for providing bot information and metadata.
|
||||
/// Contains information about the bot user and provides initialization capabilities.
|
||||
/// </summary>
|
||||
public interface ITelegramBotInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="User"/> representing the bot.
|
||||
/// </summary>
|
||||
public User User { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Telegrator.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for Telegram bot behavior and execution settings.
|
||||
/// Controls various aspects of bot operation including concurrency, routing, and execution policies.
|
||||
/// </summary>
|
||||
public class TelegramBotOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether only the first found handler should be executed for each update.
|
||||
/// </summary>
|
||||
public bool ExecuteOnlyFirstFoundHanlder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of parallel working handlers. Null means no limit.
|
||||
/// </summary>
|
||||
public int? MaximumParallelWorkingHandlers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether awaiting handlers should be routed separately from regular handlers.
|
||||
/// </summary>
|
||||
public bool ExclusiveAwaitingHandlerRouting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the global cancellation token for all bot operations.
|
||||
/// </summary>
|
||||
public CancellationToken GlobalCancellationToken { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Telegrator
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of dice types supported by Telegram.
|
||||
/// Used for filtering dice messages and determining dice emoji representations.
|
||||
/// </summary>
|
||||
public enum DiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard dice (🎲).
|
||||
/// </summary>
|
||||
Dice,
|
||||
|
||||
/// <summary>
|
||||
/// Darts (🎯).
|
||||
/// </summary>
|
||||
Darts,
|
||||
|
||||
/// <summary>
|
||||
/// Bowling (🎳).
|
||||
/// </summary>
|
||||
Bowling,
|
||||
|
||||
/// <summary>
|
||||
/// Basketball (🏀).
|
||||
/// </summary>
|
||||
Basketball,
|
||||
|
||||
/// <summary>
|
||||
/// Football (⚽).
|
||||
/// </summary>
|
||||
Football,
|
||||
|
||||
/// <summary>
|
||||
/// Casino slot machine (🎰).
|
||||
/// </summary>
|
||||
Casino
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when attempting to modify a frozen collection.
|
||||
/// </summary>
|
||||
public class CollectionFrozenException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionFrozenException"/> class.
|
||||
/// </summary>
|
||||
public CollectionFrozenException()
|
||||
: base("Can't change a frozen collection.") { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a type is not a valid filter type.
|
||||
/// </summary>
|
||||
public class NotFilterTypeException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotFilterTypeException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type that is not a filter type.</param>
|
||||
public NotFilterTypeException(Type type)
|
||||
: base(string.Format("\"{0}\" is not a filter type", type.Name)) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a handler execution fails.
|
||||
/// Contains information about the handler and the inner exception.
|
||||
/// </summary>
|
||||
public class HandlerFaultedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The handler info associated with the faulted handler.
|
||||
/// </summary>
|
||||
public readonly DescribedHandlerInfo HandlerInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerFaultedException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handlerInfo">The handler info.</param>
|
||||
/// <param name="inner">The inner exception.</param>
|
||||
public HandlerFaultedException(DescribedHandlerInfo handlerInfo, Exception inner)
|
||||
: base(string.Format("Handler's \"{0}\" execution was faulted", handlerInfo.DisplayString), inner)
|
||||
{
|
||||
HandlerInfo = handlerInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter that checks if a command matches any of the specified aliases.
|
||||
/// Requires a <see cref="CommandHandlerAttribute"/> to be applied first to extract the command.
|
||||
/// </summary>
|
||||
/// <param name="alliases">The command aliases to check against.</param>
|
||||
public class CommandAlliasFilter(params string[] alliases) : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the command that was received and extracted by the <see cref="CommandHandlerAttribute"/>.
|
||||
/// </summary>
|
||||
public string ReceivedCommand { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the received command matches any of the specified aliases.
|
||||
/// This filter requires a <see cref="CommandHandlerAttribute"/> to be applied first
|
||||
/// to extract the command from the message.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the completed filters.</param>
|
||||
/// <returns>True if the command matches any of the specified aliases; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
ReceivedCommand = context.CompletedFilters.Get<CommandHandlerAttribute>(0).ReceivedCommand;
|
||||
return alliases.Contains(ReceivedCommand, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compiled filter that applies a set of filters to an anonymous target type.
|
||||
/// </summary>
|
||||
public sealed class AnonymousCompiledFilter : AnonymousTypeFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filterAction">The filter action delegate.</param>
|
||||
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
|
||||
private AnonymousCompiledFilter(Func<FilterExecutionContext<Update>, object, bool> filterAction, Func<Update, object?> getFilterringTarget)
|
||||
: base(filterAction, getFilterringTarget) { }
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the filtering target.</typeparam>
|
||||
/// <param name="filters">The list of filters to compile.</param>
|
||||
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
|
||||
/// <returns>The compiled filter.</returns>
|
||||
public static AnonymousCompiledFilter Compile<T>(IList<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
|
||||
{
|
||||
return new AnonymousCompiledFilter(
|
||||
(context, filterringTarget) => CanPassInternal(filters, context, filterringTarget),
|
||||
getFilterringTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all filters can pass for the given context and filtering target.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the filtering target.</typeparam>
|
||||
/// <param name="filters">The list of filters.</param>
|
||||
/// <param name="updateContext">The filter execution context.</param>
|
||||
/// <param name="filterringTarget">The filtering target.</param>
|
||||
/// <returns>True if all filters pass; otherwise, false.</returns>
|
||||
private static bool CanPassInternal<T>(IList<IFilter<T>> filters, FilterExecutionContext<Update> updateContext, object filterringTarget) where T : class
|
||||
{
|
||||
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
|
||||
|
||||
foreach (IFilter<T> filter in filters)
|
||||
{
|
||||
if (!filter.CanPass(context))
|
||||
return false;
|
||||
|
||||
context.CompletedFilters.Add(filter);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a filter that applies a filter action to an anonymous target type extracted from an update.
|
||||
/// </summary>
|
||||
public class AnonymousTypeFilter : Filter<Update>
|
||||
{
|
||||
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
|
||||
private readonly Func<Update, object?> GetFilterringTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filterAction">The filter action delegate.</param>
|
||||
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
|
||||
protected AnonymousTypeFilter(Func<FilterExecutionContext<Update>, object, bool> filterAction, Func<Update, object?> getFilterringTarget)
|
||||
{
|
||||
FilterAction = filterAction;
|
||||
GetFilterringTarget = getFilterringTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a filter for a specific target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the filtering target.</typeparam>
|
||||
/// <param name="filter">The filter to apply.</param>
|
||||
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
|
||||
/// <returns>The compiled filter.</returns>
|
||||
public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
|
||||
{
|
||||
return new AnonymousTypeFilter(
|
||||
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget),
|
||||
getFilterringTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context and filtering target.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the filtering target.</typeparam>
|
||||
/// <param name="updateContext">The filter execution context.</param>
|
||||
/// <param name="filter">The filter to apply.</param>
|
||||
/// <param name="filterringTarget">The filtering target.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IFilter<T> filter, object filterringTarget) where T : class
|
||||
{
|
||||
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
|
||||
if (!filter.CanPass(context))
|
||||
return false;
|
||||
|
||||
context.CompletedFilters.Add(filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context by extracting the filtering target and applying the filter action.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
|
||||
if (filterringTarget == null)
|
||||
return false;
|
||||
|
||||
return FilterAction.Invoke(context, filterringTarget);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a filter that composes multiple filters and passes only if all of them pass.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class CompiledFilter<T> : Filter<T> where T : class
|
||||
{
|
||||
private readonly IFilter<T>[] Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to compose.</param>
|
||||
private CompiledFilter(IFilter<T>[] filters)
|
||||
{
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles multiple filters into a <see cref="CompiledFilter{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to compose.</param>
|
||||
/// <returns>A new <see cref="CompiledFilter{T}"/> instance.</returns>
|
||||
public static CompiledFilter<T> Compile(params IFilter<T>[] filters)
|
||||
{
|
||||
return new CompiledFilter<T>(filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all composed filters pass for the given context.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if all filters pass; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
{
|
||||
foreach (IFilter<T> filter in Filters)
|
||||
{
|
||||
if (!filter.CanPass(context))
|
||||
return false;
|
||||
|
||||
context.CompletedFilters.Add(filter);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// The list containing filters worked out during Polling to further obtain additional filtering information
|
||||
/// </summary>
|
||||
public class CompletedFiltersList : IEnumerable<IFilterCollectable>
|
||||
{
|
||||
private readonly List<IFilterCollectable> CompletedFilters = [];
|
||||
|
||||
/// <summary>
|
||||
/// Adds the completed filter to the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update.</typeparam>
|
||||
/// <param name="filter">The filter to add.</param>
|
||||
public void Add<TUpdate>(IFilter<TUpdate> filter) where TUpdate : class
|
||||
{
|
||||
if (filter is AnonymousTypeFilter | filter is AnonymousCompiledFilter)
|
||||
return;
|
||||
|
||||
if (!filter.IsCollectible)
|
||||
return;
|
||||
|
||||
CompletedFilters.Add(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds many completed filters to the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update.</typeparam>
|
||||
/// <param name="filters">The filters to add.</param>
|
||||
public void AddRange<TUpdate>(IEnumerable<IFilter<TUpdate>> filters) where TUpdate : class
|
||||
{
|
||||
foreach (IFilter<TUpdate> filter in filters)
|
||||
Add(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for filters of a given type in the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
|
||||
/// <returns>The enumerable containing filters of the given type.</returns>
|
||||
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
|
||||
public IEnumerable<TFilter> Get<TFilter>() where TFilter : notnull, IFilterCollectable
|
||||
{
|
||||
if (!typeof(TFilter).IsFilterType())
|
||||
throw new NotFilterTypeException(typeof(TFilter));
|
||||
|
||||
return CompletedFilters.WhereCast<TFilter>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a filter of a given type at the specified index in the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
|
||||
/// <param name="index">The index of the filter.</param>
|
||||
/// <returns>The filter of the given type at the specified index.</returns>
|
||||
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
|
||||
/// <exception cref="KeyNotFoundException">Thrown if no filter is found at the index.</exception>
|
||||
public TFilter Get<TFilter>(int index) where TFilter : notnull, IFilterCollectable
|
||||
{
|
||||
IEnumerable<TFilter> filters = Get<TFilter>();
|
||||
return filters.Any() ? filters.ElementAt(index) : throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a filter of a given type at the specified index, or null if it does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
|
||||
/// <param name="index">The index of the filter.</param>
|
||||
/// <returns>The filter at the specified index, or null if it does not exist.</returns>
|
||||
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
|
||||
public TFilter? GetOrDefault<TFilter>(int index) where TFilter : IFilterCollectable
|
||||
{
|
||||
IEnumerable<TFilter> filters = Get<TFilter>();
|
||||
return filters.Any() ? filters.ElementAt(index) : default;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<IFilterCollectable> GetEnumerator() => CompletedFilters.GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => CompletedFilters.GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Configuration;
|
||||
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the context for filter execution, including update, input, and additional data.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class FilterExecutionContext<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
|
||||
/// </summary>
|
||||
public ITelegramBotInfo BotInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the additional data dictionary for the context.
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of completed filters for the context.
|
||||
/// </summary>
|
||||
public CompletedFiltersList CompletedFilters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Update"/> being processed.
|
||||
/// </summary>
|
||||
public Update Update { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UpdateType"/> of the update.
|
||||
/// </summary>
|
||||
public UpdateType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input object for the filter.
|
||||
/// </summary>
|
||||
public T Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters.
|
||||
/// </summary>
|
||||
/// <param name="botInfo">The bot info.</param>
|
||||
/// <param name="update">The update.</param>
|
||||
/// <param name="input">The input object.</param>
|
||||
/// <param name="data">The additional data dictionary.</param>
|
||||
/// <param name="completedFilters">The list of completed filters.</param>
|
||||
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
|
||||
{
|
||||
BotInfo = botInfo;
|
||||
Data = data;
|
||||
CompletedFilters = completedFilters;
|
||||
Update = update;
|
||||
Type = update.Type;
|
||||
Input = input;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters.
|
||||
/// </summary>
|
||||
/// <param name="botInfo">The bot info.</param>
|
||||
/// <param name="update">The update.</param>
|
||||
/// <param name="input">The input object.</param>
|
||||
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input)
|
||||
: this(botInfo, update, input, [], []) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a child context for a different input type, sharing the same data and completed filters.
|
||||
/// </summary>
|
||||
/// <typeparam name="C">The type of the new input.</typeparam>
|
||||
/// <param name="input">The new input object.</param>
|
||||
/// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns>
|
||||
public FilterExecutionContext<C> CreateChild<C>(C input) where C : class
|
||||
=> new FilterExecutionContext<C>(BotInfo, Update, input, Data, CompletedFilters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for filters that can be collected into a completed filters list.
|
||||
/// Provides information about whether a filter should be tracked during execution.
|
||||
/// </summary>
|
||||
public interface IFilterCollectable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets if filter can be collected to <see cref="CompletedFiltersList"/>
|
||||
/// </summary>
|
||||
public bool IsCollectible { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a filter for a specific update type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the update to filter.</typeparam>
|
||||
public interface IFilter<T> : IFilterCollectable where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context.
|
||||
/// </summary>
|
||||
/// <param name="info">The filter execution context.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
public bool CanPass(FilterExecutionContext<T> info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Telegrator.Filters.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a filter that joins multiple filters together.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public interface IJoinedFilter<T> : IFilter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the array of joined filters.
|
||||
/// </summary>
|
||||
public IFilter<T>[] Filters { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Diagnostics;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for filters that operate based on the current environment.
|
||||
/// Provides functionality to detect debug vs release environments.
|
||||
/// </summary>
|
||||
public abstract class EnvironmentFilter : Filter<Update>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current environment is debug mode.
|
||||
/// This is set during static initialization based on the DEBUG conditional compilation symbol.
|
||||
/// </summary>
|
||||
protected static bool IsCurrentEnvDebug { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor that initializes the environment detection.
|
||||
/// </summary>
|
||||
static EnvironmentFilter()
|
||||
=> SetIsCurrentEnvDebug();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug environment flag. This method is only compiled in DEBUG builds.
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
private static void SetIsCurrentEnvDebug()
|
||||
=> IsCurrentEnvDebug = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that only passes in debug environment builds.
|
||||
/// </summary>
|
||||
public class IsDebugEnvironmentFilter() : EnvironmentFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current environment is debug mode.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the current environment is debug mode; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> _)
|
||||
=> IsCurrentEnvDebug;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that only passes in release environment builds.
|
||||
/// </summary>
|
||||
public class IsReleaseEnvironmentFilter() : EnvironmentFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current environment is release mode.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the current environment is release mode; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> _)
|
||||
=> !IsCurrentEnvDebug;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks environment variable values.
|
||||
/// </summary>
|
||||
/// <param name="variable">The environment variable name to check.</param>
|
||||
/// <param name="value">The expected value of the environment variable (optional).</param>
|
||||
/// <param name="comparison">The string comparison type to use for value matching.</param>
|
||||
public class EnvironmentVariableFilter(string variable, string? value, StringComparison comparison) : Filter<Update>
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable name to check.
|
||||
/// </summary>
|
||||
private readonly string _variable = variable;
|
||||
|
||||
/// <summary>
|
||||
/// The expected value of the environment variable (optional).
|
||||
/// </summary>
|
||||
private readonly string? _value = value;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison type to use for value matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _comparison = comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnvironmentVariableFilter"/> class with a specific value.
|
||||
/// </summary>
|
||||
/// <param name="variable">The environment variable name to check.</param>
|
||||
/// <param name="value">The expected value of the environment variable.</param>
|
||||
public EnvironmentVariableFilter(string variable, string? value)
|
||||
: this(variable, value, StringComparison.InvariantCulture) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnvironmentVariableFilter"/> class that checks for non-null values.
|
||||
/// </summary>
|
||||
/// <param name="variable">The environment variable name to check.</param>
|
||||
public EnvironmentVariableFilter(string variable)
|
||||
: this(variable, "{NOT_NULL}", StringComparison.InvariantCulture) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnvironmentVariableFilter"/> class with custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="variable">The environment variable name to check.</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
public EnvironmentVariableFilter(string variable, StringComparison comparison)
|
||||
: this(variable, "{NOT_NULL}", comparison) { }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the environment variable matches the expected criteria.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the environment variable matches the criteria; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> _)
|
||||
{
|
||||
string? envValue = Environment.GetEnvironmentVariable(_variable);
|
||||
|
||||
if (envValue == null && _value == null)
|
||||
return true;
|
||||
|
||||
if (envValue == null)
|
||||
return false;
|
||||
|
||||
return envValue.Equals(_value, _comparison);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.Reflection;
|
||||
using Telegrator;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters, providing logical operations and collectability.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public abstract class Filter<T> : IFilter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a filter from a function.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter function.</param>
|
||||
/// <returns>A <see cref="Filter{T}"/> instance.</returns>
|
||||
public static Filter<T> If(Func<FilterExecutionContext<T>, bool> filter)
|
||||
=> new FunctionFilter<T>(filter);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a filter that always passes.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="AnyFilter{T}"/> instance.</returns>
|
||||
public static AnyFilter<T> Any()
|
||||
=> new AnyFilter<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a filter that inverts the result of this filter.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ReverseFilter{T}"/> instance.</returns>
|
||||
public Filter<T> Not()
|
||||
=> new ReverseFilter<T>(this);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a filter that passes only if both this and the specified filter pass.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter to combine with.</param>
|
||||
/// <returns>An <see cref="AndFilter{T}"/> instance.</returns>
|
||||
public AndFilter<T> And(IFilter<T> filter)
|
||||
=> new AndFilter<T>(this, filter);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a filter that passes if either this or the specified filter pass.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter to combine with.</param>
|
||||
/// <returns>An <see cref="OrFilter{T}"/> instance.</returns>
|
||||
public OrFilter<T> Or(IFilter<T> filter)
|
||||
=> new OrFilter<T>(this, filter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this filter is collectible.
|
||||
/// </summary>
|
||||
public bool IsCollectible => this.HasPublicProperties();
|
||||
|
||||
/// <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>
|
||||
public abstract bool CanPass(FilterExecutionContext<T> context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter that always passes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class AnyFilter<T> : Filter<T> where T : class
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
=> true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter that inverts the result of another filter.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class ReverseFilter<T> : Filter<T> where T : class
|
||||
{
|
||||
private readonly IFilter<T> filter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReverseFilter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="filter">The filter to invert.</param>
|
||||
public ReverseFilter(IFilter<T> filter)
|
||||
=> this.filter = filter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
=> !filter.CanPass(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter that uses a function to determine if it passes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class FunctionFilter<T> : Filter<T> where T : class
|
||||
{
|
||||
private readonly Func<FilterExecutionContext<T>, bool>? FilterFunc;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FunctionFilter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcFilter">The filter function.</param>
|
||||
public FunctionFilter(Func<FilterExecutionContext<T>, bool> funcFilter)
|
||||
=> FilterFunc = funcFilter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
=> context.Input != null && FilterFunc != null && FilterFunc(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters that join multiple filters together.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public abstract class JoinedFilter<T>(params IFilter<T>[] filters) : Filter<T>, IJoinedFilter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the array of joined filters.
|
||||
/// </summary>
|
||||
public IFilter<T>[] Filters { get; } = filters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter that passes only if both joined filters pass.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class AndFilter<T> : JoinedFilter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AndFilter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="leftFilter">The left filter.</param>
|
||||
/// <param name="rightFilter">The right filter.</param>
|
||||
public AndFilter(IFilter<T> leftFilter, IFilter<T> rightFilter)
|
||||
: base(leftFilter, rightFilter) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
=> Filters[0].CanPass(context) && Filters[1].CanPass(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter that passes if at least one of the joined filters passes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class OrFilter<T> : JoinedFilter<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OrFilter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="leftFilter">The left filter.</param>
|
||||
/// <param name="rightFilter">The right filter.</param>
|
||||
public OrFilter(IFilter<T> leftFilter, IFilter<T> rightFilter)
|
||||
: base(leftFilter, rightFilter) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
=> Filters[0].CanPass(context) || Filters[1].CanPass(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter that checks if a message contains a mention of the bot or a specific user.
|
||||
/// Requires a <see cref="MessageHasEntityFilter"/> to be applied first to identify mention entities.
|
||||
/// </summary>
|
||||
public class MentionedFilter : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// The username to check for in the mention (null means check for bot's username).
|
||||
/// </summary>
|
||||
private readonly string? Mention;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MentionedFilter"/> class that checks for bot mentions.
|
||||
/// </summary>
|
||||
public MentionedFilter()
|
||||
{
|
||||
Mention = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MentionedFilter"/> class that checks for specific user mentions.
|
||||
/// </summary>
|
||||
/// <param name="mention">The username to check for in the mention.</param>
|
||||
public MentionedFilter(string mention)
|
||||
{
|
||||
Mention = mention;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the message contains a mention of the specified user or bot.
|
||||
/// This filter requires a <see cref="MessageHasEntityFilter"/> to be applied first
|
||||
/// to identify mention entities in the message.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message and completed filters.</param>
|
||||
/// <returns>True if the message contains the specified mention; otherwise, false.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the bot username is null and no specific mention is provided.</exception>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (context.Input.Text == null)
|
||||
return false;
|
||||
|
||||
string userName = Mention ?? context.BotInfo.User.Username ?? throw new ArgumentNullException(nameof(context), "MentionedFilter requires BotInfo to be initialized");
|
||||
MessageHasEntityFilter entityFilter = context.CompletedFilters.Get<MessageHasEntityFilter>(0);
|
||||
return entityFilter.FoundEntities.Any(ent => context.Input.Text.Substring(ent.Offset + 1, ent.Length - 1) == userName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters that operate on the chat of the message being processed.
|
||||
/// </summary>
|
||||
public abstract class MessageChatFilter : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the chat of the message being processed.
|
||||
/// </summary>
|
||||
public Chat Chat { get; private set; } = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
Chat = context.Input.Chat;
|
||||
return CanPassNext(context.CreateChild(Chat));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter passes for the given chat context.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context for the chat.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
protected abstract bool CanPassNext(FilterExecutionContext<Chat> _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat is a forum.
|
||||
/// </summary>
|
||||
public class MessageChatIsForumFilter : MessageChatFilter
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
=> Chat.IsForum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat ID matches the specified value.
|
||||
/// </summary>
|
||||
public class MessageChatIdFilter(long id) : MessageChatFilter
|
||||
{
|
||||
private readonly long Id = id;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
=> Chat.Id == Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat type matches the specified value.
|
||||
/// </summary>
|
||||
public class MessageChatTypeFilter(ChatType type) : MessageChatFilter
|
||||
{
|
||||
private readonly ChatType Type = type;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
=> Chat.Type == Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat title matches the specified value.
|
||||
/// </summary>
|
||||
public class MessageChatTitleFilter : MessageChatFilter
|
||||
{
|
||||
private readonly string? Title;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatTitleFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match.</param>
|
||||
public MessageChatTitleFilter(string? title) => Title = title;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatTitleFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
public MessageChatTitleFilter(string? title, StringComparison comparison)
|
||||
: this(title) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
{
|
||||
if (Chat.Title == null)
|
||||
return false;
|
||||
|
||||
return Chat.Title.Equals(Title, Comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat username matches the specified value.
|
||||
/// </summary>
|
||||
public class MessageChatUsernameFilter : MessageChatFilter
|
||||
{
|
||||
private readonly string? UserName;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatUsernameFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match.</param>
|
||||
public MessageChatUsernameFilter(string? userName) => UserName = userName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatUsernameFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
public MessageChatUsernameFilter(string? userName, StringComparison comparison)
|
||||
: this(userName) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
{
|
||||
if (Chat.Username == null)
|
||||
return false;
|
||||
|
||||
return Chat.Username.Equals(UserName, Comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages whose chat first and/or last name matches the specified values.
|
||||
/// </summary>
|
||||
public class MessageChatNameFilter : MessageChatFilter
|
||||
{
|
||||
private readonly string? FirstName;
|
||||
private readonly string? LastName;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatNameFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The chat first name to match.</param>
|
||||
/// <param name="lastName">The chat last name to match.</param>
|
||||
public MessageChatNameFilter(string? firstName, string? lastName)
|
||||
{
|
||||
FirstName = firstName;
|
||||
LastName = lastName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageChatNameFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The chat first name to match.</param>
|
||||
/// <param name="lastName">The chat last name to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
public MessageChatNameFilter(string? firstName, string? lastName, StringComparison comparison)
|
||||
: this(firstName, lastName) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Chat> _)
|
||||
{
|
||||
if (LastName != null)
|
||||
{
|
||||
if (Chat.LastName == null)
|
||||
return false;
|
||||
|
||||
if (Chat.LastName.Equals(LastName, Comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FirstName != null)
|
||||
{
|
||||
if (Chat.FirstName == null)
|
||||
return false;
|
||||
|
||||
if (Chat.FirstName.Equals(FirstName, Comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters messages by their <see cref="MessageType"/>.
|
||||
/// </summary>
|
||||
public class MessageTypeFilter : Filter<Message>
|
||||
{
|
||||
private readonly MessageType type;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageTypeFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The message type to filter by.</param>
|
||||
public MessageTypeFilter(MessageType type) => this.type = type;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
=> context.Input.Type == type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages that are automatic forwards.
|
||||
/// </summary>
|
||||
public class IsAutomaticFormwardMessageFilter : Filter<Message>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
=> context.Input.IsAutomaticForward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages that are sent from offline.
|
||||
/// </summary>
|
||||
public class IsFromOfflineMessageFilter : Filter<Message>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
=> context.Input.IsFromOffline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters service messages (e.g., chat events).
|
||||
/// </summary>
|
||||
public class IsServiceMessageMessageFilter : Filter<Message>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
=> context.Input.IsServiceMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages that are topic messages.
|
||||
/// </summary>
|
||||
public class IsTopicMessageMessageFilter : Filter<Message>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
=> context.Input.IsTopicMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages by dice throw value and optionally by dice type.
|
||||
/// </summary>
|
||||
public class DiceThrowedFilter : Filter<Message>
|
||||
{
|
||||
private readonly DiceType? Dice;
|
||||
private readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DiceThrowedFilter"/> class for a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The dice value to filter by.</param>
|
||||
public DiceThrowedFilter(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DiceThrowedFilter"/> class for a specific dice type and value.
|
||||
/// </summary>
|
||||
/// <param name="diceType">The dice type to filter by.</param>
|
||||
/// <param name="value">The dice value to filter by.</param>
|
||||
public DiceThrowedFilter(DiceType diceType, int value) : this(value) => Dice = diceType;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (context.Input.Dice == null)
|
||||
return false;
|
||||
|
||||
if (Dice != null && context.Input.Dice.Emoji != GetEmojyForDiceType(Dice))
|
||||
return false;
|
||||
|
||||
return context.Input.Dice.Value == Value;
|
||||
}
|
||||
|
||||
private static string? GetEmojyForDiceType(DiceType? diceType) => diceType switch
|
||||
{
|
||||
DiceType.Dice => "🎲",
|
||||
DiceType.Darts => "🎯",
|
||||
DiceType.Bowling => "🎳",
|
||||
DiceType.Basketball => "🏀",
|
||||
DiceType.Football => "⚽",
|
||||
DiceType.Casino => "🎰",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages by matching their text with a regular expression.
|
||||
/// </summary>
|
||||
public class MessageRegexFilter : RegexFilterBase<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageRegexFilter"/> class with a pattern and options.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The regex pattern.</param>
|
||||
/// <param name="regexOptions">The regex options.</param>
|
||||
public MessageRegexFilter(string pattern, RegexOptions regexOptions = default)
|
||||
: base(msg => msg.Text, pattern, regexOptions) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageRegexFilter"/> class with a regex object.
|
||||
/// </summary>
|
||||
/// <param name="regex">The regex object.</param>
|
||||
public MessageRegexFilter(Regex regex)
|
||||
: base(msg => msg.Text, regex) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters messages that contain a specific entity type, content, offset, or length.
|
||||
/// </summary>
|
||||
public class MessageHasEntityFilter : Filter<Message>
|
||||
{
|
||||
private readonly StringComparison _stringComparison = StringComparison.CurrentCulture;
|
||||
private readonly MessageEntityType? EntityType;
|
||||
private readonly string? Content;
|
||||
private readonly int? Offset;
|
||||
private readonly int? Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entities found in the message that match the filter.
|
||||
/// </summary>
|
||||
public MessageEntity[] FoundEntities { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageHasEntityFilter"/> class for a specific entity type.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
public MessageHasEntityFilter(MessageEntityType type)
|
||||
{
|
||||
EntityType = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageHasEntityFilter"/> class for a specific entity type, offset, and length.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="offset">The offset to filter by.</param>
|
||||
/// <param name="length">The length to filter by.</param>
|
||||
public MessageHasEntityFilter(MessageEntityType type, int offset, int? length)
|
||||
{
|
||||
EntityType = type;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageHasEntityFilter"/> class for a specific entity type and content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="content">The content to filter by.</param>
|
||||
/// <param name="stringComparison">The string comparison to use.</param>
|
||||
public MessageHasEntityFilter(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
|
||||
{
|
||||
EntityType = type;
|
||||
Content = content;
|
||||
_stringComparison = stringComparison;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageHasEntityFilter"/> class for a specific entity type, offset, length, and content.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="offset">The offset to filter by.</param>
|
||||
/// <param name="length">The length to filter by.</param>
|
||||
/// <param name="content">The content to filter by.</param>
|
||||
/// <param name="stringComparison">The string comparison to use.</param>
|
||||
public MessageHasEntityFilter(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
|
||||
{
|
||||
EntityType = type;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
Content = content;
|
||||
_stringComparison = stringComparison;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (context.Input is not { Entities.Length: > 0 })
|
||||
return false;
|
||||
|
||||
FoundEntities = context.Input.Entities.Where(entity => FilterEntity(context.Input.Text, entity)).ToArray();
|
||||
return FoundEntities.Length != 0;
|
||||
}
|
||||
|
||||
private bool FilterEntity(string? text, MessageEntity entity)
|
||||
{
|
||||
if (EntityType != null && entity.Type != EntityType)
|
||||
return false;
|
||||
|
||||
if (Offset != null && entity.Offset != Offset)
|
||||
return false;
|
||||
|
||||
if (Length != null && entity.Length != Length)
|
||||
return false;
|
||||
|
||||
if (Content != null)
|
||||
{
|
||||
if (text is not { Length: > 0 })
|
||||
return false;
|
||||
|
||||
if (!text.Substring(entity.Offset, entity.Length).Equals(Content, _stringComparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for filters that operate on message senders.
|
||||
/// Provides functionality to access and validate the user who sent the message.
|
||||
/// </summary>
|
||||
public abstract class MessageSenderFilter : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user who sent the message.
|
||||
/// </summary>
|
||||
public User User { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the message can pass through the filter by validating
|
||||
/// that the message has a valid sender.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if the message has a valid sender; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
User = context.Input.From!;
|
||||
if (User is not { Id: > 0 })
|
||||
return false;
|
||||
|
||||
return CanPassNext(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method that must be implemented by derived classes to perform
|
||||
/// specific filtering logic on the message sender.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the sender passes the specific filter criteria; otherwise, false.</returns>
|
||||
protected abstract bool CanPassNext(FilterExecutionContext<Message> context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message sender has a specific username.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to check for.</param>
|
||||
public class FromUsernameFilter(string username) : MessageSenderFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The username to check for.
|
||||
/// </summary>
|
||||
private readonly string _username = username;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison type to use for username matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FromUsernameFilter"/> class with custom string comparison.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to check for.</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
public FromUsernameFilter(string username, StringComparison comparison)
|
||||
: this(username) => _comparison = comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the message sender has the specified username.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified username; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
=> User.Username != null && User.Username.Equals(_username, _comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message sender has specific first and/or last name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="lastName">The last name to check for (optional).</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
public class FromUserFilter(string firstName, string? lastName, StringComparison comparison) : MessageSenderFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The first name to check for.
|
||||
/// </summary>
|
||||
private readonly string _firstName = firstName;
|
||||
|
||||
/// <summary>
|
||||
/// The last name to check for (optional).
|
||||
/// </summary>
|
||||
private readonly string? _lastName = lastName;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison type to use for name matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _comparison = comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FromUserFilter"/> class with first and last name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="lastName">The last name to check for.</param>
|
||||
public FromUserFilter(string firstName, string lastName)
|
||||
: this(firstName, lastName, StringComparison.InvariantCulture) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FromUserFilter"/> class with first name only.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
public FromUserFilter(string firstName)
|
||||
: this(firstName, null, StringComparison.InvariantCulture) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FromUserFilter"/> class with first name and custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
public FromUserFilter(string firstName, StringComparison comparison)
|
||||
: this(firstName, null, comparison) { }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the message sender has the specified first and/or last name.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified name(s); otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (User.LastName != null)
|
||||
{
|
||||
if (_lastName == null)
|
||||
return false;
|
||||
|
||||
if (!_firstName.Equals(User.LastName, _comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return User.FirstName.Equals(_firstName, _comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message sender has a specific user ID.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to check for.</param>
|
||||
public class FromUserIdFilter(long userId) : MessageSenderFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The user ID to check for.
|
||||
/// </summary>
|
||||
private readonly long _userId = userId;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the message sender has the specified user ID.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified user ID; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.Id == _userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message was sent by a bot.
|
||||
/// </summary>
|
||||
public class FromBotFilter() : MessageSenderFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the message was sent by a bot.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the message was sent by a bot; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.IsBot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message was sent by a premium user.
|
||||
/// </summary>
|
||||
public class FromPremiumUserFilter() : MessageSenderFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the message was sent by a premium user.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the message was sent by a premium user; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.IsPremium;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for filters that operate on message text content.
|
||||
/// Provides common functionality for extracting and validating message text.
|
||||
/// </summary>
|
||||
public abstract class MessageTextFilter : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current message being processed by the filter.
|
||||
/// </summary>
|
||||
public Message Message { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extracted text content from the current message.
|
||||
/// </summary>
|
||||
public string Text { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the message can pass through the filter by validating the message
|
||||
/// and extracting its text content for further processing.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message update.</param>
|
||||
/// <returns>True if the message is valid and can be processed further; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
Message = context.Update.Message!;
|
||||
if (Message is not { Id: > 0 })
|
||||
return false;
|
||||
|
||||
Text = Message.Text ?? string.Empty;
|
||||
return CanPassNext(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method that must be implemented by derived classes to perform
|
||||
/// specific text-based filtering logic.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused in this context).</param>
|
||||
/// <returns>True if the text content passes the filter criteria; otherwise, false.</returns>
|
||||
protected abstract bool CanPassNext(FilterExecutionContext<Message> _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message text starts with a specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to check if the message text starts with.</param>
|
||||
/// <param name="comparison">The string comparison type to use for the check.</param>
|
||||
public class TextStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The content to check if the message text 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 message text starts with the specified content using the configured comparison.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the text starts with the specified content; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.StartsWith(Content, Comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message text ends with a specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to check if the message text ends with.</param>
|
||||
/// <param name="comparison">The string comparison type to use for the check.</param>
|
||||
public class TextEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The content to check if the message text 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 message text ends with the specified content using the configured comparison.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the text ends with the specified content; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.EndsWith(Content, Comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message text contains a specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to check if the message text contains.</param>
|
||||
/// <param name="comparison">The string comparison type to use for the check.</param>
|
||||
public class TextContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The content to check if the message text 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 message text contains the specified content using the configured comparison.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the text contains the specified content; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.IndexOf(Content, Comparison) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message text equals a specified content.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to check if the message text equals.</param>
|
||||
/// <param name="comparison">The string comparison type to use for the check.</param>
|
||||
public class TextEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The content to check if the message text 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 message text equals the specified content using the configured comparison.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the text equals the specified content; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.Equals(Content, Comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the message text is not null or empty.
|
||||
/// </summary>
|
||||
public class TextNotNullOrEmptyFilter() : MessageTextFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the message text is not null or empty.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the text is not null or empty; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> !string.IsNullOrEmpty(Text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters that use regular expressions to match text in updates.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public abstract class RegexFilterBase<T> : Filter<T> where T : class
|
||||
{
|
||||
private readonly Func<T, string?> getString;
|
||||
private readonly Regex regex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of matches found by the regex.
|
||||
/// </summary>
|
||||
public MatchCollection Matches { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexFilterBase{T}"/> class with a regex object.
|
||||
/// </summary>
|
||||
/// <param name="getString">Function to extract the string to match from the input.</param>
|
||||
/// <param name="regex">The regex object to use for matching.</param>
|
||||
protected RegexFilterBase(Func<T, string?> getString, Regex regex)
|
||||
{
|
||||
this.getString = getString;
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexFilterBase{T}"/> class with a pattern and options.
|
||||
/// </summary>
|
||||
/// <param name="getString">Function to extract the string to match from the input.</param>
|
||||
/// <param name="pattern">The regex pattern.</param>
|
||||
/// <param name="regexOptions">The regex options.</param>
|
||||
protected RegexFilterBase(Func<T, string?> getString, string pattern, RegexOptions regexOptions = default)
|
||||
{
|
||||
this.getString = getString;
|
||||
regex = new Regex(pattern, regexOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the regex matches the text extracted from the input.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the regex matches; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<T> context)
|
||||
{
|
||||
string? text = getString.Invoke(context.Input);
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return false;
|
||||
|
||||
Matches = regex.Matches(text);
|
||||
return Matches.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter that checks if a replied message contains a mention of the bot or a specific user.
|
||||
/// Requires a <see cref="MessageHasEntityFilter"/> to be applied first to identify mention entities.
|
||||
/// </summary>
|
||||
public class RepliedMentionedFilter : RepliedMessageFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The username to check for in the mention (null means check for bot's username).
|
||||
/// </summary>
|
||||
private readonly string? Mention;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMentionedFilter"/> class that checks for bot mentions.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMentionedFilter(int replyDepth = 1) : base(replyDepth)
|
||||
{
|
||||
Mention = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMentionedFilter"/> class that checks for specific user mentions.
|
||||
/// </summary>
|
||||
/// <param name="mention">The username to check for in the mention.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMentionedFilter(string mention, int replyDepth = 1) : base(replyDepth)
|
||||
{
|
||||
Mention = mention;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message contains a mention of the specified user or bot.
|
||||
/// This filter requires a <see cref="MessageHasEntityFilter"/> to be applied first
|
||||
/// to identify mention entities in the replied message.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message and completed filters.</param>
|
||||
/// <returns>True if the replied message contains the specified mention; otherwise, false.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the bot username is null and no specific mention is provided.</exception>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (Reply.Text == null)
|
||||
return false;
|
||||
|
||||
string userName = Mention ?? context.BotInfo.User.Username ?? throw new ArgumentNullException(nameof(context), "RepliedMentionedFilter requires BotInfo to be initialized");
|
||||
MessageEntity entity = context.CompletedFilters.Get<MessageHasEntityFilter>(0).FoundEntities.ElementAt(0);
|
||||
|
||||
string mention = Reply.Text.Substring(entity.Offset + 1, entity.Length - 1);
|
||||
return userName == mention;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters that operate on the chat of a replied message (the message being replied to).
|
||||
/// The replyDepth parameter determines how many levels up the reply chain to search for the target message.
|
||||
/// </summary>
|
||||
public abstract class RepliedMessageChatFilter : RepliedMessageFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the chat of the replied message (the message being replied to at the specified depth).
|
||||
/// </summary>
|
||||
public Chat Chat { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
protected RepliedMessageChatFilter(int replyDepth = 1) : base(replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (!CanPassReply(context))
|
||||
return false;
|
||||
|
||||
Chat = Reply.Chat;
|
||||
return CanPassNext(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat is a forum.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatIsForumFilter : RepliedMessageChatFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatIsForumFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatIsForumFilter(int replyDepth = 1)
|
||||
: base(replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Chat.IsForum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat ID matches the specified value.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatIdFilter : RepliedMessageChatFilter
|
||||
{
|
||||
private readonly long Id;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatIdFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The chat ID to match.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatIdFilter(long id, int replyDepth = 1) : base(replyDepth) => Id = id;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Chat.Id == Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat type matches the specified value.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatTypeFilter : RepliedMessageChatFilter
|
||||
{
|
||||
private readonly ChatType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatTypeFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The chat type to match.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatTypeFilter(ChatType type, int replyDepth = 1) : base(replyDepth) => Type = type;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Chat.Type == Type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat title matches the specified value.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatTitleFilter : RepliedMessageChatFilter
|
||||
{
|
||||
private readonly string? Title;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatTitleFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatTitleFilter(string? title, int replyDepth = 1) : base(replyDepth) => Title = title;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatTitleFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="title">The chat title to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatTitleFilter(string? title, StringComparison comparison, int replyDepth = 1)
|
||||
: this(title, replyDepth) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
{
|
||||
if (Chat.Title == null)
|
||||
return false;
|
||||
return Chat.Title.Equals(Title, Comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat username matches the specified value.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatUsernameFilter : RepliedMessageChatFilter
|
||||
{
|
||||
private readonly string? UserName;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatUsernameFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatUsernameFilter(string? userName, int replyDepth = 1) : base(replyDepth) => UserName = userName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatUsernameFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="userName">The chat username to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatUsernameFilter(string? userName, StringComparison comparison, int replyDepth = 1)
|
||||
: this(userName, replyDepth) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
{
|
||||
if (Chat.Username == null)
|
||||
return false;
|
||||
return Chat.Username.Equals(UserName, Comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose chat first and/or last name matches the specified values.
|
||||
/// </summary>
|
||||
public class RepliedMessageChatNameFilter : RepliedMessageChatFilter
|
||||
{
|
||||
private readonly string? FirstName;
|
||||
private readonly string? LastName;
|
||||
private readonly StringComparison Comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatNameFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The chat first name to match.</param>
|
||||
/// <param name="lastName">The chat last name to match.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatNameFilter(string? firstName, string? lastName, int replyDepth = 1) : base(replyDepth)
|
||||
{
|
||||
FirstName = firstName;
|
||||
LastName = lastName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageChatNameFilter"/> class with a specific string comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The chat first name to match.</param>
|
||||
/// <param name="lastName">The chat last name to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedMessageChatNameFilter(string? firstName, string? lastName, StringComparison comparison, int replyDepth = 1)
|
||||
: this(firstName, lastName, replyDepth) => Comparison = comparison;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
{
|
||||
if (LastName != null)
|
||||
{
|
||||
if (Chat.LastName == null)
|
||||
return false;
|
||||
|
||||
if (Chat.LastName.Equals(LastName, Comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FirstName != null)
|
||||
{
|
||||
if (Chat.FirstName == null)
|
||||
return false;
|
||||
|
||||
if (Chat.FirstName.Equals(FirstName, Comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for filters that operate on replied messages.
|
||||
/// Provides functionality to traverse reply chains and access replied message content.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public abstract class RepliedMessageFilter(int replyDepth = 1) : Filter<Message>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the replied message at the specified depth in the reply chain.
|
||||
/// </summary>
|
||||
public Message Reply { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the depth of reply chain traversal.
|
||||
/// </summary>
|
||||
public int ReplyDepth { get; private set; } = replyDepth;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the message has a valid reply chain at the specified depth.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if the reply chain is valid at the specified depth; otherwise, false.</returns>
|
||||
protected bool CanPassReply(FilterExecutionContext<Message> context)
|
||||
{
|
||||
Message reply = context.Input;
|
||||
for (int i = ReplyDepth; i > 0; i--)
|
||||
{
|
||||
if (reply.ReplyToMessage is not { Id: > 0 } replyMessage)
|
||||
return false;
|
||||
|
||||
reply = replyMessage;
|
||||
}
|
||||
|
||||
Reply = reply;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the message can pass through the filter by first validating
|
||||
/// the reply chain and then applying specific filter logic.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if the message passes both reply validation and specific filter criteria; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (!CanPassReply(context))
|
||||
return false;
|
||||
|
||||
return CanPassNext(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method that must be implemented by derived classes to perform
|
||||
/// specific filtering logic on the replied message.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the replied message passes the specific filter criteria; otherwise, false.</returns>
|
||||
protected abstract bool CanPassNext(FilterExecutionContext<Message> context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if a message is a reply to another message at the specified depth.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class MessageRepliedFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Always returns true if the reply chain is valid at the specified depth.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>True if the reply chain is valid; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
=> true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message was sent by the bot itself.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class MeRepliedFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message was sent by the bot.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing bot information.</param>
|
||||
/// <returns>True if the replied message was sent by the bot; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
=> context.BotInfo.User == Reply.From;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message has non-empty text content.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedTextNotNullOrEmptyFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message text is not null or empty.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message has non-empty text; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> !string.IsNullOrEmpty(Reply.Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message is of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="type">The message type to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedMessageTypeFilter(MessageType type, int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message is of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message is of the specified type; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Reply.Type == type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message is an automatic forward.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedIsAutomaticFormwardMessageFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message is an automatic forward.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message is an automatic forward; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Reply.IsAutomaticForward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message is from an offline user.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedIsFromOfflineMessageFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message is from an offline user.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message is from an offline user; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Reply.IsFromOffline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message is a service message.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedIsServiceMessageMessageFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message is a service message.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message is a service message; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Reply.IsServiceMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message is a topic message.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedIsTopicMessageMessageFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message is a topic message.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message is a topic message; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Reply.IsTopicMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message contains a dice with a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The dice value to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedDiceThrowedFilter(int value, int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// The dice type to check for (optional).
|
||||
/// </summary>
|
||||
private readonly DiceType? Dice = null;
|
||||
|
||||
/// <summary>
|
||||
/// The dice value to check for.
|
||||
/// </summary>
|
||||
private readonly int Value = value;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedDiceThrowedFilter"/> class with a specific dice type and value.
|
||||
/// </summary>
|
||||
/// <param name="diceType">The dice type to check for.</param>
|
||||
/// <param name="value">The dice value to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedDiceThrowedFilter(DiceType diceType, int value, int replyDepth = 1)
|
||||
: this(value, replyDepth) => Dice = diceType;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message contains a dice with the specified value and optionally the specified type.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if the replied message contains a dice with the specified criteria; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (context.Input.Dice == null)
|
||||
return false;
|
||||
|
||||
if (Dice != null && context.Input.Dice.Emoji != GetEmojyForDiceType(Dice))
|
||||
return false;
|
||||
|
||||
return context.Input.Dice.Value == Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the emoji representation for a specific dice type.
|
||||
/// </summary>
|
||||
/// <param name="diceType">The dice type to get the emoji for.</param>
|
||||
/// <returns>The emoji string for the dice type, or null if not found.</returns>
|
||||
private static string? GetEmojyForDiceType(DiceType? diceType) => diceType switch
|
||||
{
|
||||
DiceType.Dice => "🎲",
|
||||
DiceType.Darts => "🎯",
|
||||
DiceType.Bowling => "🎳",
|
||||
DiceType.Basketball => "🏀",
|
||||
DiceType.Football => "⚽",
|
||||
DiceType.Casino => "🎰",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message contains specific message entities.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedMessageHasEntityFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// The string comparison type to use for content matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _stringComparison = StringComparison.CurrentCulture;
|
||||
|
||||
/// <summary>
|
||||
/// The entity type to filter by (optional).
|
||||
/// </summary>
|
||||
private readonly MessageEntityType? EntityType;
|
||||
|
||||
/// <summary>
|
||||
/// The content to match in the entity (optional).
|
||||
/// </summary>
|
||||
private readonly string? Content;
|
||||
|
||||
/// <summary>
|
||||
/// The offset position to check (optional).
|
||||
/// </summary>
|
||||
private readonly int? Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The length to check (optional).
|
||||
/// </summary>
|
||||
private readonly int? Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the found entities that match the filter criteria.
|
||||
/// </summary>
|
||||
public MessageEntity[]? FoundEntities { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageHasEntityFilter"/> class with a specific entity type.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMessageHasEntityFilter(MessageEntityType type, int replyDepth = 1) : this(replyDepth)
|
||||
{
|
||||
EntityType = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageHasEntityFilter"/> class with position criteria.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="offset">The offset position to check.</param>
|
||||
/// <param name="length">The length to check (optional).</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMessageHasEntityFilter(MessageEntityType type, int offset, int? length, int replyDepth = 1) : this(replyDepth)
|
||||
{
|
||||
EntityType = type;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageHasEntityFilter"/> class with content criteria.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="content">The content to match in the entity.</param>
|
||||
/// <param name="stringComparison">The string comparison type to use.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMessageHasEntityFilter(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture, int replyDepth = 1) : this(replyDepth)
|
||||
{
|
||||
EntityType = type;
|
||||
Content = content;
|
||||
_stringComparison = stringComparison;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageHasEntityFilter"/> class with all criteria.
|
||||
/// </summary>
|
||||
/// <param name="type">The entity type to filter by.</param>
|
||||
/// <param name="offset">The offset position to check.</param>
|
||||
/// <param name="length">The length to check (optional).</param>
|
||||
/// <param name="content">The content to match in the entity.</param>
|
||||
/// <param name="stringComparison">The string comparison type to use.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedMessageHasEntityFilter(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture, int replyDepth = 1) : this(replyDepth)
|
||||
{
|
||||
EntityType = type;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
Content = content;
|
||||
_stringComparison = stringComparison;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message contains entities that match the specified criteria.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if matching entities are found; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (context.Input is not { Entities.Length: > 0 })
|
||||
return false;
|
||||
|
||||
FoundEntities = context.Input.Entities.Where(entity => FilterEntity(context.Input.Text, entity)).ToArray();
|
||||
return FoundEntities.Length != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters an entity based on the specified criteria.
|
||||
/// </summary>
|
||||
/// <param name="text">The message text containing the entity.</param>
|
||||
/// <param name="entity">The entity to filter.</param>
|
||||
/// <returns>True if the entity matches all specified criteria; otherwise, false.</returns>
|
||||
private bool FilterEntity(string? text, MessageEntity entity)
|
||||
{
|
||||
if (EntityType != null && entity.Type != EntityType)
|
||||
return false;
|
||||
|
||||
if (Offset != null && entity.Offset != Offset)
|
||||
return false;
|
||||
|
||||
if (Length != null && entity.Length != Length)
|
||||
return false;
|
||||
|
||||
if (Content != null)
|
||||
{
|
||||
if (text is not { Length: > 0 })
|
||||
return false;
|
||||
|
||||
if (!text.Substring(entity.Offset, entity.Length).Equals(Content, _stringComparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for filters that operate on the sender of replied messages.
|
||||
/// Provides functionality to access and validate the user who sent the replied message.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public abstract class RepliedMessageSenderFilter(int replyDepth = 1) : RepliedMessageFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user who sent the replied message.
|
||||
/// </summary>
|
||||
public User User { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the message can pass through the filter by validating the reply chain
|
||||
/// and ensuring the replied message has a valid sender.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the message.</param>
|
||||
/// <returns>True if the reply chain is valid and has a sender; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (!CanPassReply(context))
|
||||
return false;
|
||||
|
||||
if (Reply.From is not { Id: > 0 } from)
|
||||
return false;
|
||||
|
||||
User = from;
|
||||
return CanPassNext(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message sender has a specific username.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedUsernameFilter(string username, int replyDepth = 1) : RepliedMessageSenderFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// The username to check for.
|
||||
/// </summary>
|
||||
private readonly string _username = username;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison type to use for username matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _comparison = StringComparison.InvariantCulture;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedUsernameFilter"/> class with custom string comparison.
|
||||
/// </summary>
|
||||
/// <param name="username">The username to check for.</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedUsernameFilter(string username, StringComparison comparison, int replyDepth = 1)
|
||||
: this(username, replyDepth) => _comparison = comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message sender has the specified username.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified username; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
=> User.Username != null && User.Username.Equals(_username, _comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message sender has specific first and/or last name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="lastName">The last name to check for (optional).</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedUserFilter(string firstName, string? lastName, StringComparison comparison, int replyDepth = 1) : RepliedMessageSenderFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// The first name to check for.
|
||||
/// </summary>
|
||||
private readonly string _firstName = firstName;
|
||||
|
||||
/// <summary>
|
||||
/// The last name to check for (optional).
|
||||
/// </summary>
|
||||
private readonly string? _lastName = lastName;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison type to use for name matching.
|
||||
/// </summary>
|
||||
private readonly StringComparison _comparison = comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedUserFilter"/> class with first and last name.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="lastName">The last name to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedUserFilter(string firstName, string lastName, int replyDepth = 1)
|
||||
: this(firstName, lastName, StringComparison.InvariantCulture, replyDepth) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedUserFilter"/> class with first name only.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedUserFilter(string firstName, int replyDepth = 1)
|
||||
: this(firstName, null, StringComparison.InvariantCulture, replyDepth) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedUserFilter"/> class with first name and custom comparison.
|
||||
/// </summary>
|
||||
/// <param name="firstName">The first name to check for.</param>
|
||||
/// <param name="comparison">The string comparison type to use.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public RepliedUserFilter(string firstName, StringComparison comparison, int replyDepth = 1)
|
||||
: this(firstName, null, comparison, replyDepth) { }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message sender has the specified first and/or last name.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified name(s); otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (User.LastName != null)
|
||||
{
|
||||
if (_lastName == null)
|
||||
return false;
|
||||
|
||||
if (!_firstName.Equals(User.LastName, _comparison))
|
||||
return false;
|
||||
}
|
||||
|
||||
return User.FirstName.Equals(_firstName, _comparison);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message sender has a specific user ID.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to check for.</param>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class RepliedUserIdFilter(long userId, int replyDepth = 1) : RepliedMessageSenderFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// The user ID to check for.
|
||||
/// </summary>
|
||||
private readonly long _userId = userId;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the replied message sender has the specified user ID.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the sender has the specified user ID; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.Id == _userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message was sent by a bot.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class ReplyFromBotFilter(int replyDepth = 1) : RepliedMessageSenderFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message was sent by a bot.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message was sent by a bot; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.IsBot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter that checks if the replied message was sent by a premium user.
|
||||
/// </summary>
|
||||
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
|
||||
public class ReplyFromPremiumUserFilter(int replyDepth = 1) : RepliedMessageSenderFilter(replyDepth)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the replied message was sent by a premium user.
|
||||
/// </summary>
|
||||
/// <param name="_">The filter execution context (unused).</param>
|
||||
/// <returns>True if the replied message was sent by a premium user; otherwise, false.</returns>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> User.IsPremium;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for filters that operate on the text of a replied message (the message being replied to).
|
||||
/// The replyDepth parameter determines how many levels up the reply chain to search for the target message.
|
||||
/// </summary>
|
||||
public abstract class RepliedMessageTextFilters : RepliedMessageFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text of the replied message (the message being replied to at the specified depth).
|
||||
/// </summary>
|
||||
public string Text { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The content to match in the replied message.
|
||||
/// </summary>
|
||||
protected readonly string Content;
|
||||
|
||||
/// <summary>
|
||||
/// The string comparison to use for matching.
|
||||
/// </summary>
|
||||
protected readonly StringComparison Comparison;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedMessageTextFilters"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
protected RepliedMessageTextFilters(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: base(replyDepth)
|
||||
{
|
||||
Content = content;
|
||||
Comparison = comparison;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Message> context)
|
||||
{
|
||||
Text = Reply.Text ?? string.Empty;
|
||||
return CanPassNext(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose text starts with the specified content.
|
||||
/// </summary>
|
||||
public class RepliedTextStartsWithFilter : RepliedMessageTextFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedTextStartsWithFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedTextStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: base(content, comparison, replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.StartsWith(Content, Comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose text ends with the specified content.
|
||||
/// </summary>
|
||||
public class RepliedTextEndsWithFilter : RepliedMessageTextFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedTextEndsWithFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedTextEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: base(content, comparison, replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.EndsWith(Content, Comparison);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose text contains the specified content.
|
||||
/// </summary>
|
||||
public class RepliedTextContainsFilter : RepliedMessageTextFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedTextContainsFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedTextContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: base(content, comparison, replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.IndexOf(Content, Comparison) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters replied messages (the message being replied to at the specified depth) whose text equals the specified content.
|
||||
/// </summary>
|
||||
public class RepliedTextEqualsFilter : RepliedMessageTextFilters
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepliedTextEqualsFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The content to match.</param>
|
||||
/// <param name="comparison">The string comparison to use.</param>
|
||||
/// <param name="replyDepth">The reply depth to search up the reply chain for the target message.</param>
|
||||
public RepliedTextEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int replyDepth = 1)
|
||||
: base(content, comparison, replyDepth) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> _)
|
||||
=> Text.Equals(Content, Comparison);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
public class RepliedToMeFilter : RepliedMessageFilter
|
||||
{
|
||||
protected override bool CanPassNext(FilterExecutionContext<Message> context)
|
||||
{
|
||||
if (Reply.From == null)
|
||||
return false;
|
||||
|
||||
return Reply.From.Id == (context.BotInfo?.User.Id ?? throw new ArgumentNullException(nameof(context), "MentionedFilter requires BotInfo to be initialized"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters updates by comparing a resolved state key with a target key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam>
|
||||
public class StateKeyFilter<TKey> : Filter<Update> where TKey : IEquatable<TKey>
|
||||
{
|
||||
private readonly IStateKeyResolver<TKey> KeyResolver;
|
||||
private readonly TKey TargetKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeyFilter{TKey}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="keyResolver">The key resolver to extract the key from the update.</param>
|
||||
/// <param name="targetKey">The target key to compare with.</param>
|
||||
public StateKeyFilter(IStateKeyResolver<TKey> keyResolver, TKey targetKey)
|
||||
{
|
||||
KeyResolver = keyResolver;
|
||||
TargetKey = targetKey;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context)
|
||||
=> KeyResolver.ResolveKey(context.Input).Equals(TargetKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0290")]
|
||||
[assembly: SuppressMessage("Style", "IDE0090")]
|
||||
[assembly: SuppressMessage("Style", "IDE0057")]
|
||||
@@ -0,0 +1,62 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Container class that holds the context and data for handler execution.
|
||||
/// Provides access to the update, client, filters, and other execution context.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
|
||||
public class AbstractHandlerContainer<TUpdate> : IAbstractHandlerContainer<TUpdate> where TUpdate : class
|
||||
{
|
||||
private readonly TUpdate _actualUpdate;
|
||||
private readonly Update _handlingUpdate;
|
||||
private readonly ITelegramBotClient _client;
|
||||
private readonly Dictionary<string, object> _extraData;
|
||||
private readonly CompletedFiltersList _completedFilters;
|
||||
private readonly IAwaitingProvider _awaitingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actual update object of type TUpdate.
|
||||
/// </summary>
|
||||
public TUpdate ActualUpdate => _actualUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Update HandlingUpdate => _handlingUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ITelegramBotClient Client => _client;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<string, object> ExtraData => _extraData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CompletedFiltersList CompletedFilters => _completedFilters;
|
||||
|
||||
/// <inheritdoc cref="IHandlerContainer.AwaitingProvider"/>
|
||||
public IAwaitingProvider AwaitingProvider => _awaitingProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
IAwaitingProvider IHandlerContainer.AwaitingProvider => AwaitingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractHandlerContainer{TUpdate}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="awaitingProvider">The awaiting provider for managing async operations.</param>
|
||||
/// <param name="handlerInfo">The handler information containing execution context.</param>
|
||||
public AbstractHandlerContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo)
|
||||
{
|
||||
_actualUpdate = handlerInfo.HandlingUpdate.GetActualUpdateObject<TUpdate>();
|
||||
_handlingUpdate = handlerInfo.HandlingUpdate;
|
||||
_client = handlerInfo.Client;
|
||||
_extraData = handlerInfo.ExtraData;
|
||||
_completedFilters = handlerInfo.CompletedFilters;
|
||||
_awaitingProvider = awaitingProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that marks a handler to process any type of update.
|
||||
/// This handler will be triggered for all incoming updates regardless of their type.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: -1 for unlimited).</param>
|
||||
public class AnyUpdateHandlerAttribute(int concurrency = -1) : UpdateHandlerAttribute<AnyUpdateHandler>(UpdateType.Unknown, concurrency)
|
||||
{
|
||||
/// <summary>
|
||||
/// Always returns true, allowing any update to pass through this filter.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>Always returns true to allow any update.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context) => true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for handlers that can process any type of update.
|
||||
/// Provides a foundation for creating handlers that respond to all incoming updates.
|
||||
/// </summary>
|
||||
public abstract class AnyUpdateHandler() : AbstractUpdateHandler<Update>(UpdateType.Unknown)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Handlers.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal handler used for awaiting specific update types.
|
||||
/// Provides synchronization mechanism for waiting for updates of a particular type.
|
||||
/// </summary>
|
||||
/// <param name="handlingUpdateType">The type of update this awaiter handler waits for.</param>
|
||||
internal class AwaiterHandler(UpdateType handlingUpdateType) : UpdateHandlerBase(handlingUpdateType), IHandlerContainerFactory, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Manual reset event used for synchronization.
|
||||
/// </summary>
|
||||
private ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the update that triggered this awaiter handler.
|
||||
/// </summary>
|
||||
public Update HandlingUpdate { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the specified update type to be received.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
|
||||
public void Wait(CancellationToken cancellationToken)
|
||||
{
|
||||
ResetEvent.Reset();
|
||||
ResetEvent.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handler container for this awaiter handler.
|
||||
/// </summary>
|
||||
/// <param name="_">The awaiting provider (unused).</param>
|
||||
/// <param name="describedHandler">The handler information containing the update.</param>
|
||||
/// <returns>An empty handler container.</returns>
|
||||
public IHandlerContainer CreateContainer(IAwaitingProvider _, DescribedHandlerInfo describedHandler)
|
||||
{
|
||||
HandlingUpdate = describedHandler.HandlingUpdate;
|
||||
return new EmptyHandlerContainer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the awaiter handler by setting the reset event.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container (unused).</param>
|
||||
/// <param name="cancellation">The cancellation token (unused).</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation)
|
||||
{
|
||||
ResetEvent.Set();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the reset event.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
ResetEvent.Dispose();
|
||||
ResetEvent = null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Handlers.Building.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Builder class for creating awaiter handlers that can wait for specific update types.
|
||||
/// Provides fluent API for configuring filters, state keepers, and other handler properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update to await.</typeparam>
|
||||
public class AwaiterHandlerBuilder<TUpdate> : HandlerBuilderBase, IAwaiterHandlerBuilder<TUpdate> where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The awaiting provider for managing handler registration.
|
||||
/// </summary>
|
||||
private readonly IAwaitingProvider HandlerProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The update that triggered the awaiter creation.
|
||||
/// </summary>
|
||||
private readonly Update HandlingUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AwaiterHandlerBuilder{TUpdate}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateType">The type of update to await.</param>
|
||||
/// <param name="handlingUpdate">The update that triggered the awaiter creation.</param>
|
||||
/// <param name="handlerProvider">The awaiting provider for managing handler registration.</param>
|
||||
/// <exception cref="Exception">Thrown when the update type is not valid for TUpdate.</exception>
|
||||
public AwaiterHandlerBuilder(UpdateType updateType, Update handlingUpdate, IAwaitingProvider handlerProvider) : base(typeof(AwaiterHandler), updateType, null)
|
||||
{
|
||||
if (!updateType.IsValidUpdateObject<TUpdate>())
|
||||
throw new Exception();
|
||||
|
||||
HandlerProvider = handlerProvider;
|
||||
HandlingUpdate = handlingUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Awaits for an update of the specified type using the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
|
||||
/// <returns>The awaited update of type TUpdate.</returns>
|
||||
public async Task<TUpdate> Await(CancellationToken cancellationToken = default)
|
||||
=> await Await(new SenderIdResolver(), cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Awaits for an update of the specified type using a custom state key resolver.
|
||||
/// </summary>
|
||||
/// <param name="keyResolver">The state key resolver to use for filtering updates.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
|
||||
/// <returns>The awaited update of type TUpdate.</returns>
|
||||
public async Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Filters.Add(new StateKeyFilter<long>(keyResolver, keyResolver.ResolveKey(HandlingUpdate)));
|
||||
AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType);
|
||||
HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance);
|
||||
|
||||
using (HandlerProvider.UseHandler(descriptor))
|
||||
{
|
||||
handlerInstance.Wait(cancellationToken);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
return handlerInstance.HandlingUpdate.GetActualUpdateObject<TUpdate>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal handler class that wraps a delegate action for execution.
|
||||
/// Used for dynamically created handlers that execute custom actions.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
|
||||
internal class BuildedAbstractHandler<TUpdate> : AbstractUpdateHandler<TUpdate> where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The delegate action to execute when the handler is invoked.
|
||||
/// </summary>
|
||||
private readonly AbstractHandlerAction<TUpdate> HandlerAction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuildedAbstractHandler{TUpdate}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handlingUpdateType">The type of update this handler processes.</param>
|
||||
/// <param name="handlerAction">The delegate action to execute.</param>
|
||||
public BuildedAbstractHandler(UpdateType handlingUpdateType, AbstractHandlerAction<TUpdate> handlerAction) : base(handlingUpdateType)
|
||||
{
|
||||
HandlerAction = handlerAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the wrapped handler action.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container with execution context.</param>
|
||||
/// <param name="cancellation">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous execution.</returns>
|
||||
public override Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
|
||||
=> HandlerAction.Invoke(container, cancellation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for building handler descriptors and managing handler filters.
|
||||
/// </summary>
|
||||
public abstract class HandlerBuilderBase(Type buildingHandlerType, UpdateType updateType, IHandlersCollection? handlerCollection) : IHandlerBuilder
|
||||
{
|
||||
private static int HandlerServiceKeyIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IHandlersCollection"/> to ehich new builded handlers is adding
|
||||
/// </summary>
|
||||
protected readonly IHandlersCollection? HandlerCollection = handlerCollection;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="UpdateType"/> of building handler
|
||||
/// </summary>
|
||||
protected readonly UpdateType UpdateType = updateType;
|
||||
|
||||
/// <summary>
|
||||
/// Type of handler to build
|
||||
/// </summary>
|
||||
protected readonly Type BuildingHandlerType = buildingHandlerType;
|
||||
|
||||
/// <summary>
|
||||
/// Filters applied to handler
|
||||
/// </summary>
|
||||
protected readonly List<IFilter<Update>> Filters = [];
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DescriptorIndexer"/> of building handler
|
||||
/// </summary>
|
||||
protected DescriptorIndexer Indexer = new DescriptorIndexer(0, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Update validation filter of building handler
|
||||
/// </summary>
|
||||
protected IFilter<Update>? ValidateFilter;
|
||||
|
||||
/// <summary>
|
||||
/// State keeper of building handler
|
||||
/// </summary>
|
||||
protected IFilter<Update>? StateKeeper;
|
||||
|
||||
/// <summary>
|
||||
/// Builds an implicit <see cref="HandlerDescriptor"/> for the specified handler instance.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="UpdateHandlerBase"/> instance.</param>
|
||||
/// <returns>The created <see cref="HandlerDescriptor"/>.</returns>
|
||||
protected HandlerDescriptor BuildImplicitDescriptor(UpdateHandlerBase instance)
|
||||
{
|
||||
object handlerServiceKey = GetImplicitHandlerServiceKey(BuildingHandlerType);
|
||||
|
||||
HandlerDescriptor descriptor = new HandlerDescriptor(
|
||||
DescriptorType.Implicit, BuildingHandlerType,
|
||||
UpdateType, Indexer, ValidateFilter,
|
||||
Filters.ToArray(), StateKeeper,
|
||||
handlerServiceKey, instance);
|
||||
|
||||
HandlerCollection?.AddDescriptor(descriptor);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique service key for an implicit handler type.
|
||||
/// </summary>
|
||||
/// <param name="BuildingHandlerType">The handler type.</param>
|
||||
/// <returns>A unique service key string.</returns>
|
||||
public static object GetImplicitHandlerServiceKey(Type BuildingHandlerType)
|
||||
=> string.Format("ImplicitHandler_{0}+{1}", HandlerServiceKeyIndex++, BuildingHandlerType.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the update validating action for the handler.
|
||||
/// </summary>
|
||||
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetUpdateValidating(UpdateValidateAction validateAction)
|
||||
{
|
||||
ValidateFilter = new UpdateValidateFilter(validateAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the concurrency level for the handler.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The concurrency value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetConcurreny(int concurrency)
|
||||
{
|
||||
Indexer = Indexer.UpdateConcurrency(concurrency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the priority for the handler.
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetPriority(int priority)
|
||||
{
|
||||
Indexer = Indexer.UpdatePriority(priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets both concurrency and priority for the handler.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The concurrency value.</param>
|
||||
/// <param name="priority">The priority value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetIndexer(int concurrency, int priority)
|
||||
{
|
||||
Indexer = new DescriptorIndexer(0, concurrency, priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filter to the handler.
|
||||
/// </summary>
|
||||
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void AddFilter(IFilter<Update> filter)
|
||||
{
|
||||
Filters.Add(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple filters to the handler.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to add.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void AddFilters(params IFilter<Update>[] filters)
|
||||
{
|
||||
Filters.AddRange(filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a specific state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(myState, keyResolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a special state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(specialState, keyResolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a targeted filter for a specific filter target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
|
||||
/// <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 void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter) where TFilterTarget : class
|
||||
{
|
||||
AnonymousTypeFilter anonymousTypeFilter = AnonymousTypeFilter.Compile(filter, getFilterringTarget);
|
||||
Filters.Add(anonymousTypeFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple targeted filters for a specific filter target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
|
||||
/// <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 void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters) where TFilterTarget : class
|
||||
{
|
||||
AnonymousCompiledFilter compiledPollingFilter = AnonymousCompiledFilter.Compile(filters, getFilterringTarget);
|
||||
Filters.Add(compiledPollingFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a builder for awaiting handler logic for a specific update type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update to await.</typeparam>
|
||||
public interface IAwaiterHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Awaits an update using the specified key resolver and cancellation token.
|
||||
/// </summary>
|
||||
/// <param name="keyResolver">The <see cref="IStateKeyResolver{TKey}"/> to resolve the key.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task{TUpdate}"/> representing the awaited update.</returns>
|
||||
public Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines builder actions for configuring handler builders.
|
||||
/// </summary>
|
||||
public interface IHandlerBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the update validating action for the handler.
|
||||
/// </summary>
|
||||
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetUpdateValidating(UpdateValidateAction validateAction);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the concurrency level for the handler.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The concurrency value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetConcurreny(int concurrency);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the priority for the handler.
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetPriority(int priority);
|
||||
|
||||
/// <summary>
|
||||
/// Sets both concurrency and priority for the handler.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The concurrency value.</param>
|
||||
/// <param name="priority">The priority value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetIndexer(int concurrency, int priority);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filter to the handler.
|
||||
/// </summary>
|
||||
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void AddFilter(IFilter<Update> filter);
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple filters to the handler.
|
||||
/// </summary>
|
||||
/// <param name="filters">The filters to add.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void AddFilters(params IFilter<Update>[] filters);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a specific state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a special state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a targeted filter for a specific filter target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
|
||||
/// <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 void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter)
|
||||
where TFilterTarget : class;
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple targeted filters for a specific filter target type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
|
||||
/// <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 void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters)
|
||||
where TFilterTarget : class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Telegrator.Handlers.Building;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a builder for regular handler logic for a specific update type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update to handle.</typeparam>
|
||||
public interface IRegularHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the handler logic using the specified execution delegate.
|
||||
/// </summary>
|
||||
/// <param name="executeHandler">The delegate to execute the handler logic.</param>
|
||||
public IHandlersCollection Build(AbstractHandlerAction<TUpdate> executeHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Filters;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.StateKeeping.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter for state keeping logic, allowing filtering based on state and special state conditions.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key for state resolution.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
public class StateKeepFilter<TKey, TState, TKeeper> : Filter<Update>
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the state keeper instance.
|
||||
/// </summary>
|
||||
public static TKeeper StateKeeper { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value for this filter.
|
||||
/// </summary>
|
||||
public TState MyState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special state value for this filter.
|
||||
/// </summary>
|
||||
public SpecialState SpecialState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a specific state.
|
||||
/// </summary>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
public StateKeepFilter(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = myState;
|
||||
SpecialState = SpecialState.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a special state.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
public StateKeepFilter(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = StateKeeper.DefaultState;
|
||||
SpecialState = specialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context based on state logic.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context)
|
||||
{
|
||||
if (SpecialState == SpecialState.AnyState)
|
||||
return true;
|
||||
|
||||
if (!StateKeeper.TryGetState(context.Input, out TState? state))
|
||||
return SpecialState == SpecialState.NoState;
|
||||
|
||||
if (state == null)
|
||||
return false;
|
||||
|
||||
return MyState.Equals(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Handlers.Building.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for validating an update in a filter context.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the update is valid; otherwise, false.</returns>
|
||||
public delegate bool UpdateValidateAction(FilterExecutionContext<Update> context);
|
||||
|
||||
/// <summary>
|
||||
/// Filter that uses a delegate to validate updates.
|
||||
/// </summary>
|
||||
public class UpdateValidateFilter : IFilter<Update>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this filter is collectable. Always false for this filter.
|
||||
/// </summary>
|
||||
public bool IsCollectible => false;
|
||||
private readonly UpdateValidateAction UpdateValidateAction;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateValidateFilter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateValidateAction">The validation delegate to use.</param>
|
||||
public UpdateValidateFilter(UpdateValidateAction updateValidateAction)
|
||||
{
|
||||
UpdateValidateAction = updateValidateAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context using the validation delegate.
|
||||
/// </summary>
|
||||
/// <param name="info">The filter execution context.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
public bool CanPass(FilterExecutionContext<Update> info)
|
||||
=> UpdateValidateAction.Invoke(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Providers;
|
||||
using Telegrator.Handlers.Building.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.Handlers.Building
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for handler execution actions that take a container and cancellation token.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
|
||||
/// <param name="container">The handler container with execution context.</param>
|
||||
/// <param name="cancellation">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous execution.</returns>
|
||||
public delegate Task AbstractHandlerAction<TUpdate>(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation) where TUpdate : class;
|
||||
|
||||
/// <summary>
|
||||
/// Builder class for creating regular handlers that can process updates.
|
||||
/// Provides fluent API for configuring filters, state keepers, and other handler properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update to handle.</typeparam>
|
||||
public class HandlerBuilder<TUpdate> : HandlerBuilderBase, IRegularHandlerBuilder<TUpdate> where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerBuilder{TUpdate}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateType">The type of update this handler will process.</param>
|
||||
/// <param name="handlerCollection">The collection to register the built handler with.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the update type is not valid for TUpdate.</exception>
|
||||
public HandlerBuilder(UpdateType updateType, IHandlersCollection handlerCollection) : base(typeof(BuildedAbstractHandler<TUpdate>), updateType, handlerCollection)
|
||||
{
|
||||
if (!updateType.IsValidUpdateObject<TUpdate>())
|
||||
throw new ArgumentException("\"UpdateType." + updateType + "\" is not valid type for \"" + nameof(TUpdate) + "\" update object", nameof(updateType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an abstract handler with the specified execution action.
|
||||
/// </summary>
|
||||
/// <param name="executeHandler">The delegate action to execute when the handler is invoked.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when executeHandler is null.</exception>
|
||||
public IHandlersCollection Build(AbstractHandlerAction<TUpdate> executeHandler)
|
||||
{
|
||||
if (executeHandler == null)
|
||||
throw new ArgumentNullException(nameof(executeHandler));
|
||||
|
||||
BuildedAbstractHandler<TUpdate> instance = new BuildedAbstractHandler<TUpdate>(UpdateType, executeHandler);
|
||||
BuildImplicitDescriptor(instance);
|
||||
return HandlerCollection!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that marks a handler to process callback query updates.
|
||||
/// This handler will be triggered when users interact with inline keyboards or other callback mechanisms.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param>
|
||||
public sealed class CallbackQueryHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<CallbackQueryHandler>(UpdateType.CallbackQuery, concurrency)
|
||||
{
|
||||
/// <summary>
|
||||
/// Always returns true, allowing any callback query update to pass through this filter.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context (unused).</param>
|
||||
/// <returns>Always returns true to allow any callback query update.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context) => true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for handlers that process callback query updates.
|
||||
/// Provides a foundation for creating handlers that respond to user interactions with inline keyboards.
|
||||
/// </summary>
|
||||
public abstract class CallbackQueryHandler() : AbstractUpdateHandler<CallbackQuery>(UpdateType.CallbackQuery)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type-specific data from the callback query.
|
||||
/// Returns the data string, chat instance, or game short name depending on the callback query type.
|
||||
/// </summary>
|
||||
protected string TypeData
|
||||
{
|
||||
get => Input switch
|
||||
{
|
||||
{ Data: { } data } => data,
|
||||
{ ChatInstance: { } chatInstance } => chatInstance,
|
||||
{ GameShortName: { } gameShortName } => gameShortName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that marks a handler to process command messages.
|
||||
/// This handler will be triggered when users send bot commands (messages starting with '/').
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 1).</param>
|
||||
public class CommandHandlerAttribute(int concurrency = 1) : UpdateHandlerAttribute<CommandHandler>(UpdateType.Message, concurrency)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the command that was extracted from the message (without the '/' prefix and bot username).
|
||||
/// </summary>
|
||||
public string ReceivedCommand { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the update contains a valid bot command and extracts the command text.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the update.</param>
|
||||
/// <returns>True if the update contains a valid bot command; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context)
|
||||
{
|
||||
if (context.Input.Message is not { Entities.Length: > 0, Text.Length: > 0 } message)
|
||||
return false;
|
||||
|
||||
MessageEntity commandEntity = message.Entities[0];
|
||||
if (commandEntity.Type != MessageEntityType.BotCommand)
|
||||
return false;
|
||||
|
||||
ReceivedCommand = message.Text.Substring(commandEntity.Offset + 1, commandEntity.Length - 1);
|
||||
if (ReceivedCommand.Contains('@'))
|
||||
{
|
||||
string[] split = ReceivedCommand.Split('@');
|
||||
ReceivedCommand = split[0];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for handlers that process command messages.
|
||||
/// Provides functionality to extract and parse command arguments.
|
||||
/// </summary>
|
||||
public abstract class CommandHandler : MessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached array of command arguments.
|
||||
/// </summary>
|
||||
private string[]? _cmdArgsSplit;
|
||||
|
||||
/// <summary>
|
||||
/// Cached string representation of command arguments.
|
||||
/// </summary>
|
||||
private string? _argsString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command that was extracted from the message.
|
||||
/// </summary>
|
||||
protected string ReceivedCommand
|
||||
{
|
||||
get => CompletedFilters.Get<CommandHandlerAttribute>(0).ReceivedCommand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments string (everything after the command).
|
||||
/// </summary>
|
||||
protected string ArgumentsString
|
||||
{
|
||||
get => _argsString ??= ArgsStringify();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command arguments as an array of strings.
|
||||
/// </summary>
|
||||
protected string[] Arguments
|
||||
{
|
||||
get => _cmdArgsSplit ??= SplitArgs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the command arguments into an array of strings.
|
||||
/// </summary>
|
||||
/// <returns>An array of command arguments.</returns>
|
||||
private string[] SplitArgs()
|
||||
{
|
||||
if (Input.Text is not { Length: > 0 })
|
||||
return [];
|
||||
|
||||
return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the arguments string from the command message.
|
||||
/// </summary>
|
||||
/// <returns>The arguments string (everything after the command).</returns>
|
||||
private string ArgsStringify()
|
||||
{
|
||||
if (Input.Text is not { Length: > 0 })
|
||||
return string.Empty;
|
||||
|
||||
return Input.Text.Substring(ReceivedCommand.Length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for branching handlers that process command messages.
|
||||
/// Provides functionality to extract and parse command arguments for branching scenarios.
|
||||
/// </summary>
|
||||
public abstract class BranchingCommandHandler : BranchingMessageHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached array of command arguments.
|
||||
/// </summary>
|
||||
private string[]? _cmdArgsSplit;
|
||||
|
||||
/// <summary>
|
||||
/// Cached string representation of command arguments.
|
||||
/// </summary>
|
||||
private string? _argsString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command that was extracted from the message.
|
||||
/// </summary>
|
||||
protected string ReceivedCommand
|
||||
{
|
||||
get => CompletedFilters.Get<CommandHandlerAttribute>(0).ReceivedCommand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments string (everything after the command).
|
||||
/// </summary>
|
||||
protected string ArgumentsString
|
||||
{
|
||||
get => _argsString ??= ArgsStringify();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command arguments as an array of strings.
|
||||
/// </summary>
|
||||
protected string[] Arguments
|
||||
{
|
||||
get => _cmdArgsSplit ??= SplitArgs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the command arguments into an array of strings.
|
||||
/// </summary>
|
||||
/// <returns>An array of command arguments.</returns>
|
||||
private string[] SplitArgs()
|
||||
{
|
||||
if (Input.Text is not { Length: > 0 })
|
||||
return [];
|
||||
|
||||
return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the arguments string from the command message.
|
||||
/// </summary>
|
||||
/// <returns>The arguments string (everything after the command).</returns>
|
||||
private string ArgsStringify()
|
||||
{
|
||||
if (Input.Text is not { Length: > 0 })
|
||||
return string.Empty;
|
||||
|
||||
return Input.Text.Substring(ReceivedCommand.Length + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract handler for Telegram updates of type <typeparamref name="TUpdate"/>.
|
||||
/// </summary>
|
||||
public abstract class AbstractUpdateHandler<TUpdate> : UpdateHandlerBase, IHandlerContainerFactory where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Handler container for the current update.
|
||||
/// </summary>
|
||||
protected IAbstractHandlerContainer<TUpdate> Container { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Telegram Bot client associated with the current container.
|
||||
/// </summary>
|
||||
protected ITelegramBotClient Client => Container.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Incoming update of type <typeparamref name="TUpdate"/>.
|
||||
/// </summary>
|
||||
protected TUpdate Input => Container.ActualUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// The Telegram update being handled.
|
||||
/// </summary>
|
||||
protected Update HandlingUpdate => Container.HandlingUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Additional data associated with the handler execution.
|
||||
/// </summary>
|
||||
protected Dictionary<string, object> ExtraData => Container.ExtraData;
|
||||
|
||||
/// <summary>
|
||||
/// List of successfully passed filters.
|
||||
/// </summary>
|
||||
protected CompletedFiltersList CompletedFilters => Container.CompletedFilters;
|
||||
|
||||
/// <summary>
|
||||
/// Provider for awaiting asynchronous operations.
|
||||
/// </summary>
|
||||
protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="handlingUpdateType">The type of update to handle.</param>
|
||||
protected AbstractUpdateHandler(UpdateType handlingUpdateType) : base(handlingUpdateType)
|
||||
{
|
||||
if (!HandlingUpdateType.IsValidUpdateObject<TUpdate>())
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handler container for the specified awaiting provider and handler info.
|
||||
/// </summary>
|
||||
/// <param name="awaitingProvider">The awaiting provider.</param>
|
||||
/// <param name="handlerInfo">The handler descriptor info.</param>
|
||||
/// <returns>The created handler container.</returns>
|
||||
public virtual IHandlerContainer CreateContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo)
|
||||
{
|
||||
return new AbstractHandlerContainer<TUpdate>(awaitingProvider, handlerInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the handler logic using the specified container.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken)
|
||||
{
|
||||
Container = (IAbstractHandlerContainer<TUpdate>)container;
|
||||
await Execute(Container, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method to execute the update handling logic.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container.</param>
|
||||
/// <param name="cancellation">Cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public abstract Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.Reflection;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
using Telegrator.Providers;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for handlers that support branching execution based on different methods.
|
||||
/// Allows multiple handler methods to be defined in a single class, each with its own filters.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
|
||||
public abstract class BranchingUpdateHandler<TUpdate> : AbstractUpdateHandler<TUpdate>, IHandlerContainerFactory, ICustomDescriptorsProvider where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The method info for the current branch being executed.
|
||||
/// </summary>
|
||||
private MethodInfo? branchMethodInfo = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the binding flags used to discover branch methods.
|
||||
/// </summary>
|
||||
protected virtual BindingFlags BranchesBindingFlags => BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the allowed return types for branch methods.
|
||||
/// </summary>
|
||||
protected virtual Type[] AllowedBranchReturnTypes => [typeof(void), typeof(Task)];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cancellation token for the current execution.
|
||||
/// </summary>
|
||||
protected CancellationToken Cancellation { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BranchingUpdateHandler{TUpdate}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handlingUpdateType">The type of update this handler processes.</param>
|
||||
protected BranchingUpdateHandler(UpdateType handlingUpdateType)
|
||||
: base(handlingUpdateType) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BranchingUpdateHandler{TUpdate}"/> class with a specific branch method.
|
||||
/// </summary>
|
||||
/// <param name="handlingUpdateType">The type of update this handler processes.</param>
|
||||
/// <param name="branch">The specific branch method to execute.</param>
|
||||
protected BranchingUpdateHandler(UpdateType handlingUpdateType, MethodInfo branch)
|
||||
: base(handlingUpdateType) => branchMethodInfo = branch;
|
||||
|
||||
/// <summary>
|
||||
/// Describes all handler branches in this class.
|
||||
/// </summary>
|
||||
/// <returns>A collection of handler descriptors for each branch method.</returns>
|
||||
/// <exception cref="Exception">Thrown when no branch methods are found.</exception>
|
||||
public IEnumerable<HandlerDescriptor> DescribeHandlers()
|
||||
{
|
||||
Type thisType = GetType();
|
||||
UpdateHandlerAttributeBase updateHandlerAttribute = HandlerInspector.GetHandlerAttribute(thisType);
|
||||
IEnumerable<IFilter<Update>> handlerFilters = HandlerInspector.GetFilterAttributes(thisType, HandlingUpdateType);
|
||||
|
||||
MethodInfo[] handlerBranches = thisType.GetMethods().Where(branch => branch.DeclaringType == thisType).ToArray();
|
||||
if (handlerBranches.Length == 0)
|
||||
throw new Exception();
|
||||
|
||||
foreach (MethodInfo branch in handlerBranches)
|
||||
yield return DescribeBranch(branch, updateHandlerAttribute, handlerFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a specific branch method.
|
||||
/// </summary>
|
||||
/// <param name="branch">The branch method to describe.</param>
|
||||
/// <param name="handlerAttribute">The handler attribute for the class.</param>
|
||||
/// <param name="handlerFilters">The filters applied to the class.</param>
|
||||
/// <returns>A handler descriptor for the branch method.</returns>
|
||||
/// <exception cref="Exception">Thrown when the branch method has parameters or invalid return type.</exception>
|
||||
protected virtual HandlerDescriptor DescribeBranch(MethodInfo branch, UpdateHandlerAttributeBase handlerAttribute, IEnumerable<IFilter<Update>> handlerFilters)
|
||||
{
|
||||
Type thisType = GetType();
|
||||
|
||||
if (branch.GetParameters().Length != 0)
|
||||
throw new Exception();
|
||||
|
||||
if (!AllowedBranchReturnTypes.Any(branch.ReturnType.Equals))
|
||||
throw new Exception();
|
||||
|
||||
List<IFilter<Update>> branchFiltersList = HandlerInspector.GetFilterAttributes(branch, HandlingUpdateType).ToList();
|
||||
branchFiltersList.AddRange(handlerFilters);
|
||||
|
||||
DescriptorFiltersSet filtersSet = new DescriptorFiltersSet(
|
||||
handlerAttribute,
|
||||
HandlerInspector.GetStateKeeperAttribute(branch),
|
||||
branchFiltersList.ToArray());
|
||||
|
||||
return new HandlerBranchDescriptor(branch, HandlingUpdateType, handlerAttribute.GetIndexer(), filtersSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handler container for this branching handler.
|
||||
/// </summary>
|
||||
/// <param name="awaitingProvider">The awaiting provider for the container.</param>
|
||||
/// <param name="handlerInfo">The handler information.</param>
|
||||
/// <returns>A handler container for this branching handler.</returns>
|
||||
/// <exception cref="Exception">Thrown when the awaiting provider is not of the expected type.</exception>
|
||||
public override IHandlerContainer CreateContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo)
|
||||
{
|
||||
return new AbstractHandlerContainer<TUpdate>(awaitingProvider, handlerInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the current branch method.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container.</param>
|
||||
/// <param name="cancellation">The cancellation token.</param>
|
||||
/// <exception cref="Exception">Thrown when no branch method is set.</exception>
|
||||
public override async Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
|
||||
{
|
||||
if (branchMethodInfo is null)
|
||||
throw new Exception();
|
||||
|
||||
Cancellation = cancellation;
|
||||
await BranchExecuteWrapper(container, branchMethodInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the execution of a branch method, handling both void and Task return types.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container.</param>
|
||||
/// <param name="methodInfo">The method to execute.</param>
|
||||
protected virtual async Task BranchExecuteWrapper(IAbstractHandlerContainer<TUpdate> container, MethodInfo methodInfo)
|
||||
{
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
methodInfo.Invoke(this, []);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
object branchReturn = methodInfo.Invoke(this, []);
|
||||
if (branchReturn == null)
|
||||
return;
|
||||
|
||||
if (branchReturn is Task branchTask)
|
||||
await branchTask;
|
||||
}
|
||||
}
|
||||
|
||||
private class HandlerBranchDescriptor : HandlerDescriptor
|
||||
{
|
||||
public HandlerBranchDescriptor(MethodInfo method, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters)
|
||||
: base(DescriptorType.General, method.DeclaringType, updateType, indexer, filters)
|
||||
{
|
||||
DisplayString = string.Format("{0}+{1}", method.DeclaringType.Name, method.Name);
|
||||
InstanceFactory = () =>
|
||||
{
|
||||
BranchingUpdateHandler<TUpdate> handler = (BranchingUpdateHandler<TUpdate>)Activator.CreateInstance(method.DeclaringType);
|
||||
handler.branchMethodInfo = method;
|
||||
return handler;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an empty handler container that throws <see cref="NotImplementedException"/> for all members.
|
||||
/// </summary>
|
||||
public class EmptyHandlerContainer : IHandlerContainer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Update HandlingUpdate => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ITelegramBotClient Client => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<string, object> ExtraData => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CompletedFiltersList CompletedFilters => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handler containers that provide context and resources for update handlers.
|
||||
/// Contains all necessary information and services that handlers need during execution.
|
||||
/// </summary>
|
||||
public interface IHandlerContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Update"/> being handled.
|
||||
/// </summary>
|
||||
public Update HandlingUpdate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITelegramBotClient"/> used for this handler.
|
||||
/// </summary>
|
||||
public ITelegramBotClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extra data associated with the handler execution.
|
||||
/// </summary>
|
||||
public Dictionary<string, object> ExtraData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="CompletedFiltersList"/> for this handler.
|
||||
/// </summary>
|
||||
public CompletedFiltersList CompletedFilters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IAwaitingProvider"/> for awaiting operations.
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory interface for creating handler containers.
|
||||
/// Provides a way to create handler containers with specific providers and handler information.
|
||||
/// </summary>
|
||||
public interface IHandlerContainerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IHandlerContainer"/> for the specified awaiting provider and handler info.
|
||||
/// </summary>
|
||||
/// <param name="awaitingProvider">The <see cref="IAwaitingProvider"/> to use.</param>
|
||||
/// <param name="handlerInfo">The <see cref="DescribedHandlerInfo"/> for the handler.</param>
|
||||
/// <returns>A new <see cref="IHandlerContainer"/> instance.</returns>
|
||||
public IHandlerContainer CreateContainer(IAwaitingProvider awaitingProvider, DescribedHandlerInfo handlerInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Handlers.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for update handlers, providing execution and lifetime management for Telegram updates.
|
||||
/// </summary>
|
||||
public abstract class UpdateHandlerBase(UpdateType handlingUpdateType)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UpdateType"/> that this handler processes.
|
||||
/// </summary>
|
||||
public UpdateType HandlingUpdateType { get; } = handlingUpdateType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HandlerLifetimeToken"/> associated with this handler instance.
|
||||
/// </summary>
|
||||
public HandlerLifetimeToken LifetimeToken { get; } = new HandlerLifetimeToken();
|
||||
|
||||
/// <summary>
|
||||
/// Executes the handler logic and marks the lifetime as ended after execution.
|
||||
/// </summary>
|
||||
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task Execute(IHandlerContainer container, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ExecuteInternal(container, cancellationToken);
|
||||
LifetimeToken.LifetimeEnded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the handler logic for the given container and cancellation token.
|
||||
/// </summary>
|
||||
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a handler container for a specific update type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdate">The type of update handled by the container.</typeparam>
|
||||
public interface IAbstractHandlerContainer<TUpdate> : IHandlerContainer where TUpdate : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the actual update object of type <typeparamref name="TUpdate"/>.
|
||||
/// </summary>
|
||||
public TUpdate ActualUpdate { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegram.Bot.Types.ReplyMarkups;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.Handlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute that marks a handler to process message updates.
|
||||
/// This handler will be triggered when users send messages in chats.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param>
|
||||
public class MessageHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<MessageHandler>(UpdateType.Message, concurrency)
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the update contains a valid message.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context containing the update.</param>
|
||||
/// <returns>True if the update contains a message; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> context) => context.Input is { Message: { } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for handlers that process message updates.
|
||||
/// Provides convenient methods for sending replies and responses to messages.
|
||||
/// </summary>
|
||||
public abstract class MessageHandler() : AbstractUpdateHandler<Message>(UpdateType.Message)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a reply message to the current message.
|
||||
/// </summary>
|
||||
/// <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="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The sent message.</returns>
|
||||
protected async Task<Message> Reply(
|
||||
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,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> await Client.SendMessage(
|
||||
Input.Chat, text, parseMode, Input,
|
||||
replyMarkup, linkPreviewOptions,
|
||||
messageThreadId, entities,
|
||||
disableNotification, protectContent,
|
||||
messageEffectId, businessConnectionId,
|
||||
allowPaidBroadcast, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a response message to the current chat.
|
||||
/// </summary>
|
||||
/// <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="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The sent message.</returns>
|
||||
protected async Task<Message> Responce(
|
||||
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,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> await Client.SendMessage(
|
||||
Input.Chat, text, parseMode, replyParameters,
|
||||
replyMarkup, linkPreviewOptions,
|
||||
messageThreadId, entities,
|
||||
disableNotification, protectContent,
|
||||
messageEffectId, businessConnectionId,
|
||||
allowPaidBroadcast, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for branching handlers that process message updates.
|
||||
/// Provides convenient methods for sending replies and responses to messages in branching scenarios.
|
||||
/// </summary>
|
||||
public abstract class BranchingMessageHandler() : BranchingUpdateHandler<Message>(UpdateType.Message)
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a reply message to the current message.
|
||||
/// </summary>
|
||||
/// <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="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The sent message.</returns>
|
||||
protected async Task<Message> Reply(
|
||||
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,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> await Client.SendMessage(
|
||||
Input.Chat, text, parseMode, Input,
|
||||
replyMarkup, linkPreviewOptions,
|
||||
messageThreadId, entities,
|
||||
disableNotification, protectContent,
|
||||
messageEffectId, businessConnectionId,
|
||||
allowPaidBroadcast, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a response message to the current chat.
|
||||
/// </summary>
|
||||
/// <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="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The sent message.</returns>
|
||||
protected async Task<Message> Responce(
|
||||
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,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> await Client.SendMessage(
|
||||
Input.Chat, text, parseMode, replyParameters,
|
||||
replyMarkup, linkPreviewOptions,
|
||||
messageThreadId, entities,
|
||||
disableNotification, protectContent,
|
||||
messageEffectId, businessConnectionId,
|
||||
allowPaidBroadcast, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for reactive Telegram bot implementations.
|
||||
/// Defines the core properties and capabilities of a reactive bot.
|
||||
/// </summary>
|
||||
public interface IReactiveTelegramBot
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the update router for handling incoming updates.
|
||||
/// </summary>
|
||||
public IUpdateRouter UpdateRouter { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Telegrator.Handlers;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/*
|
||||
public class MethodHandlerDescriptor : HandlerDescriptor
|
||||
{
|
||||
public MethodHandlerDescriptor() : base(DescriptorType.General, )
|
||||
}
|
||||
|
||||
public class MethodHandler<TUpdate> : AbstractUpdateHandler<TUpdate>
|
||||
{
|
||||
public override Task Execute(IAbstractHandlerContainer<TUpdate> container, CancellationToken cancellation)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Polling;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about a described handler, including its context, client, and execution logic.
|
||||
/// </summary>
|
||||
public class DescribedHandlerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The update router associated with this handler.
|
||||
/// </summary>
|
||||
public readonly IUpdateRouter UpdateRouter;
|
||||
|
||||
/// <summary>
|
||||
/// The Telegram bot client used for this handler.
|
||||
/// </summary>
|
||||
public readonly ITelegramBotClient Client;
|
||||
|
||||
/// <summary>
|
||||
/// The handler instance being described.
|
||||
/// </summary>
|
||||
public readonly UpdateHandlerBase HandlerInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Extra data associated with the handler execution.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, object> ExtraData;
|
||||
|
||||
/// <summary>
|
||||
/// List of completed filters for this handler.
|
||||
/// </summary>
|
||||
public readonly CompletedFiltersList CompletedFilters;
|
||||
|
||||
/// <summary>
|
||||
/// The update being handled.
|
||||
/// </summary>
|
||||
public readonly Update HandlingUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Lifetime token for the handler instance.
|
||||
/// </summary>
|
||||
public HandlerLifetimeToken HandlerLifetime => HandlerInstance.LifetimeToken;
|
||||
|
||||
/// <summary>
|
||||
/// The handler container created during execution.
|
||||
/// </summary>
|
||||
public IHandlerContainer? HandlerContainer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display string for the handler (for debugging or logging).
|
||||
/// </summary>
|
||||
public string DisplayString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DescribedHandlerInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateRouter">The update router.</param>
|
||||
/// <param name="client">The Telegram bot client.</param>
|
||||
/// <param name="handlerInstance">The handler instance.</param>
|
||||
/// <param name="filterContext">The filter execution context.</param>
|
||||
/// <param name="displayString">Optional display string.</param>
|
||||
public DescribedHandlerInfo(IUpdateRouter updateRouter, ITelegramBotClient client, UpdateHandlerBase handlerInstance, FilterExecutionContext<Update> filterContext, string? displayString)
|
||||
{
|
||||
UpdateRouter = updateRouter;
|
||||
Client = client;
|
||||
HandlerInstance = handlerInstance;
|
||||
ExtraData = filterContext.Data;
|
||||
CompletedFilters = filterContext.CompletedFilters;
|
||||
HandlingUpdate = filterContext.Update;
|
||||
DisplayString = displayString ?? handlerInstance.GetType().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the handler logic asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
/// <exception cref="Exception">Thrown if the handler lifetime has ended or the handler is not a container factory.</exception>
|
||||
public async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
if (HandlerLifetime.IsEnded)
|
||||
throw new Exception();
|
||||
|
||||
IHandlerContainerFactory? containerFactory = HandlerInstance is IHandlerContainerFactory handlerDefainedContainerFactory
|
||||
? handlerDefainedContainerFactory
|
||||
: UpdateRouter.DefaultContainerFactory is not null
|
||||
? UpdateRouter.DefaultContainerFactory
|
||||
: throw new Exception();
|
||||
|
||||
try
|
||||
{
|
||||
HandlerContainer = containerFactory.CreateContainer(UpdateRouter.AwaitingProvider, this);
|
||||
await HandlerInstance.Execute(HandlerContainer, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Cancelled
|
||||
_ = 0xBAD + 0xC0DE;
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await UpdateRouter
|
||||
.HandleErrorAsync(Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a set of filters for a handler descriptor, including update and state keeper validators.
|
||||
/// </summary>
|
||||
public sealed class DescriptorFiltersSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Validator for the update object.
|
||||
/// </summary>
|
||||
public IFilter<Update>? UpdateValidator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validator for the state keeper.
|
||||
/// </summary>
|
||||
public IFilter<Update>? StateKeeperValidator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of update filters.
|
||||
/// </summary>
|
||||
public IFilter<Update>[]? UpdateFilters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DescriptorFiltersSet"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateValidator">Validator for the update object.</param>
|
||||
/// <param name="stateKeeperValidator">Validator for the state keeper.</param>
|
||||
/// <param name="updateFilters">Array of update filters.</param>
|
||||
public DescriptorFiltersSet(IFilter<Update>? updateValidator, IFilter<Update>? stateKeeperValidator, IFilter<Update>[]? updateFilters)
|
||||
{
|
||||
UpdateValidator = updateValidator;
|
||||
StateKeeperValidator = stateKeeperValidator;
|
||||
UpdateFilters = updateFilters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the filter context using all filters in the set.
|
||||
/// </summary>
|
||||
/// <param name="filterContext">The filter execution context.</param>
|
||||
/// <returns>True if all filters pass; otherwise, false.</returns>
|
||||
public bool Validate(FilterExecutionContext<Update> filterContext)
|
||||
{
|
||||
if (UpdateValidator != null)
|
||||
{
|
||||
if (!UpdateValidator.CanPass(filterContext))
|
||||
return false;
|
||||
|
||||
filterContext.CompletedFilters.Add(UpdateValidator);
|
||||
}
|
||||
|
||||
if (StateKeeperValidator != null)
|
||||
{
|
||||
if (!StateKeeperValidator.CanPass(filterContext))
|
||||
return false;
|
||||
|
||||
filterContext.CompletedFilters.Add(StateKeeperValidator);
|
||||
}
|
||||
|
||||
if (UpdateFilters != null)
|
||||
{
|
||||
foreach (IFilter<Update> filter in UpdateFilters)
|
||||
{
|
||||
if (!filter.CanPass(filterContext))
|
||||
return false;
|
||||
|
||||
filterContext.CompletedFilters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Telegrator.Attributes.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an indexer for handler descriptors, containing concurrency and priority information.
|
||||
/// </summary>
|
||||
public readonly struct DescriptorIndexer(int routerIndex, int concurrency, int priority) : IComparable<DescriptorIndexer>
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of this descriptor when it was added to router
|
||||
/// </summary>
|
||||
public readonly int RouterIndex = routerIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Of this handlert type
|
||||
/// </summary>
|
||||
public readonly int Importance = concurrency;
|
||||
|
||||
/// <summary>
|
||||
/// The priority of the handler.
|
||||
/// </summary>
|
||||
public readonly int Priority = priority;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DescriptorIndexer"/> struct from a handler attribute.
|
||||
/// </summary>
|
||||
/// <param name="routerIndex"></param>
|
||||
/// <param name="pollingHandler">The handler attribute.</param>
|
||||
public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler)
|
||||
: this(routerIndex, pollingHandler.Concurrency, pollingHandler.Priority) { }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="DescriptorIndexer"/> with updated priority.
|
||||
/// </summary>
|
||||
/// <param name="priority">The new priority value.</param>
|
||||
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
|
||||
public DescriptorIndexer UpdatePriority(int priority)
|
||||
=> new DescriptorIndexer(RouterIndex, Importance, priority);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="DescriptorIndexer"/> with updated concurrency.
|
||||
/// </summary>
|
||||
/// <param name="concurrency">The new concurrency value.</param>
|
||||
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
|
||||
public DescriptorIndexer UpdateConcurrency(int concurrency)
|
||||
=> new DescriptorIndexer(RouterIndex, concurrency, Priority);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex.
|
||||
/// </summary>
|
||||
/// <param name="routerIndex"></param>
|
||||
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
|
||||
public DescriptorIndexer UpdateIndex(int routerIndex)
|
||||
=> new DescriptorIndexer(routerIndex, Importance, Priority);
|
||||
|
||||
/// <summary>
|
||||
/// Compares this instance to another <see cref="DescriptorIndexer"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The other indexer to compare to.</param>
|
||||
/// <returns>An integer indicating the relative order.</returns>
|
||||
public int CompareTo(DescriptorIndexer other)
|
||||
{
|
||||
int importanceCmp = Importance.CompareTo(other.Importance);
|
||||
if (importanceCmp != 0)
|
||||
return importanceCmp;
|
||||
|
||||
int priorityCmp = Priority.CompareTo(other.Priority);
|
||||
if (priorityCmp != 0)
|
||||
return priorityCmp;
|
||||
|
||||
int routerIndexCmp = RouterIndex.CompareTo(other.RouterIndex);
|
||||
if (routerIndexCmp != 0)
|
||||
return routerIndexCmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the indexer.
|
||||
/// </summary>
|
||||
/// <returns>A string in the format (C:concurrency, P:priority).</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("(I:{0}, C:{1}, P:{2})", RouterIndex, Importance, Priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Filters.Components;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the type of handler descriptor.
|
||||
/// </summary>
|
||||
public enum DescriptorType
|
||||
{
|
||||
/// <summary>
|
||||
/// General handler descriptor.
|
||||
/// </summary>
|
||||
General,
|
||||
|
||||
/// <summary>
|
||||
/// Keyed handler descriptor (uses a service key).
|
||||
/// </summary>
|
||||
Keyed,
|
||||
|
||||
/// <summary>
|
||||
/// Implicit handler descriptor.
|
||||
/// </summary>
|
||||
Implicit,
|
||||
|
||||
/// <summary>
|
||||
/// Singleton handler descriptor (single instance).
|
||||
/// </summary>
|
||||
Singleton
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a handler, its type, filters, and instantiation logic.
|
||||
/// </summary>
|
||||
public class HandlerDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the descriptor.
|
||||
/// </summary>
|
||||
public DescriptorType Type
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the handler.
|
||||
/// </summary>
|
||||
public Type HandlerType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The update type handled by this handler.
|
||||
/// </summary>
|
||||
public UpdateType UpdateType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The indexer for handler concurrency and priority.
|
||||
/// </summary>
|
||||
public DescriptorIndexer Indexer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The set of filters associated with this handler.
|
||||
/// </summary>
|
||||
public DescriptorFiltersSet Filters
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The service key for keyed handlers.
|
||||
/// </summary>
|
||||
public object? ServiceKey
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating handler instances.
|
||||
/// </summary>
|
||||
public Func<UpdateHandlerBase>? InstanceFactory
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the handler, if applicable.
|
||||
/// </summary>
|
||||
public UpdateHandlerBase? SingletonInstance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display string for the handler (for debugging or logging).
|
||||
/// </summary>
|
||||
public string? DisplayString
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with the specified descriptor type and handler type.
|
||||
/// Automatically inspects the handler type to extract attributes, filters, and configuration.
|
||||
/// </summary>
|
||||
/// <param name="descriptorType">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler to describe</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the handler type is not compatible with the expected handler type</exception>
|
||||
public HandlerDescriptor(DescriptorType descriptorType, Type handlerType)
|
||||
{
|
||||
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(handlerType);
|
||||
if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType))
|
||||
throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable())));
|
||||
|
||||
StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType);
|
||||
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
|
||||
|
||||
Type = descriptorType;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = handlerAttribute.Type;
|
||||
Indexer = handlerAttribute.GetIndexer();
|
||||
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class as a keyed handler with the specified service key.
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The type of the handler to describe</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> is null</exception>
|
||||
public HandlerDescriptor(Type handlerType, object serviceKey) : this(DescriptorType.Keyed, handlerType)
|
||||
{
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with all basic properties.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="filters">The set of filters associated with this handler</param>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with singleton instance support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="filters">The set of filters associated with this handler</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="singletonInstance">The singleton instance of the handler</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, UpdateHandlerBase singletonInstance)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = filters;
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with instance factory support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="filters">The set of filters associated with this handler</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = filters;
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with service key and instance factory support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="filters">The set of filters associated with this handler</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = filters;
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute and filters.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = pollingHandlerAttribute.Type;
|
||||
Indexer = pollingHandlerAttribute.GetIndexer();
|
||||
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and singleton instance.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="singletonInstance">The singleton instance of the handler</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = pollingHandlerAttribute.Type;
|
||||
Indexer = pollingHandlerAttribute.GetIndexer();
|
||||
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and instance factory.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = pollingHandlerAttribute.Type;
|
||||
Indexer = pollingHandlerAttribute.GetIndexer();
|
||||
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, service key, and instance factory.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = pollingHandlerAttribute.Type;
|
||||
Indexer = pollingHandlerAttribute.GetIndexer();
|
||||
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="validateFilter">Optional validation filter</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and singleton instance support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="validateFilter">Optional validation filter</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="singletonInstance">The singleton instance of the handler</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and instance factory support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="validateFilter">Optional validation filter</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter, service key, and instance factory support.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the descriptor</param>
|
||||
/// <param name="handlerType">The type of the handler</param>
|
||||
/// <param name="updateType">The type of update this handler processes</param>
|
||||
/// <param name="indexer">The indexer for handler concurrency and priority</param>
|
||||
/// <param name="validateFilter">Optional validation filter</param>
|
||||
/// <param name="filters">Optional array of filters to apply</param>
|
||||
/// <param name="stateKeepFilter">Optional state keeping filter</param>
|
||||
/// <param name="serviceKey">The service key for dependency injection</param>
|
||||
/// <param name="instanceFactory">Factory for creating handler instances</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
|
||||
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
|
||||
{
|
||||
Type = type;
|
||||
HandlerType = handlerType;
|
||||
UpdateType = updateType;
|
||||
Indexer = indexer;
|
||||
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
|
||||
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
|
||||
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using System.Collections;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator;
|
||||
using Telegrator.Configuration;
|
||||
using Telegrator.MadiatorCore;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// The collection containing the <see cref="HandlerDescriptor"/>'s. Used to route <see cref="Update"/>'s in <see cref="IHandlersProvider"/>
|
||||
/// </summary>
|
||||
public sealed class HandlerDescriptorList : IEnumerable<HandlerDescriptor>
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly SortedList<DescriptorIndexer, HandlerDescriptor> _innerCollection;
|
||||
private readonly IHandlersCollectingOptions? _options;
|
||||
private readonly UpdateType _handlingType;
|
||||
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the collection is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="UpdateType"/> of handlers in this collection.
|
||||
/// </summary>
|
||||
public UpdateType HandlingType => _handlingType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public HandlerDescriptor this[int index]
|
||||
{
|
||||
get => _innerCollection.Values[index];
|
||||
set => _innerCollection.Values[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class without a specific <see cref="UpdateType"/>.
|
||||
/// </summary>
|
||||
public HandlerDescriptorList()
|
||||
: this(UpdateType.Unknown, default) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="updateType">The update type for the handlers.</param>
|
||||
/// <param name="options">The collecting options.</param>
|
||||
public HandlerDescriptorList(UpdateType updateType, IHandlersCollectingOptions? options)
|
||||
{
|
||||
_innerCollection = [];
|
||||
_handlingType = updateType;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new <see cref="HandlerDescriptor"/> to the collection.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The handler descriptor to add.</param>
|
||||
/// <exception cref="CollectionFrozenException">Thrown if the collection is frozen.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the update type does not match.</exception>
|
||||
public void Add(HandlerDescriptor descriptor)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
throw new CollectionFrozenException();
|
||||
|
||||
if (_handlingType != UpdateType.Unknown && descriptor.UpdateType != _handlingType)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
while (_innerCollection.TryGetValue(descriptor.Indexer, out HandlerDescriptor? conflictDescriptor))
|
||||
{
|
||||
int newIndex = count++;
|
||||
if (_options?.DescendDescriptorIndex ?? false)
|
||||
newIndex += -1;
|
||||
|
||||
descriptor.Indexer = descriptor.Indexer.UpdateIndex(count);
|
||||
}
|
||||
|
||||
_innerCollection.Add(descriptor.Indexer, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the collection contains a <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/>.
|
||||
/// </summary>
|
||||
/// <param name="indexer">The descriptor indexer.</param>
|
||||
/// <returns>True if the descriptor exists; otherwise, false.</returns>
|
||||
public bool ContainsKey(DescriptorIndexer indexer)
|
||||
{
|
||||
return _innerCollection.ContainsKey(indexer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/> from the collection.
|
||||
/// </summary>
|
||||
/// <param name="indexer">The descriptor indexer.</param>
|
||||
/// <returns>True if the descriptor was removed; otherwise, false.</returns>
|
||||
public bool Remove(DescriptorIndexer indexer)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _innerCollection.Remove(indexer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="HandlerDescriptor"/> from the collection.
|
||||
/// </summary>
|
||||
/// <param name="descriptor"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(HandlerDescriptor descriptor)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int index = _innerCollection.IndexOfValue(descriptor);
|
||||
if (index == -1)
|
||||
return false;
|
||||
|
||||
_innerCollection.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all descriptos from the <see cref="HandlerDescriptorList"/>
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_innerCollection.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes the <see cref="HandlerDescriptorList"/> and prohibits adding new elements to it.
|
||||
/// </summary>
|
||||
public void Freeze()
|
||||
{
|
||||
IsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<HandlerDescriptor> GetEnumerator()
|
||||
{
|
||||
return _innerCollection.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _innerCollection.Values.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Reflection;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Attributes.Components;
|
||||
using Telegrator.Filters.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for inspecting handler types and retrieving their attributes and filters.
|
||||
/// </summary>
|
||||
public static class HandlerInspector
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the handler attribute from the specified member info.
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The member info representing the handler type.</param>
|
||||
/// <returns>The handler attribute.</returns>
|
||||
public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType)
|
||||
{
|
||||
// Getting polling handler attribute
|
||||
IEnumerable<UpdateHandlerAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<UpdateHandlerAttributeBase>();
|
||||
|
||||
//
|
||||
return handlerAttrs.Single();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state keeper attribute from the specified member info, if present.
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The member info representing the handler type.</param>
|
||||
/// <returns>The state keeper attribute, or null if not present.</returns>
|
||||
public static StateKeeperAttributeBase? GetStateKeeperAttribute(MemberInfo handlerType)
|
||||
{
|
||||
// Getting polling handler attribute
|
||||
IEnumerable<StateKeeperAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<StateKeeperAttributeBase>();
|
||||
|
||||
//
|
||||
return handlerAttrs.Any() ? handlerAttrs.Single() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all filter attributes for the specified handler type and update type.
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The member info representing the handler type.</param>
|
||||
/// <param name="validUpdType">The valid update type.</param>
|
||||
/// <returns>An enumerable of filter attributes.</returns>
|
||||
public static IEnumerable<IFilter<Update>> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType)
|
||||
{
|
||||
//
|
||||
IEnumerable<UpdateFilterAttributeBase> filters = handlerType.GetCustomAttributes<UpdateFilterAttributeBase>();
|
||||
|
||||
//
|
||||
if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType)))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
UpdateFilterAttributeBase? lastFilterAttribute = null;
|
||||
foreach (UpdateFilterAttributeBase filterAttribute in filters)
|
||||
{
|
||||
if (!filterAttribute.ProcessModifiers(lastFilterAttribute))
|
||||
{
|
||||
lastFilterAttribute = null;
|
||||
yield return filterAttribute.AnonymousFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastFilterAttribute = filterAttribute;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Telegrator.MadiatorCore.Descriptors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a token that tracks the lifetime of a handler instance.
|
||||
/// </summary>
|
||||
public class HandlerLifetimeToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when the handler's lifetime has ended.
|
||||
/// </summary>
|
||||
public event Action<HandlerLifetimeToken>? OnLifetimeEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the handler's lifetime has ended.
|
||||
/// </summary>
|
||||
public bool IsEnded { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks the handler's lifetime as ended and triggers the event.
|
||||
/// </summary>
|
||||
public void LifetimeEnded()
|
||||
{
|
||||
IsEnded = true;
|
||||
OnLifetimeEnded?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for managing awaiting handlers that can wait for specific update types.
|
||||
/// </summary>
|
||||
public interface IAwaitingProvider : IHandlersProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the usage of a handler and returns a disposable object to manage its lifetime.
|
||||
/// </summary>
|
||||
/// <param name="handlerDescriptor">The <see cref="HandlerDescriptor"/> to use.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> that manages the handler's usage lifetime.</returns>
|
||||
public IDisposable UseHandler(HandlerDescriptor handlerDescriptor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for providers that collect and manage handler collections.
|
||||
/// Provides access to a collection of handlers for various processing operations.
|
||||
/// </summary>
|
||||
public interface ICollectingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the collection of handlers managed by this provider.
|
||||
/// </summary>
|
||||
public IHandlersCollection Handlers { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection class for managing handler descriptors organized by update type.
|
||||
/// Provides functionality for collecting, adding, and organizing handlers.
|
||||
/// </summary>
|
||||
public interface IHandlersCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the collection of <see cref="UpdateType"/>'s allowed by registered handlers
|
||||
/// </summary>
|
||||
public IEnumerable<UpdateType> AllowedTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of <see cref="UpdateType"/> keys for the handler lists.
|
||||
/// </summary>
|
||||
public IEnumerable<UpdateType> Keys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of <see cref="HandlerDescriptorList"/> values.
|
||||
/// </summary>
|
||||
public IEnumerable<HandlerDescriptorList> Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HandlerDescriptorList"/> for the specified <see cref="UpdateType"/>.
|
||||
/// </summary>
|
||||
/// <param name="updateType">The update type key.</param>
|
||||
/// <returns>The handler descriptor list for the given update type.</returns>
|
||||
public HandlerDescriptorList this[UpdateType updateType] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collects all handlers domain-wide and returns a new <see cref="IHandlersCollection"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IHandlersCollection"/> with all handlers collected.</returns>
|
||||
public IHandlersCollection CollectHandlersDomainWide();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="HandlerDescriptor"/> to the collection and returns the updated collection.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The handler descriptor to add.</param>
|
||||
/// <returns>The updated <see cref="IHandlersCollection"/>.</returns>
|
||||
public IHandlersCollection AddDescriptor(HandlerDescriptor descriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a handler of the specified type to the collection and returns the updated collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="THandler">The type of handler to add, must inherit from <see cref="UpdateHandlerBase"/>.</typeparam>
|
||||
/// <returns>The updated <see cref="IHandlersCollection"/>.</returns>
|
||||
public IHandlersCollection AddHandler<THandler>() where THandler : UpdateHandlerBase;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a handler of the specified type to the collection and returns the updated collection.
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The type of handler to add.</param>
|
||||
/// <returns>The updated <see cref="IHandlersCollection"/>.</returns>
|
||||
/// <exception cref="Exception">Thrown if the handler type is invalid.</exception>
|
||||
public IHandlersCollection AddHandler(Type handlerType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HandlerDescriptorList"/> for the specified <see cref="HandlerDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The handler descriptor.</param>
|
||||
/// <returns>The handler descriptor list containing the descriptor.</returns>
|
||||
public HandlerDescriptorList GetDescriptorList(HandlerDescriptor descriptor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to retrieve and describe handler information for updates.
|
||||
/// </summary>
|
||||
public interface IHandlersProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the collection of <see cref="UpdateType"/>'s allowed by registered handlers
|
||||
/// </summary>
|
||||
public IEnumerable<UpdateType> AllowedTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handlers for the specified update and context.
|
||||
/// </summary>
|
||||
/// <param name="updateRouter">The update router.</param>
|
||||
/// <param name="client">The Telegram bot client.</param>
|
||||
/// <param name="update">The update to handle.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>An enumerable of described handler info.</returns>
|
||||
public IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Describes all handler descriptors in the list for the given context.
|
||||
/// </summary>
|
||||
/// <param name="descriptors">The handler descriptor list.</param>
|
||||
/// <param name="updateRouter">The update router.</param>
|
||||
/// <param name="client">The Telegram bot client.</param>
|
||||
/// <param name="update">The update to handle.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>An enumerable of described handler info.</returns>
|
||||
public IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Describes a single handler descriptor for the given context.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The handler descriptor.</param>
|
||||
/// <param name="updateRouter">The update router.</param>
|
||||
/// <param name="client">The Telegram bot client.</param>
|
||||
/// <param name="update">The update to handle.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>The described handler info, or null if not applicable.</returns>
|
||||
public DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance of the handler for the specified descriptor.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The handler descriptor.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>The handler instance.</returns>
|
||||
public UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of bot commands supported by the provider.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of bot commands.</returns>
|
||||
public IEnumerable<BotCommand> GetBotCommands(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provider contains any handlers.
|
||||
/// </summary>
|
||||
/// <returns>True if the provider is empty; otherwise, false.</returns>
|
||||
public bool IsEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for polling providers that manage both regular and awaiting handlers.
|
||||
/// Provides access to handlers for different types of update processing during polling operations.
|
||||
/// </summary>
|
||||
public interface IPollingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IHandlersProvider"/> that manages handlers for polling.
|
||||
/// </summary>
|
||||
public IHandlersProvider HandlersProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IAwaitingProvider"/> that manages awaiting handlers for polling.
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Polling;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handling exceptions that occur during update routing operations.
|
||||
/// Provides a centralized way to handle and log errors that occur during bot operation.
|
||||
/// </summary>
|
||||
public interface IRouterExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles exceptions that occur during update routing.
|
||||
/// </summary>
|
||||
/// <param name="botClient">The <see cref="ITelegramBotClient"/> instance.</param>
|
||||
/// <param name="exception">The exception that occurred.</param>
|
||||
/// <param name="source">The <see cref="HandleErrorSource"/> indicating the source of the error.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a delegate for when a handler is enqueued.
|
||||
/// </summary>
|
||||
/// <param name="args">The <see cref="DescribedHandlerInfo"/> for the enqueued handler.</param>
|
||||
public delegate void HandlerEnqueued(DescribedHandlerInfo args);
|
||||
/// <summary>
|
||||
/// Represents a delegate for when a handler is executing.
|
||||
/// </summary>
|
||||
/// <param name="args">The <see cref="DescribedHandlerInfo"/> for the executing handler.</param>
|
||||
public delegate void HandlerExecuting(DescribedHandlerInfo args);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a pool for managing the execution and queuing of update handlers.
|
||||
/// </summary>
|
||||
public interface IUpdateHandlersPool : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a handler is enqueued.
|
||||
/// </summary>
|
||||
public event HandlerEnqueued? HandlerEnqueued;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a handler is executing.
|
||||
/// </summary>
|
||||
public event HandlerExecuting? HandlerExecuting;
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a collection of handlers for execution.
|
||||
/// </summary>
|
||||
/// <param name="handlers">The handlers to enqueue.</param>
|
||||
public void Enqueue(IEnumerable<DescribedHandlerInfo> handlers);
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a single handler for execution.
|
||||
/// </summary>
|
||||
/// <param name="handlerInfo">The handler to enqueue.</param>
|
||||
public void Enqueue(DescribedHandlerInfo handlerInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues a handler using its lifetime token.
|
||||
/// </summary>
|
||||
/// <param name="token">The <see cref="HandlerLifetimeToken"/> of the handler to dequeue.</param>
|
||||
public void Dequeue(HandlerLifetimeToken token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Telegram.Bot.Polling;
|
||||
using Telegrator.Configuration;
|
||||
using Telegrator.Handlers.Components;
|
||||
|
||||
namespace Telegrator.MadiatorCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for update routers that handle incoming updates and manage handler execution.
|
||||
/// Combines update handling capabilities with polling provider functionality and exception handling.
|
||||
/// </summary>
|
||||
public interface IUpdateRouter : IUpdateHandler, IPollingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TelegramBotOptions"/> for the router.
|
||||
/// </summary>
|
||||
public TelegramBotOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IUpdateHandlersPool"/> that manages handler execution.
|
||||
/// </summary>
|
||||
public IUpdateHandlersPool HandlersPool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IRouterExceptionHandler"/> for handling exceptions.
|
||||
/// </summary>
|
||||
public IRouterExceptionHandler? ExceptionHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default hand;er container factory
|
||||
/// </summary>
|
||||
public IHandlerContainerFactory? DefaultContainerFactory { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Polling;
|
||||
using Telegram.Bot.Requests;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Polling
|
||||
{
|
||||
/// <summary>
|
||||
/// Reactive implementation of <see cref="IUpdateReceiver"/> for polling updates from Telegram.
|
||||
/// Provides custom update receiving logic with error handling and configuration options.
|
||||
/// </summary>
|
||||
/// <param name="client">The Telegram bot client for making API requests.</param>
|
||||
/// <param name="options">Optional receiver options for configuring update polling behavior.</param>
|
||||
public class ReactiveUpdateReceiver(ITelegramBotClient client, ReceiverOptions? options) : IUpdateReceiver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the receiver options for configuring update polling behavior.
|
||||
/// </summary>
|
||||
public readonly ReceiverOptions? Options = options;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Telegram bot client for making API requests.
|
||||
/// </summary>
|
||||
public readonly ITelegramBotClient Client = client;
|
||||
|
||||
/// <summary>
|
||||
/// Receives updates from Telegram using long polling.
|
||||
/// Handles update processing, error handling, and cancellation.
|
||||
/// </summary>
|
||||
/// <param name="updateHandler">The update handler to process received updates.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to stop receiving updates.</param>
|
||||
/// <returns>A task representing the asynchronous update receiving operation.</returns>
|
||||
public async Task ReceiveAsync(IUpdateHandler updateHandler, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken).Token;
|
||||
GetUpdatesRequest request = new GetUpdatesRequest()
|
||||
{
|
||||
AllowedUpdates = Options?.AllowedUpdates ?? [],
|
||||
Limit = Options?.Limit.GetValueOrDefault(100),
|
||||
Offset = Options?.Offset
|
||||
};
|
||||
|
||||
if (Options?.DropPendingUpdates ?? false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Update[] array = await Client.GetUpdates(-1, 1, 0, [], cancellationToken).ConfigureAwait(false);
|
||||
request.Offset = array.Length != 0 ? array[^1].Id + 1 : 0;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Timeout = (int)Client.Timeout.TotalSeconds;
|
||||
foreach (Update update in await Client.SendRequest(request, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
request.Offset = update.Id + 1;
|
||||
await updateHandler.HandleUpdateAsync(Client, update, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
||||
}
|
||||
catch (Exception exception2)
|
||||
{
|
||||
await updateHandler.HandleErrorAsync(Client, exception2, HandleErrorSource.HandleUpdateError, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await updateHandler.HandleErrorAsync(Client, exception, HandleErrorSource.PollingError, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Telegrator.Configuration;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Polling
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUpdateHandlersPool"/> that manages the execution of handlers.
|
||||
/// Provides thread-safe queuing and execution of handlers with configurable concurrency limits.
|
||||
/// </summary>
|
||||
public class UpdateHandlersPool : IUpdateHandlersPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronization object for thread-safe operations.
|
||||
/// </summary>
|
||||
protected object SyncObj = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Event that signals when awaiting handlers are queued.
|
||||
/// </summary>
|
||||
protected ManualResetEventSlim AwaitingHandlersQueuedEvent = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore for controlling the number of concurrently executing handlers.
|
||||
/// </summary>
|
||||
protected SemaphoreSlim ExecutingHandlersSemaphore = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Queue for storing awaiting handlers.
|
||||
/// </summary>
|
||||
protected readonly ConcurrentQueue<DescribedHandlerInfo> AwaitingHandlersQueue = [];
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary for tracking currently executing handlers.
|
||||
/// </summary>
|
||||
protected readonly ConcurrentDictionary<HandlerLifetimeToken, Task> ExecutingHandlersPool = [];
|
||||
|
||||
/// <summary>
|
||||
/// The bot configuration options.
|
||||
/// </summary>
|
||||
protected readonly TelegramBotOptions Options;
|
||||
|
||||
/// <summary>
|
||||
/// The global cancellation token for stopping all operations.
|
||||
/// </summary>
|
||||
protected readonly CancellationToken GlobalCancellationToken;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating whether the pool has been disposed.
|
||||
/// </summary>
|
||||
protected bool disposed = false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event HandlerEnqueued? HandlerEnqueued;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event HandlerExecuting? HandlerExecuting;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateHandlersPool"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The bot configuration options.</param>
|
||||
/// <param name="globalCancellationToken">The global cancellation token.</param>
|
||||
public UpdateHandlersPool(TelegramBotOptions options, CancellationToken globalCancellationToken)
|
||||
{
|
||||
Options = options;
|
||||
GlobalCancellationToken = globalCancellationToken;
|
||||
|
||||
if (options.MaximumParallelWorkingHandlers != null)
|
||||
{
|
||||
ExecutingHandlersSemaphore = new SemaphoreSlim(options.MaximumParallelWorkingHandlers ?? 0);
|
||||
AwaitingHandlersQueuedEvent = new ManualResetEventSlim(false);
|
||||
}
|
||||
|
||||
if (Options.MaximumParallelWorkingHandlers != null)
|
||||
HandlersCheckpoint();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Enqueue(IEnumerable<DescribedHandlerInfo> handlers)
|
||||
{
|
||||
handlers.ForEach(Enqueue);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Enqueue(DescribedHandlerInfo handlerInfo)
|
||||
{
|
||||
if (Options.MaximumParallelWorkingHandlers == null)
|
||||
{
|
||||
Task.Run(async () => await ExecuteHandlerWrapper(handlerInfo));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (SyncObj)
|
||||
{
|
||||
AwaitingHandlersQueue.Enqueue(handlerInfo);
|
||||
HandlerEnqueued?.Invoke(handlerInfo);
|
||||
AwaitingHandlersQueuedEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dequeue(HandlerLifetimeToken token)
|
||||
{
|
||||
if (Options.MaximumParallelWorkingHandlers == null)
|
||||
return;
|
||||
|
||||
lock (SyncObj)
|
||||
{
|
||||
ExecutingHandlersPool.TryRemove(token, out _);
|
||||
ExecutingHandlersSemaphore.Release(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main checkpoint method that manages handler execution in a loop.
|
||||
/// Continuously processes queued handlers while respecting concurrency limits.
|
||||
/// </summary>
|
||||
protected virtual async void HandlersCheckpoint()
|
||||
{
|
||||
await Task.Yield();
|
||||
while (!GlobalCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!CanEnqueueHandler())
|
||||
{
|
||||
await ExecutingHandlersSemaphore.WaitAsync(GlobalCancellationToken);
|
||||
if (!CanEnqueueHandler())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryDequeueHandler(out DescribedHandlerInfo? enqueuedHandler))
|
||||
{
|
||||
AwaitingHandlersQueuedEvent.Reset();
|
||||
AwaitingHandlersQueuedEvent.Wait(GlobalCancellationToken);
|
||||
|
||||
if (!TryDequeueHandler(out enqueuedHandler))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enqueuedHandler == null)
|
||||
continue;
|
||||
|
||||
ExecuteHandler(enqueuedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a handler by creating a lifetime token and tracking the execution.
|
||||
/// </summary>
|
||||
/// <param name="enqueuedHandler">The handler to execute.</param>
|
||||
protected virtual void ExecuteHandler(DescribedHandlerInfo enqueuedHandler)
|
||||
{
|
||||
HandlerLifetimeToken lifetimeToken = enqueuedHandler.HandlerLifetime;
|
||||
lifetimeToken.OnLifetimeEnded += Dequeue;
|
||||
|
||||
Task executingHandler = ExecuteHandlerWrapper(enqueuedHandler);
|
||||
lock (SyncObj)
|
||||
ExecutingHandlersPool.TryAdd(lifetimeToken, executingHandler);
|
||||
|
||||
HandlerExecuting?.Invoke(enqueuedHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper method that executes a handler and handles exceptions.
|
||||
/// </summary>
|
||||
/// <param name="enqueuedHandler">The handler to execute.</param>
|
||||
/// <returns>A task representing the asynchronous execution.</returns>
|
||||
/// <exception cref="HandlerFaultedException">Thrown when the handler execution fails.</exception>
|
||||
protected virtual async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler)
|
||||
{
|
||||
try
|
||||
{
|
||||
await enqueuedHandler.Execute(GlobalCancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new HandlerFaultedException(enqueuedHandler, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a new handler can be enqueued based on the current execution count.
|
||||
/// </summary>
|
||||
/// <returns>True if a new handler can be enqueued; otherwise, false.</returns>
|
||||
protected virtual bool CanEnqueueHandler()
|
||||
{
|
||||
lock (SyncObj)
|
||||
{
|
||||
return ExecutingHandlersPool.Count < Options.MaximumParallelWorkingHandlers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to dequeue a handler from the awaiting queue.
|
||||
/// </summary>
|
||||
/// <param name="enqueuedHandler">The dequeued handler, if successful.</param>
|
||||
/// <returns>True if a handler was successfully dequeued; otherwise, false.</returns>
|
||||
protected virtual bool TryDequeueHandler(out DescribedHandlerInfo? enqueuedHandler)
|
||||
{
|
||||
lock (SyncObj)
|
||||
{
|
||||
return AwaitingHandlersQueue.TryDequeue(out enqueuedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the handlers pool and releases all resources.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (ExecutingHandlersSemaphore != null)
|
||||
{
|
||||
ExecutingHandlersSemaphore.Dispose();
|
||||
ExecutingHandlersSemaphore = null!;
|
||||
}
|
||||
|
||||
if (AwaitingHandlersQueuedEvent != null)
|
||||
{
|
||||
AwaitingHandlersQueuedEvent.Dispose();
|
||||
AwaitingHandlersQueuedEvent = null!;
|
||||
}
|
||||
|
||||
if (SyncObj != null)
|
||||
SyncObj = null!;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Polling;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Polling;
|
||||
using Telegrator.Configuration;
|
||||
using Telegrator.Handlers.Components;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Polling
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IUpdateRouter"/> that routes updates to appropriate handlers.
|
||||
/// Manages the distribution of updates between regular handlers and awaiting handlers.
|
||||
/// </summary>
|
||||
public class UpdateRouter : IUpdateRouter
|
||||
{
|
||||
/// <summary>
|
||||
/// The bot configuration options.
|
||||
/// </summary>
|
||||
private readonly TelegramBotOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// The provider for regular handlers.
|
||||
/// </summary>
|
||||
private readonly IHandlersProvider _handlersProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The provider for awaiting handlers.
|
||||
/// </summary>
|
||||
private readonly IAwaitingProvider _awaitingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The pool for managing handler execution.
|
||||
/// </summary>
|
||||
private readonly IUpdateHandlersPool _HandlersPool;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IHandlersProvider HandlersProvider => _handlersProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAwaitingProvider AwaitingProvider => _awaitingProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TelegramBotOptions Options => _options;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IUpdateHandlersPool HandlersPool => _HandlersPool;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IRouterExceptionHandler? ExceptionHandler { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IHandlerContainerFactory? DefaultContainerFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateRouter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="handlersProvider">The provider for regular handlers.</param>
|
||||
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
|
||||
/// <param name="options">The bot configuration options.</param>
|
||||
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_handlersProvider = handlersProvider;
|
||||
_awaitingProvider = awaitingProvider;
|
||||
_HandlersPool = new UpdateHandlersPool(_options, _options.GlobalCancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateRouter"/> class with a custom handlers pool.
|
||||
/// </summary>
|
||||
/// <param name="handlersProvider">The provider for regular handlers.</param>
|
||||
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
|
||||
/// <param name="options">The bot configuration options.</param>
|
||||
/// <param name="handlersPool">The custom handlers pool to use.</param>
|
||||
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool)
|
||||
{
|
||||
_options = options;
|
||||
_handlersProvider = handlersProvider;
|
||||
_awaitingProvider = awaitingProvider;
|
||||
_HandlersPool = handlersPool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles errors that occur during update processing.
|
||||
/// </summary>
|
||||
/// <param name="botClient">The Telegram bot client.</param>
|
||||
/// <param name="exception">The exception that occurred.</param>
|
||||
/// <param name="source">The source of the error.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous error handling operation.</returns>
|
||||
public virtual Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken)
|
||||
{
|
||||
ExceptionHandler?.HandleException(botClient, exception, source, cancellationToken);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming updates by routing them to appropriate handlers.
|
||||
/// </summary>
|
||||
/// <param name="botClient">The Telegram bot client.</param>
|
||||
/// <param name="update">The update to handle.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the asynchronous update handling operation.</returns>
|
||||
public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
|
||||
{
|
||||
// Queuing handlers for execution
|
||||
foreach (DescribedHandlerInfo handler in GetHandlers(botClient, update, cancellationToken))
|
||||
HandlersPool.Enqueue(handler);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private IEnumerable<DescribedHandlerInfo> GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
|
||||
{
|
||||
// Getting handlers in update awaiting pool
|
||||
IEnumerable<DescribedHandlerInfo> handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken);
|
||||
if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting)
|
||||
return handlers;
|
||||
|
||||
return handlers.Concat(HandlersProvider.GetHandlers(this, botClient, update, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Configuration;
|
||||
using Telegrator.MadiatorCore;
|
||||
using Telegrator.MadiatorCore.Descriptors;
|
||||
|
||||
namespace Telegrator.Providers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provider for managing awaiting handlers that can wait for specific update types.
|
||||
/// Extends HandlersProvider to provide functionality for creating and managing awaiter handlers.
|
||||
/// </summary>
|
||||
/// <param name="options">The bot configuration options.</param>
|
||||
/// <param name="botInfo">The bot information.</param>
|
||||
public class AwaitingProvider(TelegramBotOptions options, ITelegramBotInfo botInfo) : HandlersProvider([], options, botInfo), IAwaitingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// List of handler descriptors for awaiting handlers.
|
||||
/// </summary>
|
||||
protected readonly HandlerDescriptorList HandlersList = [];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return DescribeDescriptors(HandlersList, updateRouter, client, update, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDisposable UseHandler(HandlerDescriptor handlerDescriptor)
|
||||
{
|
||||
HandlerToken handlerToken = new HandlerToken(HandlersList, handlerDescriptor);
|
||||
handlerToken.Register();
|
||||
return handlerToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Token for managing the lifetime of a handler in the awaiting provider.
|
||||
/// Implements IDisposable to automatically remove the handler when disposed.
|
||||
/// </summary>
|
||||
/// <param name="handlersList">The list of handler descriptors.</param>
|
||||
/// <param name="handlerDescriptor">The handler descriptor to manage.</param>
|
||||
private readonly struct HandlerToken(HandlerDescriptorList handlersList, HandlerDescriptor handlerDescriptor) : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the handler descriptor in the handlers list.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">Thrown when the handler descriptor has no singleton instance.</exception>
|
||||
public readonly void Register()
|
||||
{
|
||||
if (handlerDescriptor.SingletonInstance == null)
|
||||
throw new Exception();
|
||||
|
||||
handlersList.Add(handlerDescriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the handler token by removing the handler descriptor from the list.
|
||||
/// </summary>
|
||||
public readonly void Dispose()
|
||||
{
|
||||
handlersList.Remove(handlerDescriptor.Indexer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user