diff --git a/src/Modix.Bot/Behaviors/CommandListeningBehavior.cs b/src/Modix.Bot/Behaviors/CommandListeningBehavior.cs index 00d9b25f3..c4c1a65ad 100644 --- a/src/Modix.Bot/Behaviors/CommandListeningBehavior.cs +++ b/src/Modix.Bot/Behaviors/CommandListeningBehavior.cs @@ -1,121 +1,82 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; - using Discord; using Discord.Commands; - -using Modix.Common.Messaging; -using Modix.Services.CommandHelp; +using MediatR; +using Modix.Bot.Notifications; +using Modix.Bot.Responders.CommandErrors; using Modix.Services.Core; -using Modix.Services.Utilities; - using Serilog; - using Stopwatch = System.Diagnostics.Stopwatch; -namespace Modix.Bot.Behaviors +namespace Modix.Bot.Behaviors; + +public class CommandListeningBehavior( + ICommandPrefixParser commandPrefixParser, + IServiceProvider serviceProvider, + CommandService commandService, + CommandErrorService commandErrorService, + IDiscordClient discordClient, + IAuthorizationService authorizationService) : INotificationHandler { - /// - /// Listens for user commands within messages received from Discord, and executes them, if found. - /// - public class CommandListeningBehavior : INotificationHandler + public async Task Handle(MessageReceivedNotificationV3 notification, CancellationToken cancellationToken = default) { - /// - /// Constructs a new , with the given dependencies. - /// - public CommandListeningBehavior( - ICommandPrefixParser commandPrefixParser, - IServiceProvider serviceProvider, - CommandService commandService, - CommandErrorHandler commandErrorHandler, - IDiscordClient discordClient, - IAuthorizationService authorizationService) - { - _commandPrefixParser = commandPrefixParser; - ServiceProvider = serviceProvider; - CommandService = commandService; - CommandErrorHandler = commandErrorHandler; - DiscordClient = discordClient; - AuthorizationService = authorizationService; - } + var stopwatch = new Stopwatch(); + stopwatch.Start(); - /// - public async Task HandleNotificationAsync(MessageReceivedNotification notification, CancellationToken cancellationToken = default) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); + if (!(notification.Message is IUserMessage userMessage) + || (userMessage.Author is null)) + return; - if (!(notification.Message is IUserMessage userMessage) - || (userMessage.Author is null)) - return; + if (!(userMessage.Author is IGuildUser author) + || (author.Guild is null) + || author.IsBot + || author.IsWebhook) + return; - if (!(userMessage.Author is IGuildUser author) - || (author.Guild is null) - || author.IsBot - || author.IsWebhook) - return; + if (userMessage.Content.Length <= 1) + return; - if (userMessage.Content.Length <= 1) - return; + var argPos = await commandPrefixParser.TryFindCommandArgPosAsync(userMessage, cancellationToken); - var argPos = await _commandPrefixParser.TryFindCommandArgPosAsync(userMessage, cancellationToken); - if (argPos is null) - return; + if (argPos is null) + return; - var commandContext = new CommandContext(DiscordClient, userMessage); + var commandContext = new CommandContext(discordClient, userMessage); - await AuthorizationService.OnAuthenticatedAsync(author.Id, author.Guild.Id, author.RoleIds.ToList()); + await authorizationService.OnAuthenticatedAsync(author.Id, author.Guild.Id, author.RoleIds.ToList()); - var commandResult = await CommandService.ExecuteAsync(commandContext, argPos.Value, ServiceProvider); + var commandResult = await commandService.ExecuteAsync(commandContext, argPos.Value, serviceProvider); - if(!commandResult.IsSuccess) - { - var error = $"{commandResult.Error}: {commandResult.ErrorReason}"; - - if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) - Log.Error(error); - else - Log.Warning(error); + if(!commandResult.IsSuccess) + { + var error = $"{commandResult.Error}: {commandResult.ErrorReason}"; - if (commandResult.Error == CommandError.Exception) - await commandContext.Channel.SendMessageAsync($"Error: {commandResult.ErrorReason}", allowedMentions: AllowedMentions.None); - else - await CommandErrorHandler.AssociateErrorAsync(userMessage, error); + if (string.Equals(commandResult.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) + { + Log.Error(error); + } + else + { + Log.Warning(error); } - stopwatch.Stop(); - Log.Information($"Command took {stopwatch.ElapsedMilliseconds}ms to process: {commandContext.Message}"); + if (commandResult.Error == CommandError.Exception) + { + await commandContext.Channel.SendMessageAsync($"Error: {commandResult.ErrorReason}", allowedMentions: AllowedMentions.None); + } + else + { + await commandErrorService.SignalError(userMessage, error); + } } - /// - /// The for the current service scope. - /// - internal protected IServiceProvider ServiceProvider { get; } - - /// - /// A used to parse and execute commands. - /// - internal protected CommandService CommandService { get; } - - /// - /// A used to report and track command errors, in the Discord UI. - /// - internal protected CommandErrorHandler CommandErrorHandler { get; } - - /// - /// An used to interact with the Discord API. - /// - internal protected IDiscordClient DiscordClient { get; } - - /// - /// An used to interact with the application authorization system. - /// - internal protected IAuthorizationService AuthorizationService { get; } + stopwatch.Stop(); - private readonly ICommandPrefixParser _commandPrefixParser; - } + Log.Information("Command took {StopwatchElapsedMilliseconds}ms to process: {CommandContextMessage}", + stopwatch.ElapsedMilliseconds, + commandContext.Message); + } } diff --git a/src/Modix.Bot/ModixBot.cs b/src/Modix.Bot/ModixBot.cs index 5e08e3b61..ad501c027 100644 --- a/src/Modix.Bot/ModixBot.cs +++ b/src/Modix.Bot/ModixBot.cs @@ -58,6 +58,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) discordSocketClient.Ready += OnClientReady; discordSocketClient.MessageReceived += OnMessageReceived; discordSocketClient.MessageUpdated += OnMessageUpdated; + discordSocketClient.MessageDeleted += OnMessageDeleted; + discordSocketClient.ReactionAdded += OnReactionAdded; + discordSocketClient.ReactionRemoved += OnReactionRemoved; discordRestClient.Log += discordSerilogAdapter.HandleLog; commandService.Log += discordSerilogAdapter.HandleLog; @@ -195,6 +198,9 @@ private void UnregisterClientHandlers() discordSocketClient.MessageReceived -= OnMessageReceived; discordSocketClient.MessageUpdated -= OnMessageUpdated; + discordSocketClient.MessageDeleted -= OnMessageDeleted; + discordSocketClient.ReactionAdded -= OnReactionAdded; + discordSocketClient.ReactionRemoved -= OnReactionRemoved; } private async Task OnClientReady() @@ -217,6 +223,27 @@ private async Task OnMessageUpdated(Cacheable cachedMessage, So await mediator.Publish(new MessageUpdatedNotificationV3(cachedMessage, newMessage, channel)); } + private async Task OnMessageDeleted(Cacheable message, Cacheable channel) + { + using var scope = serviceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + await mediator.Publish(new MessageDeletedNotificationV3(message, channel)); + } + + private async Task OnReactionAdded(Cacheable message, Cacheable channel, SocketReaction reaction) + { + using var scope = serviceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + await mediator.Publish(new ReactionAddedNotificationV3(message, channel, reaction)); + } + + private async Task OnReactionRemoved(Cacheable message, Cacheable channel, SocketReaction reaction) + { + using var scope = serviceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + await mediator.Publish(new ReactionRemovedNotificationV3(message, channel, reaction)); + } + public override void Dispose() { try diff --git a/src/Modix.Bot/Modules/DocumentationModule.cs b/src/Modix.Bot/Modules/DocumentationModule.cs index 8ecc754c9..aca416b57 100644 --- a/src/Modix.Bot/Modules/DocumentationModule.cs +++ b/src/Modix.Bot/Modules/DocumentationModule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Discord; using Discord.Interactions; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Services.AutoRemoveMessage; using Modix.Services.CommandHelp; using Modix.Services.Csharp; diff --git a/src/Modix.Bot/Modules/IlModule.cs b/src/Modix.Bot/Modules/IlModule.cs index 3639fb784..95d289069 100644 --- a/src/Modix.Bot/Modules/IlModule.cs +++ b/src/Modix.Bot/Modules/IlModule.cs @@ -6,6 +6,7 @@ using Discord; using Discord.Commands; using Microsoft.Extensions.Options; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Data.Models.Core; using Modix.Services; using Modix.Services.AutoRemoveMessage; diff --git a/src/Modix.Bot/Modules/LegacyLinkModule.cs b/src/Modix.Bot/Modules/LegacyLinkModule.cs index 86732e117..a8609e2ac 100644 --- a/src/Modix.Bot/Modules/LegacyLinkModule.cs +++ b/src/Modix.Bot/Modules/LegacyLinkModule.cs @@ -7,6 +7,7 @@ using Discord; using Discord.Commands; using LZStringCSharp; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Services.AutoRemoveMessage; using Modix.Services.Utilities; diff --git a/src/Modix.Bot/Modules/ReplModule.cs b/src/Modix.Bot/Modules/ReplModule.cs index f4f39ae5d..b83147c6c 100644 --- a/src/Modix.Bot/Modules/ReplModule.cs +++ b/src/Modix.Bot/Modules/ReplModule.cs @@ -7,6 +7,7 @@ using Discord; using Discord.Commands; using Microsoft.Extensions.Options; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Data.Models.Core; using Modix.Services; using Modix.Services.AutoRemoveMessage; diff --git a/src/Modix.Bot/Modules/SharpLabModule.cs b/src/Modix.Bot/Modules/SharpLabModule.cs index af2f5e8c5..5279880f9 100644 --- a/src/Modix.Bot/Modules/SharpLabModule.cs +++ b/src/Modix.Bot/Modules/SharpLabModule.cs @@ -7,6 +7,7 @@ using Discord; using Discord.Interactions; using LZStringCSharp; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Services.AutoRemoveMessage; using Modix.Services.CommandHelp; using Modix.Services.Utilities; diff --git a/src/Modix.Bot/Modules/UserInfoModule.cs b/src/Modix.Bot/Modules/UserInfoModule.cs index 7bb5d87af..45c39a49d 100644 --- a/src/Modix.Bot/Modules/UserInfoModule.cs +++ b/src/Modix.Bot/Modules/UserInfoModule.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Modix.Bot.Extensions; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Common.Extensions; using Modix.Data.Models; using Modix.Data.Models.Core; diff --git a/src/Modix.Bot/Notifications/MessageDeletedNotificationV3.cs b/src/Modix.Bot/Notifications/MessageDeletedNotificationV3.cs new file mode 100644 index 000000000..bff1a59f8 --- /dev/null +++ b/src/Modix.Bot/Notifications/MessageDeletedNotificationV3.cs @@ -0,0 +1,11 @@ +using Discord; +using MediatR; + +namespace Modix.Bot.Notifications; + +public class MessageDeletedNotificationV3(Cacheable message, Cacheable channel) + : INotification +{ + public Cacheable Message { get; } = message; + public Cacheable Channel { get; } = channel; +} diff --git a/src/Modix.Bot/Notifications/MessageUpdatedNotificationV3.cs b/src/Modix.Bot/Notifications/MessageUpdatedNotificationV3.cs index 43d8a42ee..a5c313326 100644 --- a/src/Modix.Bot/Notifications/MessageUpdatedNotificationV3.cs +++ b/src/Modix.Bot/Notifications/MessageUpdatedNotificationV3.cs @@ -4,9 +4,12 @@ namespace Modix.Bot.Notifications; -public class MessageUpdatedNotificationV3(Cacheable cachedMessage, SocketMessage newMessage, ISocketMessageChannel channel) : INotification +public class MessageUpdatedNotificationV3( + Cacheable oldMessage, + SocketMessage newMessage, + ISocketMessageChannel channel) : INotification { - public Cacheable Cached { get; } = cachedMessage; - public SocketMessage Message { get; } = newMessage; + public Cacheable OldMessage { get; } = oldMessage; + public SocketMessage NewMessage { get; } = newMessage; public ISocketMessageChannel Channel { get; } = channel; } diff --git a/src/Modix.Bot/Notifications/ReactionAddedNotificationV3.cs b/src/Modix.Bot/Notifications/ReactionAddedNotificationV3.cs new file mode 100644 index 000000000..b135fdf8a --- /dev/null +++ b/src/Modix.Bot/Notifications/ReactionAddedNotificationV3.cs @@ -0,0 +1,15 @@ +using Discord; +using Discord.WebSocket; +using MediatR; + +namespace Modix.Bot.Notifications; + +public class ReactionAddedNotificationV3( + Cacheable message, + Cacheable channel, + SocketReaction reaction) : INotification +{ + public Cacheable Message { get; } = message; + public Cacheable Channel { get; } = channel; + public SocketReaction Reaction { get; } = reaction; +} diff --git a/src/Modix.Bot/Notifications/ReactionRemovedNotificationV3.cs b/src/Modix.Bot/Notifications/ReactionRemovedNotificationV3.cs new file mode 100644 index 000000000..b33a5ddbf --- /dev/null +++ b/src/Modix.Bot/Notifications/ReactionRemovedNotificationV3.cs @@ -0,0 +1,15 @@ +using Discord; +using Discord.WebSocket; +using MediatR; + +namespace Modix.Bot.Notifications; + +public class ReactionRemovedNotificationV3( + Cacheable message, + Cacheable channel, + SocketReaction reaction): INotification +{ + public Cacheable Message { get; } = message; + public Cacheable Channel { get; } = channel; + public SocketReaction Reaction { get; } = reaction; +} diff --git a/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageResponder.cs b/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageResponder.cs new file mode 100644 index 000000000..7168cdbd2 --- /dev/null +++ b/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageResponder.cs @@ -0,0 +1,26 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Modix.Bot.Notifications; + +namespace Modix.Bot.Responders.AutoRemoveMessages; + +public class AutoRemoveMessageResponder(AutoRemoveMessageService service) : + INotificationHandler +{ + public async Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested + || notification.Reaction.Emote.Name != "❌" + || !service.IsKnownRemovableMessage(notification.Message.Id, out var cachedMessage) + || !cachedMessage.Users.Any(user => user.Id == notification.Reaction.UserId)) + { + return; + } + + await cachedMessage.Message.DeleteAsync(); + + service.UnregisterRemovableMessage(cachedMessage.Message); + } +} diff --git a/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageService.cs b/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageService.cs new file mode 100644 index 000000000..c3c4b1882 --- /dev/null +++ b/src/Modix.Bot/Responders/AutoRemoveMessages/AutoRemoveMessageService.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Microsoft.Extensions.Caching.Memory; +using Modix.Services.AutoRemoveMessage; + +namespace Modix.Bot.Responders.AutoRemoveMessages; + +public class AutoRemoveMessageService(IMemoryCache memoryCache) +{ + private static readonly MemoryCacheEntryOptions _messageCacheOptions = + new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(60)); + + private const string FOOTER_MESSAGE = "React with ❌ to remove this embed."; + + public Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, + Func> callback) + { + return RegisterRemovableMessageAsync([user], embed, callback); + } + + public async Task RegisterRemovableMessageAsync(IUser[] users, EmbedBuilder embed, + Func> callback) + { + if (callback == null) + throw new ArgumentNullException(nameof(callback)); + + if (embed.Footer?.Text == null) + { + embed.WithFooter(FOOTER_MESSAGE); + } + else if (!embed.Footer.Text.Contains(FOOTER_MESSAGE)) + { + embed.Footer.Text += $" | {FOOTER_MESSAGE}"; + } + + var message = await callback.Invoke(embed); + + memoryCache.Set( + GetCacheKey(message.Id), + new RemovableMessage + { + Message = message, + Users = users + }, + _messageCacheOptions); + } + + public bool IsKnownRemovableMessage(ulong messageId, out RemovableMessage removableMessage) + { + var key = GetCacheKey(messageId); + + if (memoryCache.TryGetValue(key, out var cached)) + { + removableMessage = (RemovableMessage)cached; + return true; + } + + removableMessage = default; + return false; + } + + public void UnregisterRemovableMessage(IMessage message) + { + var key = GetCacheKey(message.Id); + + if (!memoryCache.TryGetValue(key, out _)) + { + return; + } + + memoryCache.Remove(key); + } + + + private static object GetCacheKey(ulong messageId) + => new + { + MessageId = messageId, + Target = "RemovableMessage", + }; +} diff --git a/src/Modix.Services/AutoRemoveMessage/RemovableMessage.cs b/src/Modix.Bot/Responders/AutoRemoveMessages/RemovableMessage.cs similarity index 100% rename from src/Modix.Services/AutoRemoveMessage/RemovableMessage.cs rename to src/Modix.Bot/Responders/AutoRemoveMessages/RemovableMessage.cs diff --git a/src/Modix.Bot/Responders/CommandErrors/CommandErrorResponder.cs b/src/Modix.Bot/Responders/CommandErrors/CommandErrorResponder.cs new file mode 100644 index 000000000..71a4ddab1 --- /dev/null +++ b/src/Modix.Bot/Responders/CommandErrors/CommandErrorResponder.cs @@ -0,0 +1,102 @@ +using System.Threading; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using MediatR; +using Modix.Bot.Notifications; + +namespace Modix.Bot.Responders.CommandErrors; + +public class CommandErrorResponder(DiscordSocketClient discordSocketClient, CommandErrorService service) : + INotificationHandler, + INotificationHandler +{ + private readonly IEmote _emote = new Emoji(CommandErrorService.EMOJI); + + public Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken) + => ReactionAddedAsync(notification.Message, notification.Channel, notification.Reaction); + + public async Task ReactionAddedAsync(Cacheable cachedMessage, Cacheable cachedChannel, SocketReaction reaction) + { + //Don't trigger if the emoji is wrong, if the user is a bot, or if we've + //made an error message reply already + + if (reaction.User.IsSpecified && reaction.User.Value.IsBot) + { + return; + } + + if (reaction.Emote.Name != CommandErrorService.EMOJI || service.ContainsErrorReply(cachedMessage.Id)) + { + return; + } + + // If the message that was reacted to has an associated error, reply in the same channel + // with the error message then add that to the replies collection + if (service.TryGetAssociatedError(cachedMessage.Id, out var value)) + { + var channel = await cachedChannel.GetOrDownloadAsync(); + var msg = await channel.SendMessageAsync("", false, new EmbedBuilder() + { + Author = new EmbedAuthorBuilder + { + IconUrl = "https://raw.githubusercontent.com/twitter/twemoji/gh-pages/2/72x72/26a0.png", + Name = "That command had an error" + }, + Description = value, + Footer = new EmbedFooterBuilder { Text = "Remove your reaction to delete this message" } + }.Build()); + + if (service.TryAddErrorReply(cachedMessage.Id, msg.Id) == false) + { + await msg.DeleteAsync(); + } + } + } + + public Task Handle(ReactionRemovedNotificationV3 notification, CancellationToken cancellationToken) + => ReactionRemovedAsync(notification.Message, notification.Channel, notification.Reaction); + + public async Task ReactionRemovedAsync(Cacheable cachedMessage, Cacheable cachedChannel, SocketReaction reaction) + { + //Bugfix for NRE? + if (reaction is null || reaction.User.Value is null) + { + return; + } + + //Don't trigger if the emoji is wrong, or if the user is bot + if (reaction.User.IsSpecified && reaction.User.Value.IsBot) + { + return; + } + + if (reaction.Emote.Name != CommandErrorService.EMOJI) + { + return; + } + + //If there's an error reply when the reaction is removed, delete that reply, + //remove the cached error, remove it from the cached replies, and remove + //the reactions from the original message + if (service.TryGetErrorReply(cachedMessage.Id, out var botReplyId) == false) { return; } + + var channel = await cachedChannel.GetOrDownloadAsync(); + await channel.DeleteMessageAsync(botReplyId); + + if (service.TryRemoveAssociatedError(cachedMessage.Id) && service.TryRemoveErrorReply(cachedMessage.Id)) + { + var originalMessage = await cachedMessage.GetOrDownloadAsync(); + + //If we know what user added the reaction, remove their and our reaction + //Otherwise just remove ours + + if (reaction.User.IsSpecified) + { + await originalMessage.RemoveReactionAsync(_emote, reaction.User.Value); + } + + await originalMessage.RemoveReactionAsync(_emote, discordSocketClient.CurrentUser); + } + } +} diff --git a/src/Modix.Bot/Responders/CommandErrors/CommandErrorService.cs b/src/Modix.Bot/Responders/CommandErrors/CommandErrorService.cs new file mode 100644 index 000000000..e7152509c --- /dev/null +++ b/src/Modix.Bot/Responders/CommandErrors/CommandErrorService.cs @@ -0,0 +1,58 @@ +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Discord; +using Microsoft.Extensions.Caching.Memory; + +namespace Modix.Bot.Responders.CommandErrors; + +public class CommandErrorService(IMemoryCache memoryCache) +{ + private const string AssociatedErrorsKey = nameof(CommandErrorService) + ".AssociatedErrors"; + private const string ErrorRepliesKey = nameof(CommandErrorService) + ".ErrorReplies"; + + public const string EMOJI = "⚠"; + + private ConcurrentDictionary AssociatedErrors => + memoryCache.GetOrCreate(AssociatedErrorsKey, _ => new ConcurrentDictionary()); + + private ConcurrentDictionary ErrorReplies => + memoryCache.GetOrCreate(ErrorRepliesKey, _ => new ConcurrentDictionary()); + + public async Task SignalError(IMessage message, string error) + { + if (AssociatedErrors.TryAdd(message.Id, error)) + { + await message.AddReactionAsync(new Emoji(EMOJI)); + } + } + + public bool ContainsErrorReply(ulong cachedMessageId) + { + return ErrorReplies.ContainsKey(cachedMessageId); + } + + public bool TryGetAssociatedError(ulong cachedMessageId, out string output) + { + return AssociatedErrors.TryGetValue(cachedMessageId, out output); + } + + public bool TryAddErrorReply(ulong cachedMessageId, ulong messageId) + { + return ErrorReplies.TryAdd(cachedMessageId, messageId); + } + + public bool TryGetErrorReply(ulong cachedMessageId, out ulong output) + { + return ErrorReplies.TryGetValue(cachedMessageId, out output); + } + + public bool TryRemoveAssociatedError(ulong cachedMessageId) + { + return AssociatedErrors.TryRemove(cachedMessageId, out _); + } + + public bool TryRemoveErrorReply(ulong cachedMessageId) + { + return ErrorReplies.TryRemove(cachedMessageId, out _); + } +} diff --git a/src/Modix.Services/EmojiStats/EmojiUsageHandler.cs b/src/Modix.Bot/Responders/EmojiUseResponder.cs similarity index 60% rename from src/Modix.Services/EmojiStats/EmojiUsageHandler.cs rename to src/Modix.Bot/Responders/EmojiUseResponder.cs index b818f1eec..1c039c075 100644 --- a/src/Modix.Services/EmojiStats/EmojiUsageHandler.cs +++ b/src/Modix.Bot/Responders/EmojiUseResponder.cs @@ -2,42 +2,26 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; - using Discord; using Discord.WebSocket; - -using Modix.Common.Messaging; +using MediatR; +using Modix.Bot.Notifications; using Modix.Data.Models.Emoji; using Modix.Data.Repositories; using Modix.Services.Utilities; -namespace Modix.Services.EmojiStats +namespace Modix.Bot.Responders { - /// - /// Implements a handler that maintains MODiX's record of emoji. - /// - public sealed class EmojiUsageHandler : - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler + public sealed class EmojiUseResponder(IEmojiRepository emojiRepository) : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - private readonly IEmojiRepository _emojiRepository; + private static readonly Regex _emojiRegex = new($@"(|{EmojiUtilities.EmojiPattern})", RegexOptions.Compiled); - public static readonly Regex EmojiRegex = new($@"(|{EmojiUtilities.EmojiPattern})", RegexOptions.Compiled); - - /// - /// Constructs a new object with the given injected dependencies. - /// - /// Repository for managing emoji entities within an underlying data storage provider. - public EmojiUsageHandler( - IEmojiRepository emojiRepository) - { - _emojiRepository = emojiRepository; - } - - public async Task HandleNotificationAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) + public async Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -59,21 +43,11 @@ public async Task HandleNotificationAsync(ReactionAddedNotification notification await LogReactionAsync(channel, message, reaction, emote); } - /// - /// Logs a reaction in the database. - /// - /// The channel that the emoji was used in. - /// The message associated with the emoji. - /// The emoji that was used. - /// The emote that was used, if any. - /// - /// A that will complete when the operation completes. - /// private async Task LogReactionAsync(ITextChannel channel, IUserMessage message, SocketReaction reaction, Emote emote) { - using var transaction = await _emojiRepository.BeginMaintainTransactionAsync(); + using var transaction = await emojiRepository.BeginMaintainTransactionAsync(); - await _emojiRepository.CreateAsync(new EmojiCreationData() + await emojiRepository.CreateAsync(new EmojiCreationData() { GuildId = channel.GuildId, ChannelId = channel.Id, @@ -88,7 +62,7 @@ await _emojiRepository.CreateAsync(new EmojiCreationData() transaction.Commit(); } - public async Task HandleNotificationAsync(ReactionRemovedNotification notification, CancellationToken cancellationToken) + public async Task Handle(ReactionRemovedNotificationV3 notification, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -110,21 +84,11 @@ public async Task HandleNotificationAsync(ReactionRemovedNotification notificati await UnlogReactionAsync(channel, message, reaction, emote); } - /// - /// Unlogs an emoji from the database. - /// - /// The channel that the emoji was used in. - /// The message associated with the emoji. - /// The emoji that was used. - /// The emote that was used, if any. - /// - /// A that will complete when the operation completes. - /// private async Task UnlogReactionAsync(ITextChannel channel, IUserMessage message, SocketReaction reaction, Emote emote) { - using var transaction = await _emojiRepository.BeginMaintainTransactionAsync(); + using var transaction = await emojiRepository.BeginMaintainTransactionAsync(); - await _emojiRepository.DeleteAsync(new EmojiSearchCriteria() + await emojiRepository.DeleteAsync(new EmojiSearchCriteria() { GuildId = channel.GuildId, ChannelId = channel.Id, @@ -140,7 +104,7 @@ await _emojiRepository.DeleteAsync(new EmojiSearchCriteria() transaction.Commit(); } - public async Task HandleNotificationAsync(MessageReceivedNotification notification, CancellationToken cancellationToken) + public async Task Handle(MessageReceivedNotificationV3 notification, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -151,12 +115,12 @@ public async Task HandleNotificationAsync(MessageReceivedNotification notificati if (notification.Message is not { Author: { IsBot: false }, Content: not null } message) return; - var newEmoji = EmojiRegex.Matches(message.Content); + var newEmoji = _emojiRegex.Matches(message.Content); if (newEmoji.Count == 0) return; - foreach (var (emoji, count) in newEmoji.Cast().GroupBy(x => x.Value).Select(x => (x.Key, x.Count()))) + foreach (var (emoji, count) in newEmoji.GroupBy(x => x.Value).Select(x => (x.Key, x.Count()))) { var isEmote = Emote.TryParse(emoji, out var emote); @@ -164,7 +128,7 @@ public async Task HandleNotificationAsync(MessageReceivedNotification notificati } } - public async Task HandleNotificationAsync(MessageUpdatedNotification notification, CancellationToken cancellationToken) + public async Task Handle(MessageUpdatedNotificationV3 notification, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -180,7 +144,7 @@ public async Task HandleNotificationAsync(MessageUpdatedNotification notificatio if (notification.NewMessage is not { Author: { IsBot: false }, Content: not null } newMessage) return; - var newEmoji = EmojiRegex.Matches(newMessage.Content); + var newEmoji = _emojiRegex.Matches(newMessage.Content); if (newEmoji.Count == 0) return; @@ -193,7 +157,7 @@ public async Task HandleNotificationAsync(MessageUpdatedNotification notificatio } } - public async Task HandleNotificationAsync(MessageDeletedNotification notification, CancellationToken cancellationToken) + public async Task Handle(MessageDeletedNotificationV3 notification, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -209,9 +173,9 @@ public async Task HandleNotificationAsync(MessageDeletedNotification notificatio private async Task LogMultipleMessageEmojiAsync(ITextChannel channel, IMessage message, string emoji, Emote emote, int count) { - using var transaction = await _emojiRepository.BeginMaintainTransactionAsync(); + using var transaction = await emojiRepository.BeginMaintainTransactionAsync(); - await _emojiRepository.CreateMultipleAsync(new EmojiCreationData() + await emojiRepository.CreateMultipleAsync(new EmojiCreationData() { GuildId = channel.GuildId, ChannelId = channel.Id, @@ -229,9 +193,9 @@ await _emojiRepository.CreateMultipleAsync(new EmojiCreationData() private async Task UnlogMessageContentEmojiAsync(ITextChannel channel, ulong messageId) { - using var transaction = await _emojiRepository.BeginMaintainTransactionAsync(); + using var transaction = await emojiRepository.BeginMaintainTransactionAsync(); - await _emojiRepository.DeleteAsync(new EmojiSearchCriteria() + await emojiRepository.DeleteAsync(new EmojiSearchCriteria() { GuildId = channel.GuildId, ChannelId = channel.Id, @@ -244,9 +208,9 @@ await _emojiRepository.DeleteAsync(new EmojiSearchCriteria() private async Task UnlogAllEmojiAsync(ITextChannel channel, ulong messageId) { - using var transaction = await _emojiRepository.BeginMaintainTransactionAsync(); + using var transaction = await emojiRepository.BeginMaintainTransactionAsync(); - await _emojiRepository.DeleteAsync(new EmojiSearchCriteria() + await emojiRepository.DeleteAsync(new EmojiSearchCriteria() { GuildId = channel.GuildId, ChannelId = channel.Id, diff --git a/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteEmbedHelper.cs b/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteEmbedHelper.cs index 2f3df42a0..c97238110 100644 --- a/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteEmbedHelper.cs +++ b/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteEmbedHelper.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Discord; using Humanizer.Bytes; +using Modix.Bot.Responders.AutoRemoveMessages; using Modix.Services.AutoRemoveMessage; using Modix.Services.Utilities; diff --git a/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteResponder.cs b/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteResponder.cs index 8c7c4dc51..be648771f 100644 --- a/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteResponder.cs +++ b/src/Modix.Bot/Responders/MessageQuotes/MessageQuoteResponder.cs @@ -21,7 +21,7 @@ public class MessageQuoteResponder( public async Task Handle(MessageUpdatedNotificationV3 notification, CancellationToken cancellationToken) { - var cachedMessage = await notification.Cached.GetOrDownloadAsync(); + var cachedMessage = await notification.OldMessage.GetOrDownloadAsync(); if (_pattern.IsMatch(cachedMessage.Content)) return; diff --git a/src/Modix.Bot/Responders/StarboardHandler.cs b/src/Modix.Bot/Responders/StarboardReactionResponder.cs similarity index 60% rename from src/Modix.Bot/Responders/StarboardHandler.cs rename to src/Modix.Bot/Responders/StarboardReactionResponder.cs index 22a19c39d..51965b670 100644 --- a/src/Modix.Bot/Responders/StarboardHandler.cs +++ b/src/Modix.Bot/Responders/StarboardReactionResponder.cs @@ -1,8 +1,9 @@ using System.Threading; using System.Threading.Tasks; using Discord; +using MediatR; +using Modix.Bot.Notifications; using Modix.Bot.Responders.MessageQuotes; -using Modix.Common.Messaging; using Modix.Data.Models.Core; using Modix.Services.Core; using Modix.Services.Starboard; @@ -10,31 +11,19 @@ namespace Modix.Bot.Responders { - public class StarboardHandler : - INotificationHandler, - INotificationHandler + public class StarboardReactionResponder(IStarboardService starboardService, IDesignatedChannelService designatedChannelService) + : INotificationHandler, INotificationHandler { - private readonly IStarboardService _starboardService; - private readonly IDesignatedChannelService _designatedChannelService; - - public StarboardHandler( - IStarboardService starboardService, - IDesignatedChannelService designatedChannelService) - { - _starboardService = starboardService; - _designatedChannelService = designatedChannelService; - } - - public Task HandleNotificationAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) + public Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken) => HandleReactionAsync(notification.Message, notification.Reaction); - public Task HandleNotificationAsync(ReactionRemovedNotification notification, CancellationToken cancellationToken) + public Task Handle(ReactionRemovedNotificationV3 notification, CancellationToken cancellationToken) => HandleReactionAsync(notification.Message, notification.Reaction); private async Task HandleReactionAsync(Cacheable cachedMessage, IReaction reaction) { var emote = reaction.Emote; - if (!_starboardService.IsStarEmote(emote)) + if (!starboardService.IsStarEmote(emote)) { return; } @@ -45,10 +34,10 @@ private async Task HandleReactionAsync(Cacheable cachedMess return; } - var isIgnoredFromStarboard = await _designatedChannelService + var isIgnoredFromStarboard = await designatedChannelService .ChannelHasDesignationAsync(channel.Guild.Id, channel.Id, DesignatedChannelType.IgnoredFromStarboard, default); - var starboardExists = await _designatedChannelService + var starboardExists = await designatedChannelService .AnyDesignatedChannelAsync(channel.GuildId, DesignatedChannelType.Starboard); if (isIgnoredFromStarboard || !starboardExists) @@ -56,26 +45,26 @@ private async Task HandleReactionAsync(Cacheable cachedMess return; } - var reactionCount = await _starboardService.GetReactionCount(message, emote); + var reactionCount = await starboardService.GetReactionCount(message, emote); - if (await _starboardService.ExistsOnStarboard(message)) + if (await starboardService.ExistsOnStarboard(message)) { - if (_starboardService.IsAboveReactionThreshold(reactionCount)) + if (starboardService.IsAboveReactionThreshold(reactionCount)) { - await _starboardService.ModifyEntry(channel.Guild, message, FormatContent(reactionCount), GetEmbedColor(reactionCount)); + await starboardService.ModifyEntry(channel.Guild, message, FormatContent(reactionCount), GetEmbedColor(reactionCount)); } else { - await _starboardService.RemoveFromStarboard(channel.Guild, message); + await starboardService.RemoveFromStarboard(channel.Guild, message); } } - else if (_starboardService.IsAboveReactionThreshold(reactionCount)) + else if (starboardService.IsAboveReactionThreshold(reactionCount)) { var embed = GetStarEmbed(message, GetEmbedColor(reactionCount)); if (embed is { }) { - await _starboardService.AddToStarboard(channel.Guild, message, FormatContent(reactionCount), embed); + await starboardService.AddToStarboard(channel.Guild, message, FormatContent(reactionCount), embed); } } } @@ -96,7 +85,7 @@ private Color GetEmbedColor(int reactionCount) private string FormatContent(int reactionCount) { - return $"**{reactionCount}** {_starboardService.GetStarEmote(reactionCount)}"; + return $"**{reactionCount}** {starboardService.GetStarEmote(reactionCount)}"; } private Embed GetStarEmbed(IUserMessage message, Color color) diff --git a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs b/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs deleted file mode 100644 index 7dc12eb4b..000000000 --- a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Discord; - -using Microsoft.Extensions.Caching.Memory; - -using Modix.Common.Messaging; - -namespace Modix.Services.AutoRemoveMessage -{ - public class AutoRemoveMessageHandler : - INotificationHandler, - INotificationHandler, - INotificationHandler - { - public AutoRemoveMessageHandler( - IMemoryCache cache, - AutoRemoveMessageService autoRemoveMessageService) - { - Cache = cache; - AutoRemoveMessageService = autoRemoveMessageService; - } - - public Task HandleNotificationAsync(RemovableMessageSentNotification notification, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return Task.CompletedTask; - - Cache.Set( - GetKey(notification.Message.Id), - new RemovableMessage() - { - Message = notification.Message, - Users = notification.Users, - }, - _messageCacheOptions); - - return Task.CompletedTask; - } - - public async Task HandleNotificationAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) - { - var key = GetKey(notification.Message.Id); - - if (cancellationToken.IsCancellationRequested - || notification.Reaction.Emote.Name != "❌" - || !Cache.TryGetValue(key, out RemovableMessage cachedMessage) - || !cachedMessage.Users.Any(user => user.Id == notification.Reaction.UserId)) - { - return; - } - - await cachedMessage.Message.DeleteAsync(); - - AutoRemoveMessageService.UnregisterRemovableMessage(cachedMessage.Message); - } - - public Task HandleNotificationAsync(RemovableMessageRemovedNotification notification, CancellationToken cancellationToken) - { - var key = GetKey(notification.Message.Id); - - if (cancellationToken.IsCancellationRequested - || !Cache.TryGetValue(key, out _)) - { - return Task.CompletedTask; - } - - Cache.Remove(key); - - return Task.CompletedTask; - } - - protected IMemoryCache Cache { get; } - - protected AutoRemoveMessageService AutoRemoveMessageService { get; } - - private static object GetKey(ulong messageId) - => new - { - MessageId = messageId, - Target = "RemovableMessage", - }; - - private static readonly MemoryCacheEntryOptions _messageCacheOptions = - new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(60)); - } -} diff --git a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageService.cs b/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageService.cs deleted file mode 100644 index 121bfff81..000000000 --- a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; -using Discord; -using Modix.Common.Messaging; - -namespace Modix.Services.AutoRemoveMessage; - -public class AutoRemoveMessageService(IMessageDispatcher messageDispatcher) -{ - private const string FOOTER_MESSAGE = "React with ❌ to remove this embed."; - - public Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, - Func> callback) - { - return RegisterRemovableMessageAsync([user], embed, callback); - } - - public async Task RegisterRemovableMessageAsync(IUser[] users, EmbedBuilder embed, - Func> callback) - { - if (callback == null) - throw new ArgumentNullException(nameof(callback)); - - if (embed.Footer?.Text == null) - { - embed.WithFooter(FOOTER_MESSAGE); - } - else if (!embed.Footer.Text.Contains(FOOTER_MESSAGE)) - { - embed.Footer.Text += $" | {FOOTER_MESSAGE}"; - } - - var msg = await callback.Invoke(embed); - messageDispatcher.Dispatch(new RemovableMessageSentNotification(msg, users)); - } - - public void UnregisterRemovableMessage(IMessage message) - => messageDispatcher.Dispatch(new RemovableMessageRemovedNotification(message)); -} diff --git a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs b/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs deleted file mode 100644 index a79b8f325..000000000 --- a/src/Modix.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Discord; - -using Microsoft.Extensions.DependencyInjection; - -using Modix.Common.Messaging; - -namespace Modix.Services.AutoRemoveMessage -{ - /// - /// Contains extension methods for configuring the auto remove message feature upon application startup. - /// - public static class AutoRemoveMessageSetup - { - /// - /// Adds the services and classes that make up the auto remove message service to a service collection. - /// - /// The to which the auto remove message services are to be added. - /// - public static IServiceCollection AddAutoRemoveMessage(this IServiceCollection services) - => services - .AddScoped() - .AddScoped, AutoRemoveMessageHandler>() - .AddScoped, AutoRemoveMessageHandler>() - .AddScoped, AutoRemoveMessageHandler>(); - } -} diff --git a/src/Modix.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs b/src/Modix.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs deleted file mode 100644 index 3e1cb24f6..000000000 --- a/src/Modix.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -using Discord; - -namespace Modix.Services.AutoRemoveMessage -{ - public class RemovableMessageRemovedNotification - { - public RemovableMessageRemovedNotification(IMessage message) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - } - - public IMessage Message { get; } - } -} diff --git a/src/Modix.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs b/src/Modix.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs deleted file mode 100644 index 95855801d..000000000 --- a/src/Modix.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -using Discord; - -namespace Modix.Services.AutoRemoveMessage -{ - public class RemovableMessageSentNotification - { - public RemovableMessageSentNotification(IMessage message, IUser[] users) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - Users = users ?? throw new ArgumentNullException(nameof(users)); - } - - public IMessage Message { get; } - - public IUser[] Users { get; } - } -} diff --git a/src/Modix.Services/CommandHelp/CommandErrorHandler.cs b/src/Modix.Services/CommandHelp/CommandErrorHandler.cs deleted file mode 100644 index 19bfbe43c..000000000 --- a/src/Modix.Services/CommandHelp/CommandErrorHandler.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; - -using Discord; -using Discord.WebSocket; - -using Microsoft.Extensions.Caching.Memory; - -using Modix.Common.Messaging; - -namespace Modix.Services.CommandHelp -{ - public class CommandErrorHandler : - INotificationHandler, - INotificationHandler - { - private const string AssociatedErrorsKey = nameof(CommandErrorHandler) + ".AssociatedErrors"; - private const string ErrorRepliesKey = nameof(CommandErrorHandler) + ".ErrorReplies"; - - //This relates user messages with errors - private ConcurrentDictionary AssociatedErrors => - _memoryCache.GetOrCreate(AssociatedErrorsKey, _ => new ConcurrentDictionary()); - - //This relates user messages to modix messages containing errors - private ConcurrentDictionary ErrorReplies => - _memoryCache.GetOrCreate(ErrorRepliesKey, _ => new ConcurrentDictionary()); - - private const string _emoji = "⚠"; - private readonly IEmote _emote = new Emoji(_emoji); - private readonly DiscordSocketClient _discordSocketClient; - private readonly IMemoryCache _memoryCache; - - public CommandErrorHandler(DiscordSocketClient discordSocketClient, IMemoryCache memoryCache) - { - _discordSocketClient = discordSocketClient; - _memoryCache = memoryCache; - } - - /// - /// Associates a user message with an error - /// - /// The message containing an errored command - /// The error that occurred - /// - public async Task AssociateErrorAsync(IUserMessage message, string error) - { - if (AssociatedErrors.TryAdd(message.Id, error)) - { - await message.AddReactionAsync(new Emoji(_emoji)); - } - } - - public Task HandleNotificationAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) - => ReactionAddedAsync(notification.Message, notification.Channel, notification.Reaction); - - public async Task ReactionAddedAsync(Cacheable cachedMessage, Cacheable cachedChannel, SocketReaction reaction) - { - //Don't trigger if the emoji is wrong, if the user is a bot, or if we've - //made an error message reply already - - if (reaction.User.IsSpecified && reaction.User.Value.IsBot) - { - return; - } - - if (reaction.Emote.Name != _emoji || ErrorReplies.ContainsKey(cachedMessage.Id)) - { - return; - } - - //If the message that was reacted to has an associated error, reply in the same channel - //with the error message then add that to the replies collection - if (AssociatedErrors.TryGetValue(cachedMessage.Id, out var value)) - { - - var channel = await cachedChannel.GetOrDownloadAsync(); - var msg = await channel.SendMessageAsync("", false, new EmbedBuilder() - { - Author = new EmbedAuthorBuilder - { - IconUrl = "https://raw.githubusercontent.com/twitter/twemoji/gh-pages/2/72x72/26a0.png", - Name = "That command had an error" - }, - Description = value, - Footer = new EmbedFooterBuilder { Text = "Remove your reaction to delete this message" } - }.Build()); - - if (ErrorReplies.TryAdd(cachedMessage.Id, msg.Id) == false) - { - await msg.DeleteAsync(); - } - } - } - - public Task HandleNotificationAsync(ReactionRemovedNotification notification, CancellationToken cancellationToken) - => ReactionRemovedAsync(notification.Message, notification.Channel, notification.Reaction); - - public async Task ReactionRemovedAsync(Cacheable cachedMessage, Cacheable cachedChannel, SocketReaction reaction) - { - //Bugfix for NRE? - if (reaction is null || reaction.User.Value is null) - { - return; - } - - //Don't trigger if the emoji is wrong, or if the user is bot - if (reaction.User.IsSpecified && reaction.User.Value.IsBot) - { - return; - } - - if (reaction.Emote.Name != _emoji) - { - return; - } - - //If there's an error reply when the reaction is removed, delete that reply, - //remove the cached error, remove it from the cached replies, and remove - //the reactions from the original message - if (ErrorReplies.TryGetValue(cachedMessage.Id, out var botReplyId) == false) { return; } - - var channel = await cachedChannel.GetOrDownloadAsync(); - await channel.DeleteMessageAsync(botReplyId); - - if - ( - AssociatedErrors.TryRemove(cachedMessage.Id, out _) && - ErrorReplies.TryRemove(cachedMessage.Id, out _) - ) - { - var originalMessage = await cachedMessage.GetOrDownloadAsync(); - - //If we know what user added the reaction, remove their and our reaction - //Otherwise just remove ours - - if (reaction.User.IsSpecified) - { - await originalMessage.RemoveReactionAsync(_emote, reaction.User.Value); - } - - await originalMessage.RemoveReactionAsync(_emote, _discordSocketClient.CurrentUser); - } - } - } -} diff --git a/src/Modix.Services/CommandHelp/CommandHelpSetup.cs b/src/Modix.Services/CommandHelp/CommandHelpSetup.cs index 74ae80c6e..cd57fe49f 100644 --- a/src/Modix.Services/CommandHelp/CommandHelpSetup.cs +++ b/src/Modix.Services/CommandHelp/CommandHelpSetup.cs @@ -16,9 +16,6 @@ public static class CommandHelpSetup /// public static IServiceCollection AddCommandHelp(this IServiceCollection services) => services - .AddSingleton() - .AddScoped() - .AddScoped>(x => x.GetService()) - .AddScoped>(x => x.GetService()); + .AddSingleton(); } } diff --git a/src/Modix.Services/Core/DiscordSocketListeningBehavior.cs b/src/Modix.Services/Core/DiscordSocketListeningBehavior.cs index 48affca57..03b140ab5 100644 --- a/src/Modix.Services/Core/DiscordSocketListeningBehavior.cs +++ b/src/Modix.Services/Core/DiscordSocketListeningBehavior.cs @@ -42,8 +42,6 @@ public Task StartAsync( DiscordSocketClient.MessageDeleted += OnMessageDeletedAsync; DiscordSocketClient.MessageReceived += OnMessageReceivedAsync; DiscordSocketClient.MessageUpdated += OnMessageUpdatedAsync; - DiscordSocketClient.ReactionAdded += OnReactionAddedAsync; - DiscordSocketClient.ReactionRemoved += OnReactionRemovedAsync; DiscordSocketClient.Ready += OnReadyAsync; DiscordSocketClient.RoleCreated += OnRoleCreatedAsync; DiscordSocketClient.RoleUpdated += OnRoleUpdatedAsync; @@ -70,8 +68,6 @@ public Task StopAsync( DiscordSocketClient.MessageDeleted -= OnMessageDeletedAsync; DiscordSocketClient.MessageReceived -= OnMessageReceivedAsync; DiscordSocketClient.MessageUpdated -= OnMessageUpdatedAsync; - DiscordSocketClient.ReactionAdded -= OnReactionAddedAsync; - DiscordSocketClient.ReactionRemoved -= OnReactionRemovedAsync; DiscordSocketClient.Ready -= OnReadyAsync; DiscordSocketClient.ThreadCreated -= OnThreadCreatedAsync; DiscordSocketClient.ThreadUpdated -= OnThreadUpdatedAsync; @@ -168,20 +164,6 @@ private Task OnMessageUpdatedAsync(Cacheable oldMessage, Socket return Task.CompletedTask; } - private Task OnReactionAddedAsync(Cacheable message, Cacheable channel, SocketReaction reaction) - { - MessageDispatcher.Dispatch(new ReactionAddedNotification(message, channel, reaction)); - - return Task.CompletedTask; - } - - private Task OnReactionRemovedAsync(Cacheable message, Cacheable channel, SocketReaction reaction) - { - MessageDispatcher.Dispatch(new ReactionRemovedNotification(message, channel, reaction)); - - return Task.CompletedTask; - } - private Task OnReadyAsync() { MessageDispatcher.Dispatch(new ReadyNotification()); diff --git a/src/Modix.Services/Core/Messages/ReactionAddedNotification.cs b/src/Modix.Services/Core/Messages/ReactionAddedNotification.cs deleted file mode 100644 index 68984f31c..000000000 --- a/src/Modix.Services/Core/Messages/ReactionAddedNotification.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -using Discord.WebSocket; - -namespace Discord -{ - /// - /// Describes an application-wide notification that occurs when is raised. - /// - public class ReactionAddedNotification - { - /// - /// Constructs a new object from the given data values. - /// - /// The value to use for . - /// The value to use for . - /// The value to use for . - /// Throws for and . - public ReactionAddedNotification(Cacheable message, Cacheable channel, SocketReaction reaction) - { - Message = message; - Channel = channel; - Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); - } - - /// - /// The message (if cached) to which a reaction was added. - /// - public Cacheable Message { get; } - - /// - /// The channel in which a reaction was added to a message. - /// - public Cacheable Channel { get; } - - /// - /// The reaction that was added to a message. - /// - public SocketReaction Reaction { get; } - } -} diff --git a/src/Modix.Services/Core/Messages/ReactionRemovedNotification.cs b/src/Modix.Services/Core/Messages/ReactionRemovedNotification.cs deleted file mode 100644 index a599b58f5..000000000 --- a/src/Modix.Services/Core/Messages/ReactionRemovedNotification.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -using Discord.WebSocket; - -namespace Discord -{ - /// - /// Describes an application-wide notification that occurs when is raised. - /// - public class ReactionRemovedNotification - { - /// - /// Constructs a new object from the given data values. - /// - /// The value to use for . - /// The value to use for . - /// The value to use for . - /// Throws for and . - public ReactionRemovedNotification(Cacheable message, Cacheable channel, SocketReaction reaction) - { - Message = message; - Channel = channel; - Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); - } - - /// - /// The message (if cached) to which a reaction was added. - /// - public Cacheable Message { get; } - - /// - /// The channel in which a reaction was added to a message. - /// - public Cacheable Channel { get; } - - /// - /// The reaction that was added to a message. - /// - public SocketReaction Reaction { get; } - } -} diff --git a/src/Modix.Services/EmojiStats/EmojiStatsSetup.cs b/src/Modix.Services/EmojiStats/EmojiStatsSetup.cs index 924d5e699..1dd5360f2 100644 --- a/src/Modix.Services/EmojiStats/EmojiStatsSetup.cs +++ b/src/Modix.Services/EmojiStats/EmojiStatsSetup.cs @@ -19,11 +19,6 @@ public static class EmojiStatsSetup /// public static IServiceCollection AddEmojiStats(this IServiceCollection services) => services - .AddScoped() - .AddScoped, EmojiUsageHandler>() - .AddScoped, EmojiUsageHandler>() - .AddScoped, EmojiUsageHandler>() - .AddScoped, EmojiUsageHandler>() - .AddScoped, EmojiUsageHandler>(); + .AddScoped(); } } diff --git a/src/Modix.Services/Utilities/EmojiUtilities.cs b/src/Modix.Services/Utilities/EmojiUtilities.cs index 5363626d2..5f48952c5 100644 --- a/src/Modix.Services/Utilities/EmojiUtilities.cs +++ b/src/Modix.Services/Utilities/EmojiUtilities.cs @@ -60,7 +60,7 @@ static bool IsVariantSelector(Rune rune) } // https://stackoverflow.com/a/48148218/1896401 - internal const string EmojiPattern = @"(?:\uD83D(?:[\uDC76\uDC66\uDC67](?:\uD83C[\uDFFB-\uDFFF])?|\uDC68(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC68\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)|\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)))))?|\uDC69(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])|\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])))))?|[\uDC74\uDC75](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6E(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD75(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC82\uDC77](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC78(?:\uD83C[\uDFFB-\uDFFF])?|\uDC73(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC72(?:\uD83C[\uDFFB-\uDFFF])?|\uDC71(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC70\uDC7C](?:\uD83C[\uDFFB-\uDFFF])?|[\uDE4D\uDE4E\uDE45\uDE46\uDC81\uDE4B\uDE47\uDC86\uDC87\uDEB6](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC83\uDD7A](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|\uDD74(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|\uDDE3\uFE0F?|[\uDEA3\uDEB4\uDEB5](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDCAA\uDC48\uDC49\uDC46\uDD95\uDC47\uDD96](?:\uD83C[\uDFFB-\uDFFF])?|\uDD90(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\uDC4C-\uDC4E\uDC4A\uDC4B\uDC4F\uDC50\uDE4C\uDE4F\uDC85\uDC42\uDC43](?:\uD83C[\uDFFB-\uDFFF])?|\uDC41(?:(?:\uFE0F(?:\u200D\uD83D\uDDE8\uFE0F?)?|\u200D\uD83D\uDDE8\uFE0F?))?|[\uDDE8\uDDEF\uDD73\uDD76\uDECD\uDC3F\uDD4A\uDD77\uDD78\uDDFA\uDEE3\uDEE4\uDEE2\uDEF3\uDEE5\uDEE9\uDEF0\uDECE\uDD70\uDD79\uDDBC\uDDA5\uDDA8\uDDB1\uDDB2\uDCFD\uDD6F\uDDDE\uDDF3\uDD8B\uDD8A\uDD8C\uDD8D\uDDC2\uDDD2\uDDD3\uDD87\uDDC3\uDDC4\uDDD1\uDDDD\uDEE0\uDDE1\uDEE1\uDDDC\uDECF\uDECB\uDD49]\uFE0F?|[\uDE00-\uDE06\uDE09-\uDE0B\uDE0E\uDE0D\uDE18\uDE17\uDE19\uDE1A\uDE42\uDE10\uDE11\uDE36\uDE44\uDE0F\uDE23\uDE25\uDE2E\uDE2F\uDE2A\uDE2B\uDE34\uDE0C\uDE1B-\uDE1D\uDE12-\uDE15\uDE43\uDE32\uDE41\uDE16\uDE1E\uDE1F\uDE24\uDE22\uDE2D\uDE26-\uDE29\uDE2C\uDE30\uDE31\uDE33\uDE35\uDE21\uDE20\uDE37\uDE07\uDE08\uDC7F\uDC79\uDC7A\uDC80\uDC7B\uDC7D\uDC7E\uDCA9\uDE3A\uDE38\uDE39\uDE3B-\uDE3D\uDE40\uDE3F\uDE3E\uDE48-\uDE4A\uDC64\uDC65\uDC6B-\uDC6D\uDC8F\uDC91\uDC6A\uDC63\uDC40\uDC45\uDC44\uDC8B\uDC98\uDC93-\uDC97\uDC99-\uDC9C\uDDA4\uDC9D-\uDC9F\uDC8C\uDCA4\uDCA2\uDCA3\uDCA5\uDCA6\uDCA8\uDCAB-\uDCAD\uDC53-\uDC62\uDC51\uDC52\uDCFF\uDC84\uDC8D\uDC8E\uDC35\uDC12\uDC36\uDC15\uDC29\uDC3A\uDC31\uDC08\uDC2F\uDC05\uDC06\uDC34\uDC0E\uDC2E\uDC02-\uDC04\uDC37\uDC16\uDC17\uDC3D\uDC0F\uDC11\uDC10\uDC2A\uDC2B\uDC18\uDC2D\uDC01\uDC00\uDC39\uDC30\uDC07\uDC3B\uDC28\uDC3C\uDC3E\uDC14\uDC13\uDC23-\uDC27\uDC38\uDC0A\uDC22\uDC0D\uDC32\uDC09\uDC33\uDC0B\uDC2C\uDC1F-\uDC21\uDC19\uDC1A\uDC0C\uDC1B-\uDC1E\uDC90\uDCAE\uDD2A\uDDFE\uDDFB\uDC92\uDDFC\uDDFD\uDD4C\uDD4D\uDD4B\uDC88\uDE82-\uDE8A\uDE9D\uDE9E\uDE8B-\uDE8E\uDE90-\uDE9C\uDEB2\uDEF4\uDEF9\uDEF5\uDE8F\uDEA8\uDEA5\uDEA6\uDED1\uDEA7\uDEF6\uDEA4\uDEA2\uDEEB\uDEEC\uDCBA\uDE81\uDE9F-\uDEA1\uDE80\uDEF8\uDD5B\uDD67\uDD50\uDD5C\uDD51\uDD5D\uDD52\uDD5E\uDD53\uDD5F\uDD54\uDD60\uDD55\uDD61\uDD56\uDD62\uDD57\uDD63\uDD58\uDD64\uDD59\uDD65\uDD5A\uDD66\uDD25\uDCA7\uDEF7\uDD2E\uDD07-\uDD0A\uDCE2\uDCE3\uDCEF\uDD14\uDD15\uDCFB\uDCF1\uDCF2\uDCDE-\uDCE0\uDD0B\uDD0C\uDCBB\uDCBD-\uDCC0\uDCFA\uDCF7-\uDCF9\uDCFC\uDD0D\uDD0E\uDCA1\uDD26\uDCD4-\uDCDA\uDCD3\uDCD2\uDCC3\uDCDC\uDCC4\uDCF0\uDCD1\uDD16\uDCB0\uDCB4-\uDCB8\uDCB3\uDCB9\uDCB1\uDCB2\uDCE7-\uDCE9\uDCE4-\uDCE6\uDCEB\uDCEA\uDCEC-\uDCEE\uDCDD\uDCBC\uDCC1\uDCC2\uDCC5-\uDCD0\uDD12\uDD13\uDD0F-\uDD11\uDD28\uDD2B\uDD27\uDD29\uDD17\uDD2C\uDD2D\uDCE1\uDC89\uDC8A\uDEAA\uDEBD\uDEBF\uDEC1\uDED2\uDEAC\uDDFF\uDEAE\uDEB0\uDEB9-\uDEBC\uDEBE\uDEC2-\uDEC5\uDEB8\uDEAB\uDEB3\uDEAD\uDEAF\uDEB1\uDEB7\uDCF5\uDD1E\uDD03\uDD04\uDD19-\uDD1D\uDED0\uDD4E\uDD2F\uDD00-\uDD02\uDD3C\uDD3D\uDD05\uDD06\uDCF6\uDCF3\uDCF4\uDD31\uDCDB\uDD30\uDD1F\uDCAF\uDD20-\uDD24\uDD36-\uDD3B\uDCA0\uDD18\uDD32-\uDD35\uDEA9])|\uD83E(?:[\uDDD2\uDDD1\uDDD3](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDB8\uDDB9](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD34\uDDD5\uDDD4\uDD35\uDD30\uDD31\uDD36](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDD9-\uDDDD](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|[\uDDDE\uDDDF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD26\uDD37](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDDD6-\uDDD8](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|\uDD38(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD3C(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDD3D\uDD3E\uDD39](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDD33\uDDB5\uDDB6\uDD1E\uDD18\uDD19\uDD1B\uDD1C\uDD1A\uDD1F\uDD32](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD23\uDD70\uDD17\uDD29\uDD14\uDD28\uDD10\uDD24\uDD11\uDD2F\uDD75\uDD76\uDD2A\uDD2C\uDD12\uDD15\uDD22\uDD2E\uDD27\uDD20\uDD21\uDD73\uDD74\uDD7A\uDD25\uDD2B\uDD2D\uDDD0\uDD13\uDD16\uDD3A\uDD1D\uDDB0-\uDDB3\uDDE0\uDDB4\uDDB7\uDDE1\uDD7D\uDD7C\uDDE3-\uDDE6\uDD7E\uDD7F\uDDE2\uDD8D\uDD8A\uDD9D\uDD81\uDD84\uDD93\uDD8C\uDD99\uDD92\uDD8F\uDD9B\uDD94\uDD87\uDD98\uDDA1\uDD83\uDD85\uDD86\uDDA2\uDD89\uDD9A\uDD9C\uDD8E\uDD95\uDD96\uDD88\uDD80\uDD9E\uDD90\uDD91\uDD8B\uDD97\uDD82\uDD9F\uDDA0\uDD40\uDD6D\uDD5D\uDD65\uDD51\uDD54\uDD55\uDD52\uDD6C\uDD66\uDD5C\uDD50\uDD56\uDD68\uDD6F\uDD5E\uDDC0\uDD69\uDD53\uDD6A\uDD59\uDD5A\uDD58\uDD63\uDD57\uDDC2\uDD6B\uDD6E\uDD5F-\uDD61\uDDC1\uDD67\uDD5B\uDD42\uDD43\uDD64\uDD62\uDD44\uDDED\uDDF1\uDDF3\uDDE8\uDDE7\uDD47-\uDD49\uDD4E\uDD4F\uDD4D\uDD4A\uDD4B\uDD45\uDD4C\uDDFF\uDDE9\uDDF8\uDD41\uDDEE\uDDFE\uDDF0\uDDF2\uDDEA-\uDDEC\uDDEF\uDDF4-\uDDF7\uDDF9-\uDDFD])|[\u263A\u2639\u2620]\uFE0F?|\uD83C(?:\uDF85(?:\uD83C[\uDFFB-\uDFFF])?|\uDFC3(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC7\uDFC2](?:\uD83C[\uDFFB-\uDFFF])?|\uDFCC(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC4\uDFCA](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDFCB(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFCE\uDFCD\uDFF5\uDF36\uDF7D\uDFD4-\uDFD6\uDFDC-\uDFDF\uDFDB\uDFD7\uDFD8\uDFDA\uDFD9\uDF21\uDF24-\uDF2C\uDF97\uDF9F\uDF96\uDF99-\uDF9B\uDF9E\uDFF7\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37]\uFE0F?|\uDFF4(?:(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F|\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F|\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F)))?|\uDFF3(?:(?:\uFE0F(?:\u200D\uD83C\uDF08)?|\u200D\uD83C\uDF08))?|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|[\uDFFB-\uDFFF\uDF92\uDFA9\uDF93\uDF38-\uDF3C\uDF37\uDF31-\uDF35\uDF3E-\uDF43\uDF47-\uDF53\uDF45\uDF46\uDF3D\uDF44\uDF30\uDF5E\uDF56\uDF57\uDF54\uDF5F\uDF55\uDF2D-\uDF2F\uDF73\uDF72\uDF7F\uDF71\uDF58-\uDF5D\uDF60\uDF62-\uDF65\uDF61\uDF66-\uDF6A\uDF82\uDF70\uDF6B-\uDF6F\uDF7C\uDF75\uDF76\uDF7E\uDF77-\uDF7B\uDF74\uDFFA\uDF0D-\uDF10\uDF0B\uDFE0-\uDFE6\uDFE8-\uDFED\uDFEF\uDFF0\uDF01\uDF03-\uDF07\uDF09\uDF0C\uDFA0-\uDFA2\uDFAA\uDF11-\uDF20\uDF00\uDF08\uDF02\uDF0A\uDF83\uDF84\uDF86-\uDF8B\uDF8D-\uDF91\uDF80\uDF81\uDFAB\uDFC6\uDFC5\uDFC0\uDFD0\uDFC8\uDFC9\uDFBE\uDFB3\uDFCF\uDFD1-\uDFD3\uDFF8\uDFA3\uDFBD\uDFBF\uDFAF\uDFB1\uDFAE\uDFB0\uDFB2\uDCCF\uDC04\uDFB4\uDFAD\uDFA8\uDFBC\uDFB5\uDFB6\uDFA4\uDFA7\uDFB7-\uDFBB\uDFA5\uDFAC\uDFEE\uDFF9\uDFE7\uDFA6\uDD8E\uDD91-\uDD9A\uDE01\uDE36\uDE2F\uDE50\uDE39\uDE1A\uDE32\uDE51\uDE38\uDE34\uDE33\uDE3A\uDE35\uDFC1\uDF8C])|\u26F7\uFE0F?|\u26F9(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\u261D\u270C](?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u270B\u270A](?:\uD83C[\uDFFB-\uDFFF])?|\u270D(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u2764\u2763\u26D1\u2618\u26F0\u26E9\u2668\u26F4\u2708\u23F1\u23F2\u2600\u2601\u26C8\u2602\u26F1\u2744\u2603\u2604\u26F8\u2660\u2665\u2666\u2663\u260E\u2328\u2709\u270F\u2712\u2702\u26CF\u2692\u2694\u2699\u2696\u26D3\u2697\u26B0\u26B1\u26A0\u2622\u2623\u2B06\u2197\u27A1\u2198\u2B07\u2199\u2B05\u2196\u2195\u2194\u21A9\u21AA\u2934\u2935\u269B\u267E\u2721\u2638\u262F\u271D\u2626\u262A\u262E\u25B6\u23ED\u23EF\u25C0\u23EE\u23F8-\u23FA\u23CF\u2640\u2642\u2695\u267B\u269C\u2611\u2714\u2716\u303D\u2733\u2734\u2747\u203C\u2049\u3030\u00A9\u00AE\u2122]\uFE0F?|[\u0023\u002A\u0030-\u0039](?:\uFE0F\u20E3|\u20E3)|[\u2139\u24C2\u3297\u3299\u25AA\u25AB\u25FB\u25FC]\uFE0F?|[\u2615\u26EA\u26F2\u26FA\u26FD\u2693\u26F5\u231B\u23F3\u231A\u23F0\u2B50\u26C5\u2614\u26A1\u26C4\u2728\u26BD\u26BE\u26F3\u267F\u26D4\u2648-\u2653\u26CE\u23E9-\u23EC\u2B55\u2705\u274C\u274E\u2795-\u2797\u27B0\u27BF\u2753-\u2755\u2757\u25FD\u25FE\u2B1B\u2B1C\u26AA\u26AB])"; + public const string EmojiPattern = @"(?:\uD83D(?:[\uDC76\uDC66\uDC67](?:\uD83C[\uDFFB-\uDFFF])?|\uDC68(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC68\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)|\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)))))?|\uDC69(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])|\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])))))?|[\uDC74\uDC75](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6E(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD75(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC82\uDC77](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC78(?:\uD83C[\uDFFB-\uDFFF])?|\uDC73(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC72(?:\uD83C[\uDFFB-\uDFFF])?|\uDC71(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC70\uDC7C](?:\uD83C[\uDFFB-\uDFFF])?|[\uDE4D\uDE4E\uDE45\uDE46\uDC81\uDE4B\uDE47\uDC86\uDC87\uDEB6](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC83\uDD7A](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|\uDD74(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|\uDDE3\uFE0F?|[\uDEA3\uDEB4\uDEB5](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDCAA\uDC48\uDC49\uDC46\uDD95\uDC47\uDD96](?:\uD83C[\uDFFB-\uDFFF])?|\uDD90(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\uDC4C-\uDC4E\uDC4A\uDC4B\uDC4F\uDC50\uDE4C\uDE4F\uDC85\uDC42\uDC43](?:\uD83C[\uDFFB-\uDFFF])?|\uDC41(?:(?:\uFE0F(?:\u200D\uD83D\uDDE8\uFE0F?)?|\u200D\uD83D\uDDE8\uFE0F?))?|[\uDDE8\uDDEF\uDD73\uDD76\uDECD\uDC3F\uDD4A\uDD77\uDD78\uDDFA\uDEE3\uDEE4\uDEE2\uDEF3\uDEE5\uDEE9\uDEF0\uDECE\uDD70\uDD79\uDDBC\uDDA5\uDDA8\uDDB1\uDDB2\uDCFD\uDD6F\uDDDE\uDDF3\uDD8B\uDD8A\uDD8C\uDD8D\uDDC2\uDDD2\uDDD3\uDD87\uDDC3\uDDC4\uDDD1\uDDDD\uDEE0\uDDE1\uDEE1\uDDDC\uDECF\uDECB\uDD49]\uFE0F?|[\uDE00-\uDE06\uDE09-\uDE0B\uDE0E\uDE0D\uDE18\uDE17\uDE19\uDE1A\uDE42\uDE10\uDE11\uDE36\uDE44\uDE0F\uDE23\uDE25\uDE2E\uDE2F\uDE2A\uDE2B\uDE34\uDE0C\uDE1B-\uDE1D\uDE12-\uDE15\uDE43\uDE32\uDE41\uDE16\uDE1E\uDE1F\uDE24\uDE22\uDE2D\uDE26-\uDE29\uDE2C\uDE30\uDE31\uDE33\uDE35\uDE21\uDE20\uDE37\uDE07\uDE08\uDC7F\uDC79\uDC7A\uDC80\uDC7B\uDC7D\uDC7E\uDCA9\uDE3A\uDE38\uDE39\uDE3B-\uDE3D\uDE40\uDE3F\uDE3E\uDE48-\uDE4A\uDC64\uDC65\uDC6B-\uDC6D\uDC8F\uDC91\uDC6A\uDC63\uDC40\uDC45\uDC44\uDC8B\uDC98\uDC93-\uDC97\uDC99-\uDC9C\uDDA4\uDC9D-\uDC9F\uDC8C\uDCA4\uDCA2\uDCA3\uDCA5\uDCA6\uDCA8\uDCAB-\uDCAD\uDC53-\uDC62\uDC51\uDC52\uDCFF\uDC84\uDC8D\uDC8E\uDC35\uDC12\uDC36\uDC15\uDC29\uDC3A\uDC31\uDC08\uDC2F\uDC05\uDC06\uDC34\uDC0E\uDC2E\uDC02-\uDC04\uDC37\uDC16\uDC17\uDC3D\uDC0F\uDC11\uDC10\uDC2A\uDC2B\uDC18\uDC2D\uDC01\uDC00\uDC39\uDC30\uDC07\uDC3B\uDC28\uDC3C\uDC3E\uDC14\uDC13\uDC23-\uDC27\uDC38\uDC0A\uDC22\uDC0D\uDC32\uDC09\uDC33\uDC0B\uDC2C\uDC1F-\uDC21\uDC19\uDC1A\uDC0C\uDC1B-\uDC1E\uDC90\uDCAE\uDD2A\uDDFE\uDDFB\uDC92\uDDFC\uDDFD\uDD4C\uDD4D\uDD4B\uDC88\uDE82-\uDE8A\uDE9D\uDE9E\uDE8B-\uDE8E\uDE90-\uDE9C\uDEB2\uDEF4\uDEF9\uDEF5\uDE8F\uDEA8\uDEA5\uDEA6\uDED1\uDEA7\uDEF6\uDEA4\uDEA2\uDEEB\uDEEC\uDCBA\uDE81\uDE9F-\uDEA1\uDE80\uDEF8\uDD5B\uDD67\uDD50\uDD5C\uDD51\uDD5D\uDD52\uDD5E\uDD53\uDD5F\uDD54\uDD60\uDD55\uDD61\uDD56\uDD62\uDD57\uDD63\uDD58\uDD64\uDD59\uDD65\uDD5A\uDD66\uDD25\uDCA7\uDEF7\uDD2E\uDD07-\uDD0A\uDCE2\uDCE3\uDCEF\uDD14\uDD15\uDCFB\uDCF1\uDCF2\uDCDE-\uDCE0\uDD0B\uDD0C\uDCBB\uDCBD-\uDCC0\uDCFA\uDCF7-\uDCF9\uDCFC\uDD0D\uDD0E\uDCA1\uDD26\uDCD4-\uDCDA\uDCD3\uDCD2\uDCC3\uDCDC\uDCC4\uDCF0\uDCD1\uDD16\uDCB0\uDCB4-\uDCB8\uDCB3\uDCB9\uDCB1\uDCB2\uDCE7-\uDCE9\uDCE4-\uDCE6\uDCEB\uDCEA\uDCEC-\uDCEE\uDCDD\uDCBC\uDCC1\uDCC2\uDCC5-\uDCD0\uDD12\uDD13\uDD0F-\uDD11\uDD28\uDD2B\uDD27\uDD29\uDD17\uDD2C\uDD2D\uDCE1\uDC89\uDC8A\uDEAA\uDEBD\uDEBF\uDEC1\uDED2\uDEAC\uDDFF\uDEAE\uDEB0\uDEB9-\uDEBC\uDEBE\uDEC2-\uDEC5\uDEB8\uDEAB\uDEB3\uDEAD\uDEAF\uDEB1\uDEB7\uDCF5\uDD1E\uDD03\uDD04\uDD19-\uDD1D\uDED0\uDD4E\uDD2F\uDD00-\uDD02\uDD3C\uDD3D\uDD05\uDD06\uDCF6\uDCF3\uDCF4\uDD31\uDCDB\uDD30\uDD1F\uDCAF\uDD20-\uDD24\uDD36-\uDD3B\uDCA0\uDD18\uDD32-\uDD35\uDEA9])|\uD83E(?:[\uDDD2\uDDD1\uDDD3](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDB8\uDDB9](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD34\uDDD5\uDDD4\uDD35\uDD30\uDD31\uDD36](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDD9-\uDDDD](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|[\uDDDE\uDDDF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD26\uDD37](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDDD6-\uDDD8](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|\uDD38(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD3C(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDD3D\uDD3E\uDD39](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDD33\uDDB5\uDDB6\uDD1E\uDD18\uDD19\uDD1B\uDD1C\uDD1A\uDD1F\uDD32](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD23\uDD70\uDD17\uDD29\uDD14\uDD28\uDD10\uDD24\uDD11\uDD2F\uDD75\uDD76\uDD2A\uDD2C\uDD12\uDD15\uDD22\uDD2E\uDD27\uDD20\uDD21\uDD73\uDD74\uDD7A\uDD25\uDD2B\uDD2D\uDDD0\uDD13\uDD16\uDD3A\uDD1D\uDDB0-\uDDB3\uDDE0\uDDB4\uDDB7\uDDE1\uDD7D\uDD7C\uDDE3-\uDDE6\uDD7E\uDD7F\uDDE2\uDD8D\uDD8A\uDD9D\uDD81\uDD84\uDD93\uDD8C\uDD99\uDD92\uDD8F\uDD9B\uDD94\uDD87\uDD98\uDDA1\uDD83\uDD85\uDD86\uDDA2\uDD89\uDD9A\uDD9C\uDD8E\uDD95\uDD96\uDD88\uDD80\uDD9E\uDD90\uDD91\uDD8B\uDD97\uDD82\uDD9F\uDDA0\uDD40\uDD6D\uDD5D\uDD65\uDD51\uDD54\uDD55\uDD52\uDD6C\uDD66\uDD5C\uDD50\uDD56\uDD68\uDD6F\uDD5E\uDDC0\uDD69\uDD53\uDD6A\uDD59\uDD5A\uDD58\uDD63\uDD57\uDDC2\uDD6B\uDD6E\uDD5F-\uDD61\uDDC1\uDD67\uDD5B\uDD42\uDD43\uDD64\uDD62\uDD44\uDDED\uDDF1\uDDF3\uDDE8\uDDE7\uDD47-\uDD49\uDD4E\uDD4F\uDD4D\uDD4A\uDD4B\uDD45\uDD4C\uDDFF\uDDE9\uDDF8\uDD41\uDDEE\uDDFE\uDDF0\uDDF2\uDDEA-\uDDEC\uDDEF\uDDF4-\uDDF7\uDDF9-\uDDFD])|[\u263A\u2639\u2620]\uFE0F?|\uD83C(?:\uDF85(?:\uD83C[\uDFFB-\uDFFF])?|\uDFC3(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC7\uDFC2](?:\uD83C[\uDFFB-\uDFFF])?|\uDFCC(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC4\uDFCA](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDFCB(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFCE\uDFCD\uDFF5\uDF36\uDF7D\uDFD4-\uDFD6\uDFDC-\uDFDF\uDFDB\uDFD7\uDFD8\uDFDA\uDFD9\uDF21\uDF24-\uDF2C\uDF97\uDF9F\uDF96\uDF99-\uDF9B\uDF9E\uDFF7\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37]\uFE0F?|\uDFF4(?:(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F|\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F|\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F)))?|\uDFF3(?:(?:\uFE0F(?:\u200D\uD83C\uDF08)?|\u200D\uD83C\uDF08))?|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|[\uDFFB-\uDFFF\uDF92\uDFA9\uDF93\uDF38-\uDF3C\uDF37\uDF31-\uDF35\uDF3E-\uDF43\uDF47-\uDF53\uDF45\uDF46\uDF3D\uDF44\uDF30\uDF5E\uDF56\uDF57\uDF54\uDF5F\uDF55\uDF2D-\uDF2F\uDF73\uDF72\uDF7F\uDF71\uDF58-\uDF5D\uDF60\uDF62-\uDF65\uDF61\uDF66-\uDF6A\uDF82\uDF70\uDF6B-\uDF6F\uDF7C\uDF75\uDF76\uDF7E\uDF77-\uDF7B\uDF74\uDFFA\uDF0D-\uDF10\uDF0B\uDFE0-\uDFE6\uDFE8-\uDFED\uDFEF\uDFF0\uDF01\uDF03-\uDF07\uDF09\uDF0C\uDFA0-\uDFA2\uDFAA\uDF11-\uDF20\uDF00\uDF08\uDF02\uDF0A\uDF83\uDF84\uDF86-\uDF8B\uDF8D-\uDF91\uDF80\uDF81\uDFAB\uDFC6\uDFC5\uDFC0\uDFD0\uDFC8\uDFC9\uDFBE\uDFB3\uDFCF\uDFD1-\uDFD3\uDFF8\uDFA3\uDFBD\uDFBF\uDFAF\uDFB1\uDFAE\uDFB0\uDFB2\uDCCF\uDC04\uDFB4\uDFAD\uDFA8\uDFBC\uDFB5\uDFB6\uDFA4\uDFA7\uDFB7-\uDFBB\uDFA5\uDFAC\uDFEE\uDFF9\uDFE7\uDFA6\uDD8E\uDD91-\uDD9A\uDE01\uDE36\uDE2F\uDE50\uDE39\uDE1A\uDE32\uDE51\uDE38\uDE34\uDE33\uDE3A\uDE35\uDFC1\uDF8C])|\u26F7\uFE0F?|\u26F9(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\u261D\u270C](?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u270B\u270A](?:\uD83C[\uDFFB-\uDFFF])?|\u270D(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u2764\u2763\u26D1\u2618\u26F0\u26E9\u2668\u26F4\u2708\u23F1\u23F2\u2600\u2601\u26C8\u2602\u26F1\u2744\u2603\u2604\u26F8\u2660\u2665\u2666\u2663\u260E\u2328\u2709\u270F\u2712\u2702\u26CF\u2692\u2694\u2699\u2696\u26D3\u2697\u26B0\u26B1\u26A0\u2622\u2623\u2B06\u2197\u27A1\u2198\u2B07\u2199\u2B05\u2196\u2195\u2194\u21A9\u21AA\u2934\u2935\u269B\u267E\u2721\u2638\u262F\u271D\u2626\u262A\u262E\u25B6\u23ED\u23EF\u25C0\u23EE\u23F8-\u23FA\u23CF\u2640\u2642\u2695\u267B\u269C\u2611\u2714\u2716\u303D\u2733\u2734\u2747\u203C\u2049\u3030\u00A9\u00AE\u2122]\uFE0F?|[\u0023\u002A\u0030-\u0039](?:\uFE0F\u20E3|\u20E3)|[\u2139\u24C2\u3297\u3299\u25AA\u25AB\u25FB\u25FC]\uFE0F?|[\u2615\u26EA\u26F2\u26FA\u26FD\u2693\u26F5\u231B\u23F3\u231A\u23F0\u2B50\u26C5\u2614\u26A1\u26C4\u2728\u26BD\u26BE\u26F3\u267F\u26D4\u2648-\u2653\u26CE\u23E9-\u23EC\u2B55\u2705\u274C\u274E\u2795-\u2797\u27B0\u27BF\u2753-\u2755\u2757\u25FD\u25FE\u2B1B\u2B1C\u26AA\u26AB])"; private static readonly Regex _builtInEmojiRegex = new(EmojiPattern, RegexOptions.Compiled); } diff --git a/src/Modix/Extensions/ServiceCollectionExtensions.cs b/src/Modix/Extensions/ServiceCollectionExtensions.cs index ca583f4b6..a2d8ba6a8 100644 --- a/src/Modix/Extensions/ServiceCollectionExtensions.cs +++ b/src/Modix/Extensions/ServiceCollectionExtensions.cs @@ -14,6 +14,8 @@ using Modix.Bot; using Modix.Bot.Behaviors; using Modix.Bot.Responders; +using Modix.Bot.Responders.AutoRemoveMessages; +using Modix.Bot.Responders.CommandErrors; using Modix.Bot.Responders.MessageQuotes; using Modix.Common; using Modix.Common.Messaging; @@ -123,8 +125,7 @@ public static IServiceCollection AddModix( service.AddTypeReader(new UriTypeReader()); return service; - }) - .AddScoped, CommandListeningBehavior>(); + }); services.AddSingleton(provider => { @@ -157,15 +158,14 @@ public static IServiceCollection AddModix( .AddGuildStats() .AddModixTags() .AddStarboard() - .AddAutoRemoveMessage() .AddEmojiStats() .AddImages(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ModixBot).Assembly)); + services.AddScoped(); services.AddScoped(); - services.AddScoped, StarboardHandler>(); - services.AddScoped, StarboardHandler>(); services.AddScoped(); + services.AddScoped(); services.AddMemoryCache();