diff --git a/.editorconfig b/.editorconfig index 7a2388b..2b2c92f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,3 @@ - [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] indent_style = space indent_size = 4 @@ -21,7 +20,7 @@ dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none @@ -91,4 +90,4 @@ resharper_suspicious_type_conversion_global_highlighting = error resharper_use_null_propagation_when_possible_highlighting = suggestion resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning -resharper_web_config_wrong_module_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/CensorBehavior.cs b/Zhongli.Bot/Behaviors/CensorBehavior.cs index 44995b0..c77f7f9 100644 --- a/Zhongli.Bot/Behaviors/CensorBehavior.cs +++ b/Zhongli.Bot/Behaviors/CensorBehavior.cs @@ -12,56 +12,55 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class CensorBehavior : + INotificationHandler, + INotificationHandler { - public class CensorBehavior : - INotificationHandler, - INotificationHandler - { - private readonly ModerationService _moderation; - private readonly ZhongliContext _db; + private readonly ModerationService _moderation; + private readonly ZhongliContext _db; - public CensorBehavior(ModerationService moderation, ZhongliContext db) - { - _moderation = moderation; - _db = db; - } + public CensorBehavior(ModerationService moderation, ZhongliContext db) + { + _moderation = moderation; + _db = db; + } - public Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) - => ProcessMessage(notification.Message, cancellationToken); + public Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) + => ProcessMessage(notification.Message, cancellationToken); - public Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) - => ProcessMessage(notification.NewMessage, cancellationToken); + public Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) + => ProcessMessage(notification.NewMessage, cancellationToken); - private async Task ProcessMessage(SocketMessage message, CancellationToken cancellationToken = default) - { - var author = message.Author; - if (author.IsBot || author.IsWebhook - || author is not IGuildUser user - || message.Channel is not ITextChannel channel) - return; + private async Task ProcessMessage(SocketMessage message, CancellationToken cancellationToken = default) + { + var author = message.Author; + if (author.IsBot || author.IsWebhook + || author is not IGuildUser user + || message.Channel is not ITextChannel channel) + return; - var guild = channel.Guild; - var guildEntity = await _db.Guilds.FindByIdAsync(guild.Id, cancellationToken); - if (guildEntity is null || cancellationToken.IsCancellationRequested) - return; + var guild = channel.Guild; + var guildEntity = await _db.Guilds.FindByIdAsync(guild.Id, cancellationToken); + if (guildEntity is null || cancellationToken.IsCancellationRequested) + return; - if (guildEntity.ModerationRules.CensorExclusions.Any(e => e.Judge(channel, user))) - return; + if (guildEntity.ModerationRules.CensorExclusions.Any(e => e.Judge(channel, user))) + return; - await _db.Users.TrackUserAsync(user, cancellationToken); - var currentUser = await guild.GetCurrentUserAsync(); + await _db.Users.TrackUserAsync(user, cancellationToken); + var currentUser = await guild.GetCurrentUserAsync(); - foreach (var censor in guildEntity.ModerationRules.Triggers.OfType() - .Where(c => c.IsActive) - .Where(c => c.Exclusions.All(e => !e.Judge(channel, user))) - .Where(c => c.Regex().IsMatch(message.Content))) - { - var details = new ReprimandDetails(user, currentUser, "[Censor Triggered]", censor); - var length = guildEntity.ModerationRules.CensorTimeRange; + foreach (var censor in guildEntity.ModerationRules.Triggers.OfType() + .Where(c => c.IsActive) + .Where(c => c.Exclusions.All(e => !e.Judge(channel, user))) + .Where(c => c.Regex().IsMatch(message.Content))) + { + var details = new ReprimandDetails(user, currentUser, "[Censor Triggered]", censor); + var length = guildEntity.ModerationRules.CensorTimeRange; - await _moderation.CensorAsync(message, length, details, cancellationToken); - } + await _moderation.CensorAsync(message, length, details, cancellationToken); } } } \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/GuildConfigureBehavior.cs b/Zhongli.Bot/Behaviors/GuildConfigureBehavior.cs index 4160e98..51bf188 100644 --- a/Zhongli.Bot/Behaviors/GuildConfigureBehavior.cs +++ b/Zhongli.Bot/Behaviors/GuildConfigureBehavior.cs @@ -7,33 +7,32 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class GuildConfigureBehavior : + INotificationHandler, + INotificationHandler { - public class GuildConfigureBehavior : - INotificationHandler, - INotificationHandler - { - private readonly AuthorizationService _auth; - private readonly ZhongliContext _db; + private readonly AuthorizationService _auth; + private readonly ZhongliContext _db; - public GuildConfigureBehavior(AuthorizationService auth, ZhongliContext db) - { - _auth = auth; - _db = db; - } + public GuildConfigureBehavior(AuthorizationService auth, ZhongliContext db) + { + _auth = auth; + _db = db; + } - public Task Handle(GuildAvailableNotification notification, CancellationToken cancellationToken) - => ConfigureGuildAsync(notification.Guild, cancellationToken); + public Task Handle(GuildAvailableNotification notification, CancellationToken cancellationToken) + => ConfigureGuildAsync(notification.Guild, cancellationToken); - public Task Handle(JoinedGuildNotification notification, CancellationToken cancellationToken) - => ConfigureGuildAsync(notification.Guild, cancellationToken); + public Task Handle(JoinedGuildNotification notification, CancellationToken cancellationToken) + => ConfigureGuildAsync(notification.Guild, cancellationToken); - private async Task ConfigureGuildAsync(IGuild guild, CancellationToken cancellationToken) - { - await _db.Guilds.TrackGuildAsync(guild, cancellationToken); - await _db.SaveChangesAsync(cancellationToken); + private async Task ConfigureGuildAsync(IGuild guild, CancellationToken cancellationToken) + { + await _db.Guilds.TrackGuildAsync(guild, cancellationToken); + await _db.SaveChangesAsync(cancellationToken); - await _auth.AutoConfigureGuild(guild, cancellationToken); - } + await _auth.AutoConfigureGuild(guild, cancellationToken); } } \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/MessageLoggingBehavior.cs b/Zhongli.Bot/Behaviors/MessageLoggingBehavior.cs index ab76aef..b66855f 100644 --- a/Zhongli.Bot/Behaviors/MessageLoggingBehavior.cs +++ b/Zhongli.Bot/Behaviors/MessageLoggingBehavior.cs @@ -5,33 +5,32 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Logging; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class MessageLoggingBehavior : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - public class MessageLoggingBehavior : - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler - { - private readonly LoggingService _logging; - private readonly ZhongliContext _db; + private readonly LoggingService _logging; + private readonly ZhongliContext _db; - public MessageLoggingBehavior(LoggingService logging, ZhongliContext db) - { - _logging = logging; - _db = db; - } + public MessageLoggingBehavior(LoggingService logging, ZhongliContext db) + { + _logging = logging; + _db = db; + } - public Task Handle(MessageDeletedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); + public Task Handle(MessageDeletedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); - public Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); + public Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); - public Task Handle(MessagesBulkDeletedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); + public Task Handle(MessagesBulkDeletedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); - public Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); - } + public Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); } \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/ReactionLoggingBehavior.cs b/Zhongli.Bot/Behaviors/ReactionLoggingBehavior.cs index 9957f48..6d90ae1 100644 --- a/Zhongli.Bot/Behaviors/ReactionLoggingBehavior.cs +++ b/Zhongli.Bot/Behaviors/ReactionLoggingBehavior.cs @@ -4,20 +4,19 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Logging; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class ReactionLoggingBehavior : + INotificationHandler, + INotificationHandler { - public class ReactionLoggingBehavior : - INotificationHandler, - INotificationHandler - { - private readonly LoggingService _logging; + private readonly LoggingService _logging; - public ReactionLoggingBehavior(LoggingService logging) { _logging = logging; } + public ReactionLoggingBehavior(LoggingService logging) { _logging = logging; } - public Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); + public Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); - public Task Handle(ReactionRemovedNotification notification, CancellationToken cancellationToken) - => _logging.LogAsync(notification, cancellationToken); - } + public Task Handle(ReactionRemovedNotification notification, CancellationToken cancellationToken) + => _logging.LogAsync(notification, cancellationToken); } \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/UserTrackingBehavior.cs b/Zhongli.Bot/Behaviors/UserTrackingBehavior.cs index c295089..c5df15d 100644 --- a/Zhongli.Bot/Behaviors/UserTrackingBehavior.cs +++ b/Zhongli.Bot/Behaviors/UserTrackingBehavior.cs @@ -6,36 +6,35 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class UserTrackingBehavior : + INotificationHandler, + INotificationHandler, + INotificationHandler { - public class UserTrackingBehavior : - INotificationHandler, - INotificationHandler, - INotificationHandler - { - private readonly ZhongliContext _db; + private readonly ZhongliContext _db; - public UserTrackingBehavior(ZhongliContext db) { _db = db; } + public UserTrackingBehavior(ZhongliContext db) { _db = db; } - public async Task Handle(GuildMemberUpdatedNotification notification, CancellationToken cancellationToken) - { - await _db.Users.TrackUserAsync(notification.NewMember, cancellationToken); - await _db.SaveChangesAsync(cancellationToken); - } - - public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) - { - if (notification.Message.Author is IGuildUser { Guild: { } } author) - { - await _db.Users.TrackUserAsync(author, cancellationToken); - await _db.SaveChangesAsync(cancellationToken); - } - } + public async Task Handle(GuildMemberUpdatedNotification notification, CancellationToken cancellationToken) + { + await _db.Users.TrackUserAsync(notification.NewMember, cancellationToken); + await _db.SaveChangesAsync(cancellationToken); + } - public async Task Handle(UserJoinedNotification notification, CancellationToken cancellationToken) + public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) + { + if (notification.Message.Author is IGuildUser { Guild: { } } author) { - await _db.Users.TrackUserAsync(notification.GuildUser, cancellationToken); + await _db.Users.TrackUserAsync(author, cancellationToken); await _db.SaveChangesAsync(cancellationToken); } } + + public async Task Handle(UserJoinedNotification notification, CancellationToken cancellationToken) + { + await _db.Users.TrackUserAsync(notification.GuildUser, cancellationToken); + await _db.SaveChangesAsync(cancellationToken); + } } \ No newline at end of file diff --git a/Zhongli.Bot/Behaviors/VoiceChatBehavior.cs b/Zhongli.Bot/Behaviors/VoiceChatBehavior.cs index de5e582..74b3d46 100644 --- a/Zhongli.Bot/Behaviors/VoiceChatBehavior.cs +++ b/Zhongli.Bot/Behaviors/VoiceChatBehavior.cs @@ -16,148 +16,147 @@ using Zhongli.Services.Interactive.Paginator; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Behaviors +namespace Zhongli.Bot.Behaviors; + +public class VoiceChatBehavior : INotificationHandler { - public class VoiceChatBehavior : INotificationHandler - { - private static readonly Regex VcRegex = new(@"^VC([ ]|-)(?[0-9]+)$", - RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex VcRegex = new(@"^VC([ ]|-)(?[0-9]+)$", + RegexOptions.IgnoreCase | RegexOptions.Compiled); - private readonly ICommandHelpService _commandHelp; - private readonly InteractiveService _interactive; - private readonly ZhongliContext _db; + private readonly ICommandHelpService _commandHelp; + private readonly InteractiveService _interactive; + private readonly ZhongliContext _db; - public VoiceChatBehavior(ICommandHelpService commandHelp, InteractiveService interactive, ZhongliContext db) - { - _commandHelp = commandHelp; - _interactive = interactive; - _db = db; - } + public VoiceChatBehavior(ICommandHelpService commandHelp, InteractiveService interactive, ZhongliContext db) + { + _commandHelp = commandHelp; + _interactive = interactive; + _db = db; + } - private static ConcurrentDictionary PurgeTasks { get; } = new(); + private static ConcurrentDictionary PurgeTasks { get; } = new(); - public async Task Handle(UserVoiceStateNotification notification, CancellationToken cancellationToken) - { - if (notification.User.IsBot || notification.User.IsWebhook || notification.User is not SocketGuildUser user) - return; + public async Task Handle(UserVoiceStateNotification notification, CancellationToken cancellationToken) + { + if (notification.User.IsBot || notification.User.IsWebhook || notification.User is not SocketGuildUser user) + return; - await _db.Users.TrackUserAsync(user, cancellationToken); + await _db.Users.TrackUserAsync(user, cancellationToken); - var guild = user.Guild; - var rules = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(r => r.GuildId == guild.Id, cancellationToken); + var guild = user.Guild; + var rules = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(r => r.GuildId == guild.Id, cancellationToken); - if (rules is null) - return; + if (rules is null) + return; - var oldChannel = notification.Old.VoiceChannel; - var newChannel = notification.New.VoiceChannel; + var oldChannel = notification.Old.VoiceChannel; + var newChannel = notification.New.VoiceChannel; - if (newChannel?.Id == rules.HubVoiceChannelId) + if (newChannel?.Id == rules.HubVoiceChannelId) + { + var ownerOf = rules.VoiceChats.FirstOrDefault(v => v.UserId == notification.User.Id); + if (ownerOf is not null) + { + var voiceChannel = guild.GetVoiceChannel(ownerOf.VoiceChannelId); + await user.ModifyAsync(u => u.Channel = voiceChannel); + } + else { - var ownerOf = rules.VoiceChats.FirstOrDefault(v => v.UserId == notification.User.Id); - if (ownerOf is not null) + var voiceChannelCategory = guild.GetCategoryChannel(rules.VoiceChannelCategoryId); + var voiceChatCategory = guild.GetCategoryChannel(rules.VoiceChatCategoryId); + + var ruleNumbers = voiceChannelCategory.Channels.Concat(voiceChatCategory.Channels) + .Select(v => VcRegex.Match(v.Name)) + .Where(m => m.Success && uint.TryParse(m.Groups["i"].Value, out _)) + .Select(m => uint.Parse(m.Groups["i"].Value)) + .ToList(); + + // To get the next available number, sort the list and then + // get the index of the first element that does not match its index. + // If there is nothing that match, then the next value must be the length of the list. + var maxId = ruleNumbers.OrderBy(x => x).AsIndexable() + .Where(item => item.Index != item.Value) + .Select(item => item.Index) + .DefaultIfEmpty(ruleNumbers.Count) + .FirstOrDefault(); + + var voiceChannel = await guild.CreateVoiceChannelAsync($"VC {maxId}", + c => c.CategoryId = rules.VoiceChannelCategoryId); + await voiceChannel.AddPermissionOverwriteAsync(user, + new OverwritePermissions(manageChannel: PermValue.Allow)); + + var textChannel = await guild.CreateTextChannelAsync($"vc-{maxId}", + c => c.CategoryId = rules.VoiceChatCategoryId); + await textChannel.AddPermissionOverwriteAsync(guild.EveryoneRole, + new OverwritePermissions(viewChannel: PermValue.Deny)); + + if (_commandHelp.TryGetEmbed("voice", HelpDataType.Module, out var paginated)) { - var voiceChannel = guild.GetVoiceChannel(ownerOf.VoiceChannelId); - await user.ModifyAsync(u => u.Channel = voiceChannel); + var embed = paginated.ToEmbed(); + var message = await textChannel.SendMessageAsync(embed: embed.Build()); + await message.PinAsync(); } - else - { - var voiceChannelCategory = guild.GetCategoryChannel(rules.VoiceChannelCategoryId); - var voiceChatCategory = guild.GetCategoryChannel(rules.VoiceChatCategoryId); - - var ruleNumbers = voiceChannelCategory.Channels.Concat(voiceChatCategory.Channels) - .Select(v => VcRegex.Match(v.Name)) - .Where(m => m.Success && uint.TryParse(m.Groups["i"].Value, out _)) - .Select(m => uint.Parse(m.Groups["i"].Value)) - .ToList(); - - // To get the next available number, sort the list and then - // get the index of the first element that does not match its index. - // If there is nothing that match, then the next value must be the length of the list. - var maxId = ruleNumbers.OrderBy(x => x).AsIndexable() - .Where(item => item.Index != item.Value) - .Select(item => item.Index) - .DefaultIfEmpty(ruleNumbers.Count) - .FirstOrDefault(); - - var voiceChannel = await guild.CreateVoiceChannelAsync($"VC {maxId}", - c => c.CategoryId = rules.VoiceChannelCategoryId); - await voiceChannel.AddPermissionOverwriteAsync(user, - new OverwritePermissions(manageChannel: PermValue.Allow)); - - var textChannel = await guild.CreateTextChannelAsync($"vc-{maxId}", - c => c.CategoryId = rules.VoiceChatCategoryId); - await textChannel.AddPermissionOverwriteAsync(guild.EveryoneRole, - new OverwritePermissions(viewChannel: PermValue.Deny)); - - if (_commandHelp.TryGetEmbed("voice", HelpDataType.Module, out var paginated)) - { - var embed = paginated.ToEmbed(); - var message = await textChannel.SendMessageAsync(embed: embed.Build()); - await message.PinAsync(); - } - var voiceChat = new VoiceChatLink - { - UserId = user.Id, - GuildId = guild.Id, - TextChannelId = textChannel.Id, - VoiceChannelId = voiceChannel.Id - }; + var voiceChat = new VoiceChatLink + { + UserId = user.Id, + GuildId = guild.Id, + TextChannelId = textChannel.Id, + VoiceChannelId = voiceChannel.Id + }; - rules.VoiceChats.Add(voiceChat); + rules.VoiceChats.Add(voiceChat); - _db.Update(rules); - await _db.SaveChangesAsync(cancellationToken); + _db.Update(rules); + await _db.SaveChangesAsync(cancellationToken); - await user.ModifyAsync(u => u.Channel = voiceChannel); - } + await user.ModifyAsync(u => u.Channel = voiceChannel); } - else if (oldChannel is not null && newChannel is null) + } + else if (oldChannel is not null && newChannel is null) + { + if (rules.PurgeEmpty && !PurgeTasks.ContainsKey(oldChannel.Id)) { - if (rules.PurgeEmpty && !PurgeTasks.ContainsKey(oldChannel.Id)) + var users = oldChannel.Users.Where(u => !u.IsBot); + if (!users.Any()) { - var users = oldChannel.Users.Where(u => !u.IsBot); - if (!users.Any()) + var voiceChat = rules.VoiceChats.FirstOrDefault(v => v.VoiceChannelId == oldChannel.Id); + if (voiceChat is not null) { - var voiceChat = rules.VoiceChats.FirstOrDefault(v => v.VoiceChannelId == oldChannel.Id); - if (voiceChat is not null) + var voiceChannel = guild.GetVoiceChannel(voiceChat.VoiceChannelId); + var textChannel = guild.GetTextChannel(voiceChat.TextChannelId); + + var tokenSource = new CancellationTokenSource(); + _ = Task.Run(async () => { - var voiceChannel = guild.GetVoiceChannel(voiceChat.VoiceChannelId); - var textChannel = guild.GetTextChannel(voiceChat.TextChannelId); - - var tokenSource = new CancellationTokenSource(); - _ = Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMinutes(1), tokenSource.Token); - await voiceChannel.DeleteAsync(); - await textChannel.DeleteAsync(); - - _db.Remove(voiceChat); - await _db.SaveChangesAsync(cancellationToken); - }, tokenSource.Token); - - PurgeTasks.TryAdd(oldChannel.Id, tokenSource); - } + await Task.Delay(TimeSpan.FromMinutes(1), tokenSource.Token); + await voiceChannel.DeleteAsync(); + await textChannel.DeleteAsync(); + + _db.Remove(voiceChat); + await _db.SaveChangesAsync(cancellationToken); + }, tokenSource.Token); + + PurgeTasks.TryAdd(oldChannel.Id, tokenSource); } } } - else if (newChannel is not null) - { - if (PurgeTasks.TryRemove(newChannel.Id, out var token)) - token?.Cancel(); + } + else if (newChannel is not null) + { + if (PurgeTasks.TryRemove(newChannel.Id, out var token)) + token?.Cancel(); - var voiceChat = rules.VoiceChats.FirstOrDefault(v => v.VoiceChannelId == newChannel.Id); - if (voiceChat is not null) - { - var textChannel = guild.GetTextChannel(voiceChat.TextChannelId); - await textChannel.AddPermissionOverwriteAsync(user, - new OverwritePermissions(viewChannel: PermValue.Allow)); + var voiceChat = rules.VoiceChats.FirstOrDefault(v => v.VoiceChannelId == newChannel.Id); + if (voiceChat is not null) + { + var textChannel = guild.GetTextChannel(voiceChat.TextChannelId); + await textChannel.AddPermissionOverwriteAsync(user, + new OverwritePermissions(viewChannel: PermValue.Allow)); - if (rules.ShowJoinLeave) - await textChannel.SendMessageAsync($"{user.Mention} has joined the VC. You can chat in here."); - } + if (rules.ShowJoinLeave) + await textChannel.SendMessageAsync($"{user.Mention} has joined the VC. You can chat in here."); } } } diff --git a/Zhongli.Bot/Bot.cs b/Zhongli.Bot/Bot.cs index 8b53a7a..17b44b7 100644 --- a/Zhongli.Bot/Bot.cs +++ b/Zhongli.Bot/Bot.cs @@ -26,172 +26,171 @@ using Zhongli.Services.Quote; using Zhongli.Services.TimeTracking; -namespace Zhongli.Bot +namespace Zhongli.Bot; + +public class Bot { - public class Bot + private const bool AttemptReset = true; + private static CancellationTokenSource? _mediatorToken; + private static readonly TimeSpan ResetTimeout = TimeSpan.FromSeconds(15); + private CancellationTokenSource _reconnectCts = null!; + + public static async Task Main() { await new Bot().StartAsync(); } + + private static ServiceProvider ConfigureServices() => + new ServiceCollection().AddHttpClient().AddMemoryCache().AddHangfireServer() + .AddDbContext(ContextOptions) + .AddMediatR(c => c.Using().AsTransient(), + typeof(Bot), typeof(DiscordSocketListener)) + .AddLogging(l => l.AddSerilog()) + .AddSingleton(new DiscordSocketClient(new DiscordSocketConfig + { + ExclusiveBulkDelete = true, + AlwaysDownloadUsers = true, + MessageCacheSize = ZhongliConfig.Configuration.MessageCacheSize + })) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddSingleton() + .AddExpirableServices() + .AddAutoRemoveMessage() + .AddCommandHelp() + .AddImages() + .BuildServiceProvider(); + + private static async Task CheckStateAsync(IDiscordClient client) { - private const bool AttemptReset = true; - private static CancellationTokenSource? _mediatorToken; - private static readonly TimeSpan ResetTimeout = TimeSpan.FromSeconds(15); - private CancellationTokenSource _reconnectCts = null!; - - public static async Task Main() { await new Bot().StartAsync(); } - - private static ServiceProvider ConfigureServices() => - new ServiceCollection().AddHttpClient().AddMemoryCache().AddHangfireServer() - .AddDbContext(ContextOptions) - .AddMediatR(c => c.Using().AsTransient(), - typeof(Bot), typeof(DiscordSocketListener)) - .AddLogging(l => l.AddSerilog()) - .AddSingleton(new DiscordSocketClient(new DiscordSocketConfig - { - ExclusiveBulkDelete = true, - AlwaysDownloadUsers = true, - MessageCacheSize = ZhongliConfig.Configuration.MessageCacheSize - })) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddScoped() - .AddSingleton() - .AddExpirableServices() - .AddAutoRemoveMessage() - .AddCommandHelp() - .AddImages() - .BuildServiceProvider(); - - private static async Task CheckStateAsync(IDiscordClient client) + // Client reconnected, no need to reset + if (client.ConnectionState == ConnectionState.Connected) return; + + if (AttemptReset) { - // Client reconnected, no need to reset - if (client.ConnectionState == ConnectionState.Connected) return; + Log.Information("Attempting to reset the client"); + + var timeout = Task.Delay(ResetTimeout); + var connect = client.StartAsync(); + var task = await Task.WhenAny(timeout, connect); - if (AttemptReset) + if (task == timeout) { - Log.Information("Attempting to reset the client"); - - var timeout = Task.Delay(ResetTimeout); - var connect = client.StartAsync(); - var task = await Task.WhenAny(timeout, connect); - - if (task == timeout) - { - Log.Fatal("Client reset timed out (task deadlocked?), killing process"); - FailFast(); - } - else if (connect.IsFaulted) - { - Log.Fatal(connect.Exception, "Client reset faulted, killing process"); - FailFast(); - } - else if (connect.IsCompletedSuccessfully) Log.Information("Client reset successfully!"); - - return; + Log.Fatal("Client reset timed out (task deadlocked?), killing process"); + FailFast(); } + else if (connect.IsFaulted) + { + Log.Fatal(connect.Exception, "Client reset faulted, killing process"); + FailFast(); + } + else if (connect.IsCompletedSuccessfully) Log.Information("Client reset successfully!"); - Log.Fatal("Client did not reconnect in time, killing process"); - FailFast(); + return; } - private Task ClientOnConnected() - { - Log.Debug("Client reconnected, resetting cancel tokens..."); - - _reconnectCts.Cancel(); - _reconnectCts = new CancellationTokenSource(); + Log.Fatal("Client did not reconnect in time, killing process"); + FailFast(); + } - Log.Debug("Client reconnected, cancel tokens reset"); - return Task.CompletedTask; - } + private Task ClientOnConnected() + { + Log.Debug("Client reconnected, resetting cancel tokens..."); - private Task ClientOnDisconnected(IDiscordClient client) - { - // Check the state after to see if we reconnected - Log.Information("Client disconnected, starting timeout task..."); - _ = Task.Delay(ResetTimeout, _reconnectCts.Token).ContinueWith(async _ => - { - Log.Debug("Timeout expired, continuing to check client state..."); - await CheckStateAsync(client); - Log.Debug("State came back okay"); - }); + _reconnectCts.Cancel(); + _reconnectCts = new CancellationTokenSource(); - return Task.CompletedTask; - } + Log.Debug("Client reconnected, cancel tokens reset"); + return Task.CompletedTask; + } - private static Task LogAsync(LogMessage message) + private Task ClientOnDisconnected(IDiscordClient client) + { + // Check the state after to see if we reconnected + Log.Information("Client disconnected, starting timeout task..."); + _ = Task.Delay(ResetTimeout, _reconnectCts.Token).ContinueWith(async _ => { - var severity = message.Severity switch - { - LogSeverity.Critical => LogEventLevel.Fatal, - LogSeverity.Error => LogEventLevel.Error, - LogSeverity.Warning => LogEventLevel.Warning, - LogSeverity.Info => LogEventLevel.Information, - LogSeverity.Verbose => LogEventLevel.Verbose, - LogSeverity.Debug => LogEventLevel.Debug, - _ => LogEventLevel.Information - }; - - Log.Write(severity, message.Exception, message.Message); - - return Task.CompletedTask; - } + Log.Debug("Timeout expired, continuing to check client state..."); + await CheckStateAsync(client); + Log.Debug("State came back okay"); + }); - private async Task StartAsync() + return Task.CompletedTask; + } + + private static Task LogAsync(LogMessage message) + { + var severity = message.Severity switch { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Override("Hangfire", LogEventLevel.Debug) - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Verbose() - .WriteTo.Console() - .CreateLogger(); + LogSeverity.Critical => LogEventLevel.Fatal, + LogSeverity.Error => LogEventLevel.Error, + LogSeverity.Warning => LogEventLevel.Warning, + LogSeverity.Info => LogEventLevel.Information, + LogSeverity.Verbose => LogEventLevel.Verbose, + LogSeverity.Debug => LogEventLevel.Debug, + _ => LogEventLevel.Information + }; + + Log.Write(severity, message.Exception, message.Message); + + return Task.CompletedTask; + } - await using var services = ConfigureServices(); + private async Task StartAsync() + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Override("Hangfire", LogEventLevel.Debug) + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Verbose() + .WriteTo.Console() + .CreateLogger(); - GlobalConfiguration.Configuration - .UseActivator(new AspNetCoreJobActivator( - services.GetRequiredService())) - .UseSerilogLogProvider() - .UsePostgreSqlStorage(ZhongliConfig.Configuration.HangfireContext) - .UseRecommendedSerializerSettings(); + await using var services = ConfigureServices(); - await services.GetRequiredService().InitializeAsync(); - var commands = services.GetRequiredService(); + GlobalConfiguration.Configuration + .UseActivator(new AspNetCoreJobActivator( + services.GetRequiredService())) + .UseSerilogLogProvider() + .UsePostgreSqlStorage(ZhongliConfig.Configuration.HangfireContext) + .UseRecommendedSerializerSettings(); - var client = services.GetRequiredService(); + await services.GetRequiredService().InitializeAsync(); + var commands = services.GetRequiredService(); - _reconnectCts = new CancellationTokenSource(); - _mediatorToken = new CancellationTokenSource(); + var client = services.GetRequiredService(); - await services.GetRequiredService() - .StartAsync(_mediatorToken.Token); + _reconnectCts = new CancellationTokenSource(); + _mediatorToken = new CancellationTokenSource(); - client.Disconnected += _ => ClientOnDisconnected(client); - client.Connected += ClientOnConnected; + await services.GetRequiredService() + .StartAsync(_mediatorToken.Token); - client.Log += LogAsync; - commands.Log += LogAsync; + client.Disconnected += _ => ClientOnDisconnected(client); + client.Connected += ClientOnConnected; - await client.LoginAsync(TokenType.Bot, ZhongliConfig.Configuration.Token); - await client.StartAsync(); + client.Log += LogAsync; + commands.Log += LogAsync; - using var server = new BackgroundJobServer(); + await client.LoginAsync(TokenType.Bot, ZhongliConfig.Configuration.Token); + await client.StartAsync(); - await Task.Delay(Timeout.Infinite); - } + using var server = new BackgroundJobServer(); - private static void ContextOptions(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder - .UseLazyLoadingProxies() - .UseNpgsql(ZhongliConfig.Configuration.ZhongliContext); - } + await Task.Delay(Timeout.Infinite); + } - private static void FailFast() - => Environment.Exit(1); + private static void ContextOptions(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .UseLazyLoadingProxies() + .UseNpgsql(ZhongliConfig.Configuration.ZhongliContext); } + + private static void FailFast() + => Environment.Exit(1); } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Censors/CensorExclusionsModule.cs b/Zhongli.Bot/Modules/Censors/CensorExclusionsModule.cs index b034fbf..cabb769 100644 --- a/Zhongli.Bot/Modules/Censors/CensorExclusionsModule.cs +++ b/Zhongli.Bot/Modules/Censors/CensorExclusionsModule.cs @@ -16,85 +16,84 @@ using Zhongli.Services.Utilities; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Bot.Modules.Censors +namespace Zhongli.Bot.Modules.Censors; + +[Name("Censor Exclusions")] +[Group("censor")] +[Alias("censors")] +[Summary("Manages cencor exclusions.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class CensorExclusionsModule : InteractiveEntity { - [Name("Censor Exclusions")] - [Group("censor")] - [Alias("censors")] - [Summary("Manages cencor exclusions.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class CensorExclusionsModule : InteractiveEntity + private readonly ZhongliContext _db; + + public CensorExclusionsModule(CommandErrorHandler error, ZhongliContext db) : base(error, db) { _db = db; } + + protected override string Title => "Censor Exclusions"; + + [Command("exclude")] + [Alias("ignore")] + [Summary("Exclude the set criteria globally in all censors.")] + public async Task ExcludeAsync(Exclusions exclusions) + { + var collection = await GetCollectionAsync(); + collection.AddCriteria(exclusions); + + await _db.SaveChangesAsync(); + + var embed = new EmbedBuilder() + .WithTitle("Censors exclusions added") + .WithColor(Color.Green) + .AddField("Excluded: ", exclusions.ToCriteria().Humanize()) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } + + [Command("include")] + [Summary("Remove a global censor exclusion by ID.")] + protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); + + [Command("exclusions")] + [Alias("view exclusions", "list exclusions")] + [Summary("View the configured censor exclusions.")] + protected async Task ViewExclusionsAsync() + { + var collection = await GetCollectionAsync(); + await PagedViewAsync(collection); + } + + protected override (string Title, StringBuilder Value) EntityViewer(Criterion entity) + => (entity.Id.ToString(), new StringBuilder($"{entity}")); + + protected override bool IsMatch(Criterion entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + + protected override async Task RemoveEntityAsync(Criterion censor) + { + _db.Remove(censor); + await _db.SaveChangesAsync(); + } + + protected override async Task> GetCollectionAsync() { - private readonly ZhongliContext _db; - - public CensorExclusionsModule(CommandErrorHandler error, ZhongliContext db) : base(error, db) { _db = db; } - - protected override string Title => "Censor Exclusions"; - - [Command("exclude")] - [Alias("ignore")] - [Summary("Exclude the set criteria globally in all censors.")] - public async Task ExcludeAsync(Exclusions exclusions) - { - var collection = await GetCollectionAsync(); - collection.AddCriteria(exclusions); - - await _db.SaveChangesAsync(); - - var embed = new EmbedBuilder() - .WithTitle("Censors exclusions added") - .WithColor(Color.Green) - .AddField("Excluded: ", exclusions.ToCriteria().Humanize()) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } - - [Command("include")] - [Summary("Remove a global censor exclusion by ID.")] - protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); - - [Command("exclusions")] - [Alias("view exclusions", "list exclusions")] - [Summary("View the configured censor exclusions.")] - protected async Task ViewExclusionsAsync() - { - var collection = await GetCollectionAsync(); - await PagedViewAsync(collection); - } - - protected override (string Title, StringBuilder Value) EntityViewer(Criterion entity) - => (entity.Id.ToString(), new StringBuilder($"{entity}")); - - protected override bool IsMatch(Criterion entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); - - protected override async Task RemoveEntityAsync(Criterion censor) - { - _db.Remove(censor); - await _db.SaveChangesAsync(); - } - - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - return guild.ModerationRules.CensorExclusions; - } - - [NamedArgumentType] - public class Exclusions : ICriteriaOptions - { - [HelpSummary("The permissions that the user must have.")] - public GuildPermission Permission { get; set; } = GuildPermission.None; - - [HelpSummary("The text or category channels that will be excluded.")] - public IEnumerable? Channels { get; set; } - - [HelpSummary("The users that are excluded.")] - public IEnumerable? Users { get; set; } - - [HelpSummary("The roles that are excluded.")] - public IEnumerable? Roles { get; set; } - } + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + return guild.ModerationRules.CensorExclusions; + } + + [NamedArgumentType] + public class Exclusions : ICriteriaOptions + { + [HelpSummary("The permissions that the user must have.")] + public GuildPermission Permission { get; set; } = GuildPermission.None; + + [HelpSummary("The text or category channels that will be excluded.")] + public IEnumerable? Channels { get; set; } + + [HelpSummary("The users that are excluded.")] + public IEnumerable? Users { get; set; } + + [HelpSummary("The roles that are excluded.")] + public IEnumerable? Roles { get; set; } } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Censors/CensorModule.cs b/Zhongli.Bot/Modules/Censors/CensorModule.cs index f7511f6..8abec37 100644 --- a/Zhongli.Bot/Modules/Censors/CensorModule.cs +++ b/Zhongli.Bot/Modules/Censors/CensorModule.cs @@ -23,220 +23,219 @@ using Zhongli.Services.Utilities; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Bot.Modules.Censors +namespace Zhongli.Bot.Modules.Censors; + +[Name("Censor")] +[Group("censor")] +[Alias("censors")] +[Summary("Manages cencors and what action will be done to the user who triggers them.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class CensorModule : InteractiveTrigger { - [Name("Censor")] - [Group("censor")] - [Alias("censors")] - [Summary("Manages cencors and what action will be done to the user who triggers them.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class CensorModule : InteractiveTrigger + private const string PatternSummary = "The .NET flavor regex pattern to be used."; + private readonly ZhongliContext _db; + + public CensorModule(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) + : base(error, db, moderation) + { + _db = db; + } + + protected override string Title { get; } = "Censors"; + + [Command("ban")] + [Summary("A censor that deletes the message and also bans the user.")] + public async Task AddBanCensorAsync( + [Summary(PatternSummary)] string pattern, + [Summary("Amount in days of messages that will be deleted when banned.")] + uint deleteDays = 0, + [Summary("The length of the ban. Leave empty for permanent.")] + TimeSpan? length = null, + CensorOptions? options = null) + { + var trigger = new BanAction(deleteDays, length); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("add")] + [Alias("create")] + [Summary("A censor that deletes the message and does nothing to the user.")] + public async Task AddCensorAsync( + [Summary(PatternSummary)] string pattern, + CensorOptions? options = null) + { + var censor = new Censor(pattern, null, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("kick")] + [Summary("A censor that deletes the message and also kicks the user.")] + public async Task AddKickCensorAsync( + [Summary(PatternSummary)] string pattern, + CensorOptions? options = null) + { + var trigger = new KickAction(); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("mute")] + [Summary("A censor that deletes the message and mutes the user.")] + public async Task AddMuteCensorAsync( + [Summary(PatternSummary)] string pattern, + [Summary("The length of the mute. Leave empty for permanent.")] + TimeSpan? length = null, + CensorOptions? options = null) + { + var trigger = new MuteAction(length); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("note")] + [Summary("A censor that deletes the message and does nothing to the user.")] + public async Task AddNoteCensorAsync( + [Summary(PatternSummary)] string pattern, + CensorOptions? options = null) + { + var trigger = new NoteAction(); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("notice")] + [Summary("A censor that deletes the message and gives a notice.")] + public async Task AddNoticeCensorAsync( + [Summary(PatternSummary)] string pattern, + CensorOptions? options = null) + { + var trigger = new NoticeAction(); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("warning")] + [Alias("warn")] + [Summary("A censor that deletes the message and does nothing to the user.")] + public async Task AddWarningCensorAsync( + [Summary(PatternSummary)] string pattern, + [Summary("The amount of warnings to be given. Defaults to 1.")] + uint count = 1, + CensorOptions? options = null) + { + var trigger = new WarningAction(count); + var censor = new Censor(pattern, trigger, options); + + await AddCensor(censor, options); + await ReplyCensorAsync(censor); + } + + [Command("test")] + [Alias("testword")] + [Summary("Test whether a word is in the list of censors or not.")] + public async Task TestCensorAsync(string word) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var matches = guild.ModerationRules.Triggers.OfType() + .Where(c => c.Regex().IsMatch(word)).ToList(); + + if (matches.Any()) + await PagedViewAsync(matches); + else + await ReplyAsync("No matches found."); + } + + [Command] + [Alias("list", "view")] + [Summary("View the censor list.")] + protected override Task ViewEntityAsync() => base.ViewEntityAsync(); + + protected override (string Title, StringBuilder Value) EntityViewer(Censor censor) + { + var value = new StringBuilder() + .AppendLine($"▌Pattern: {Format.Code(censor.Pattern)}") + .AppendLine($"▌Options: {censor.Options.Humanize()}") + .AppendLine($"▌Reprimand: {censor.Reprimand?.Action ?? "None"}") + .AppendLine($"▌Exclusions: {censor.Exclusions.Humanize()}") + .AppendLine($"▉ Active: {censor.IsActive}") + .AppendLine($"▉ Modified by: {censor.GetModerator()}"); + + return (censor.Id.ToString(), value); + } + + protected override bool IsMatch(Censor entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + + protected override async Task> GetCollectionAsync() + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + return guild.ModerationRules.Triggers.OfType().ToList(); + } + + private async Task AddCensor(Censor censor, ICriteriaOptions? exclusions) + { + if (exclusions is not null) + censor.Exclusions = exclusions.ToCriteria(); + + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.ModerationRules.Triggers + .Add(censor.WithModerator(Context)); + + await _db.SaveChangesAsync(); + } + + private async Task ReplyCensorAsync(Censor censor) + { + var (title, value) = EntityViewer(censor); + + var embed = new EmbedBuilder() + .WithTitle("Censor added successfully.") + .WithDescription(value.ToString()) + .AddField("ID", title) + .WithColor(Color.Green) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } + + [NamedArgumentType] + public class CensorOptions : ICensorOptions, ICriteriaOptions { - private const string PatternSummary = "The .NET flavor regex pattern to be used."; - private readonly ZhongliContext _db; - - public CensorModule(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) - : base(error, db, moderation) - { - _db = db; - } - - protected override string Title { get; } = "Censors"; - - [Command("ban")] - [Summary("A censor that deletes the message and also bans the user.")] - public async Task AddBanCensorAsync( - [Summary(PatternSummary)] string pattern, - [Summary("Amount in days of messages that will be deleted when banned.")] - uint deleteDays = 0, - [Summary("The length of the ban. Leave empty for permanent.")] - TimeSpan? length = null, - CensorOptions? options = null) - { - var trigger = new BanAction(deleteDays, length); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("add")] - [Alias("create")] - [Summary("A censor that deletes the message and does nothing to the user.")] - public async Task AddCensorAsync( - [Summary(PatternSummary)] string pattern, - CensorOptions? options = null) - { - var censor = new Censor(pattern, null, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("kick")] - [Summary("A censor that deletes the message and also kicks the user.")] - public async Task AddKickCensorAsync( - [Summary(PatternSummary)] string pattern, - CensorOptions? options = null) - { - var trigger = new KickAction(); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("mute")] - [Summary("A censor that deletes the message and mutes the user.")] - public async Task AddMuteCensorAsync( - [Summary(PatternSummary)] string pattern, - [Summary("The length of the mute. Leave empty for permanent.")] - TimeSpan? length = null, - CensorOptions? options = null) - { - var trigger = new MuteAction(length); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("note")] - [Summary("A censor that deletes the message and does nothing to the user.")] - public async Task AddNoteCensorAsync( - [Summary(PatternSummary)] string pattern, - CensorOptions? options = null) - { - var trigger = new NoteAction(); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("notice")] - [Summary("A censor that deletes the message and gives a notice.")] - public async Task AddNoticeCensorAsync( - [Summary(PatternSummary)] string pattern, - CensorOptions? options = null) - { - var trigger = new NoticeAction(); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("warning")] - [Alias("warn")] - [Summary("A censor that deletes the message and does nothing to the user.")] - public async Task AddWarningCensorAsync( - [Summary(PatternSummary)] string pattern, - [Summary("The amount of warnings to be given. Defaults to 1.")] - uint count = 1, - CensorOptions? options = null) - { - var trigger = new WarningAction(count); - var censor = new Censor(pattern, trigger, options); - - await AddCensor(censor, options); - await ReplyCensorAsync(censor); - } - - [Command("test")] - [Alias("testword")] - [Summary("Test whether a word is in the list of censors or not.")] - public async Task TestCensorAsync(string word) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var matches = guild.ModerationRules.Triggers.OfType() - .Where(c => c.Regex().IsMatch(word)).ToList(); - - if (matches.Any()) - await PagedViewAsync(matches); - else - await ReplyAsync("No matches found."); - } - - [Command] - [Alias("list", "view")] - [Summary("View the censor list.")] - protected override Task ViewEntityAsync() => base.ViewEntityAsync(); - - protected override (string Title, StringBuilder Value) EntityViewer(Censor censor) - { - var value = new StringBuilder() - .AppendLine($"▌Pattern: {Format.Code(censor.Pattern)}") - .AppendLine($"▌Options: {censor.Options.Humanize()}") - .AppendLine($"▌Reprimand: {censor.Reprimand?.Action ?? "None"}") - .AppendLine($"▌Exclusions: {censor.Exclusions.Humanize()}") - .AppendLine($"▉ Active: {censor.IsActive}") - .AppendLine($"▉ Modified by: {censor.GetModerator()}"); - - return (censor.Id.ToString(), value); - } - - protected override bool IsMatch(Censor entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); - - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - return guild.ModerationRules.Triggers.OfType().ToList(); - } - - private async Task AddCensor(Censor censor, ICriteriaOptions? exclusions) - { - if (exclusions is not null) - censor.Exclusions = exclusions.ToCriteria(); - - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.ModerationRules.Triggers - .Add(censor.WithModerator(Context)); - - await _db.SaveChangesAsync(); - } - - private async Task ReplyCensorAsync(Censor censor) - { - var (title, value) = EntityViewer(censor); - - var embed = new EmbedBuilder() - .WithTitle("Censor added successfully.") - .WithDescription(value.ToString()) - .AddField("ID", title) - .WithColor(Color.Green) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } - - [NamedArgumentType] - public class CensorOptions : ICensorOptions, ICriteriaOptions - { - [HelpSummary("Silently match and do not delete the message.")] - public bool Silent { get; set; } = false; - - [HelpSummary("Comma separated regex flags.")] - public RegexOptions Flags { get; set; } = RegexOptions.None; - - [HelpSummary("The permissions that the user must have.")] - public GuildPermission Permission { get; set; } = GuildPermission.None; - - [HelpSummary("The text or category channels that will be excluded.")] - public IEnumerable? Channels { get; set; } - - [HelpSummary("The users that are excluded.")] - public IEnumerable? Users { get; set; } - - [HelpSummary("The roles that are excluded.")] - public IEnumerable? Roles { get; set; } - - [HelpSummary("The behavior in which the reprimand of the censor triggers.")] - public TriggerMode Mode { get; set; } = TriggerMode.Exact; - - [HelpSummary("The amount of times the censor should be triggered before reprimanding.")] - public uint Amount { get; set; } = 1; - } + [HelpSummary("Silently match and do not delete the message.")] + public bool Silent { get; set; } = false; + + [HelpSummary("Comma separated regex flags.")] + public RegexOptions Flags { get; set; } = RegexOptions.None; + + [HelpSummary("The permissions that the user must have.")] + public GuildPermission Permission { get; set; } = GuildPermission.None; + + [HelpSummary("The text or category channels that will be excluded.")] + public IEnumerable? Channels { get; set; } + + [HelpSummary("The users that are excluded.")] + public IEnumerable? Users { get; set; } + + [HelpSummary("The roles that are excluded.")] + public IEnumerable? Roles { get; set; } + + [HelpSummary("The behavior in which the reprimand of the censor triggers.")] + public TriggerMode Mode { get; set; } = TriggerMode.Exact; + + [HelpSummary("The amount of times the censor should be triggered before reprimanding.")] + public uint Amount { get; set; } = 1; } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Configuration/ConfigureModule.cs b/Zhongli.Bot/Modules/Configuration/ConfigureModule.cs index bad511d..ad84188 100644 --- a/Zhongli.Bot/Modules/Configuration/ConfigureModule.cs +++ b/Zhongli.Bot/Modules/Configuration/ConfigureModule.cs @@ -11,152 +11,151 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Configuration +namespace Zhongli.Bot.Modules.Configuration; + +[Name("Configurations")] +[Group("configure")] +[Summary("Bot Configurations.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class ConfigureModule : ModuleBase { - [Name("Configurations")] - [Group("configure")] - [Summary("Bot Configurations.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class ConfigureModule : ModuleBase + private readonly ModerationService _moderation; + private readonly ZhongliContext _db; + + public ConfigureModule(ModerationService moderation, ZhongliContext db) { - private readonly ModerationService _moderation; - private readonly ZhongliContext _db; + _moderation = moderation; + _db = db; + } - public ConfigureModule(ModerationService moderation, ZhongliContext db) - { - _moderation = moderation; - _db = db; - } - - [Command("notice expiry")] - [Summary("Set the time for when a notice is automatically hidden. This will not affect old cases.")] - public async Task ConfigureAutoPardonNoticeAsync( - [Summary("Leave empty to disable auto pardon of notices.")] - TimeSpan? length = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.ModerationRules.NoticeExpiryLength = length; - await _db.SaveChangesAsync(); - - if (length is null) - await ReplyAsync("Auto-pardon of notices has been disabled."); - else - await ReplyAsync($"Notices will now be pardoned after {Format.Bold(length?.Humanize())}"); - } - - [Command("warning expiry")] - [Summary("Set the time for when a warning is automatically hidden. This will not affect old cases.")] - public async Task ConfigureAutoPardonWarningAsync( - [Summary("Leave empty to disable auto pardon of warnings.")] - TimeSpan? length = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.ModerationRules.WarningExpiryLength = length; - await _db.SaveChangesAsync(); - - if (length is null) - await ReplyAsync("Auto-pardon of warnings has been disabled."); - else - await ReplyAsync($"Warnings will now be pardoned after {Format.Bold(length?.Humanize())}"); - } - - [Command("replace mutes")] - [Alias("replace mute")] - [Summary("Whether mutes should be replaced when there is an active one.")] - public async Task ConfigureAutoPardonWarningAsync( - [Summary("Leave empty to toggle")] bool? shouldReplace = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.ModerationRules.ReplaceMutes = shouldReplace ?? !guild.ModerationRules.ReplaceMutes; - await _db.SaveChangesAsync(); - - await ReplyAsync($"New value: {guild.ModerationRules.ReplaceMutes}"); - } - - [Command("censor range")] - [Summary("Set the time for when a censor is considered.")] - public async Task ConfigureCensorTimeRangeAsync( - [Summary("Leave empty to disable censor range.")] - TimeSpan? length = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.ModerationRules.CensorTimeRange = length; - await _db.SaveChangesAsync(); - - if (length is null) - await ReplyAsync("Censor range has been disabled."); - else - await ReplyAsync($"Censors will now be considered active for {Format.Bold(length?.Humanize())}"); - } - - [Command("mute")] - [Summary("Configures the Mute role.")] - public async Task ConfigureMuteAsync( - [Summary("Optionally provide a mention, ID, or name of an existing role.")] - IRole? role = null) - { - await _moderation.ConfigureMuteRoleAsync(Context.Guild, role); - - if (role is null) - await ReplyAsync("Mute role has been configured."); - else - await ReplyAsync($"Mute role has been set to {Format.Bold(role.Name)}"); - } - - [Command("voice")] - [Summary("Configures the Voice Chat settings. Leave the categories empty to use the same one the hub uses.")] - public async Task ConfigureVoiceChatAsync( - [Summary( - "Mention, ID, or name of the hub voice channel that the user can join to create a new voice chat.")] - IVoiceChannel hubVoiceChannel, VoiceChatOptions? options = null) - { - var guild = await _db.Guilds.FindAsync(Context.Guild.Id); - - if (hubVoiceChannel.CategoryId is null) - return; - - if (guild.VoiceChatRules is not null) - _db.Remove(guild.VoiceChatRules); - - guild.VoiceChatRules = new VoiceChatRules - { - GuildId = guild.Id, - HubVoiceChannelId = hubVoiceChannel.Id, - VoiceChannelCategoryId = options?.VoiceChannelCategory?.Id ?? hubVoiceChannel.CategoryId.Value, - VoiceChatCategoryId = options?.VoiceChatCategory?.Id ?? hubVoiceChannel.CategoryId.Value, - PurgeEmpty = options?.PurgeEmpty ?? true, - ShowJoinLeave = options?.ShowJoinLeave ?? true - }; - - await _db.SaveChangesAsync(); - - var embed = new EmbedBuilder() - .WithTitle("Voice Chat settings") - .WithDescription("Voice Chat Hub settings have been configured with the following:") - .WithColor(Color.Green) - .AddField("Hub Voice Channel: ", $"<#{hubVoiceChannel.Id}>") - .AddField("Voice Channel Category: ", $"<#{guild.VoiceChatRules.VoiceChannelCategoryId}>") - .AddField("Voice Chat Category: ", $"<#{guild.VoiceChatRules.VoiceChatCategoryId}>") - .AddField("Purge Empty Voice Chats: ", guild.VoiceChatRules.PurgeEmpty) - .AddField("Show Join-Leave: ", guild.VoiceChatRules.ShowJoinLeave) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } - - [NamedArgumentType] - public class VoiceChatOptions + [Command("notice expiry")] + [Summary("Set the time for when a notice is automatically hidden. This will not affect old cases.")] + public async Task ConfigureAutoPardonNoticeAsync( + [Summary("Leave empty to disable auto pardon of notices.")] + TimeSpan? length = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.ModerationRules.NoticeExpiryLength = length; + await _db.SaveChangesAsync(); + + if (length is null) + await ReplyAsync("Auto-pardon of notices has been disabled."); + else + await ReplyAsync($"Notices will now be pardoned after {Format.Bold(length?.Humanize())}"); + } + + [Command("warning expiry")] + [Summary("Set the time for when a warning is automatically hidden. This will not affect old cases.")] + public async Task ConfigureAutoPardonWarningAsync( + [Summary("Leave empty to disable auto pardon of warnings.")] + TimeSpan? length = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.ModerationRules.WarningExpiryLength = length; + await _db.SaveChangesAsync(); + + if (length is null) + await ReplyAsync("Auto-pardon of warnings has been disabled."); + else + await ReplyAsync($"Warnings will now be pardoned after {Format.Bold(length?.Humanize())}"); + } + + [Command("replace mutes")] + [Alias("replace mute")] + [Summary("Whether mutes should be replaced when there is an active one.")] + public async Task ConfigureAutoPardonWarningAsync( + [Summary("Leave empty to toggle")] bool? shouldReplace = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.ModerationRules.ReplaceMutes = shouldReplace ?? !guild.ModerationRules.ReplaceMutes; + await _db.SaveChangesAsync(); + + await ReplyAsync($"New value: {guild.ModerationRules.ReplaceMutes}"); + } + + [Command("censor range")] + [Summary("Set the time for when a censor is considered.")] + public async Task ConfigureCensorTimeRangeAsync( + [Summary("Leave empty to disable censor range.")] + TimeSpan? length = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.ModerationRules.CensorTimeRange = length; + await _db.SaveChangesAsync(); + + if (length is null) + await ReplyAsync("Censor range has been disabled."); + else + await ReplyAsync($"Censors will now be considered active for {Format.Bold(length?.Humanize())}"); + } + + [Command("mute")] + [Summary("Configures the Mute role.")] + public async Task ConfigureMuteAsync( + [Summary("Optionally provide a mention, ID, or name of an existing role.")] + IRole? role = null) + { + await _moderation.ConfigureMuteRoleAsync(Context.Guild, role); + + if (role is null) + await ReplyAsync("Mute role has been configured."); + else + await ReplyAsync($"Mute role has been set to {Format.Bold(role.Name)}"); + } + + [Command("voice")] + [Summary("Configures the Voice Chat settings. Leave the categories empty to use the same one the hub uses.")] + public async Task ConfigureVoiceChatAsync( + [Summary( + "Mention, ID, or name of the hub voice channel that the user can join to create a new voice chat.")] + IVoiceChannel hubVoiceChannel, VoiceChatOptions? options = null) + { + var guild = await _db.Guilds.FindAsync(Context.Guild.Id); + + if (hubVoiceChannel.CategoryId is null) + return; + + if (guild.VoiceChatRules is not null) + _db.Remove(guild.VoiceChatRules); + + guild.VoiceChatRules = new VoiceChatRules { - [HelpSummary("Purge empty channels after 1 minute automatically.")] - public bool PurgeEmpty { get; init; } = true; + GuildId = guild.Id, + HubVoiceChannelId = hubVoiceChannel.Id, + VoiceChannelCategoryId = options?.VoiceChannelCategory?.Id ?? hubVoiceChannel.CategoryId.Value, + VoiceChatCategoryId = options?.VoiceChatCategory?.Id ?? hubVoiceChannel.CategoryId.Value, + PurgeEmpty = options?.PurgeEmpty ?? true, + ShowJoinLeave = options?.ShowJoinLeave ?? true + }; + + await _db.SaveChangesAsync(); + + var embed = new EmbedBuilder() + .WithTitle("Voice Chat settings") + .WithDescription("Voice Chat Hub settings have been configured with the following:") + .WithColor(Color.Green) + .AddField("Hub Voice Channel: ", $"<#{hubVoiceChannel.Id}>") + .AddField("Voice Channel Category: ", $"<#{guild.VoiceChatRules.VoiceChannelCategoryId}>") + .AddField("Voice Chat Category: ", $"<#{guild.VoiceChatRules.VoiceChatCategoryId}>") + .AddField("Purge Empty Voice Chats: ", guild.VoiceChatRules.PurgeEmpty) + .AddField("Show Join-Leave: ", guild.VoiceChatRules.ShowJoinLeave) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } + + [NamedArgumentType] + public class VoiceChatOptions + { + [HelpSummary("Purge empty channels after 1 minute automatically.")] + public bool PurgeEmpty { get; init; } = true; - [HelpSummary("Show join messages.")] public bool ShowJoinLeave { get; init; } = true; + [HelpSummary("Show join messages.")] public bool ShowJoinLeave { get; init; } = true; - [HelpSummary("The category where Voice Channels will be made.")] - public ICategoryChannel? VoiceChannelCategory { get; init; } + [HelpSummary("The category where Voice Channels will be made.")] + public ICategoryChannel? VoiceChannelCategory { get; init; } - [HelpSummary("The category where Voice Chat channels will be made.")] - public ICategoryChannel? VoiceChatCategory { get; init; } - } + [HelpSummary("The category where Voice Chat channels will be made.")] + public ICategoryChannel? VoiceChatCategory { get; init; } } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Configuration/TimeTrackingModule.cs b/Zhongli.Bot/Modules/Configuration/TimeTrackingModule.cs index 84d9b7b..6898710 100644 --- a/Zhongli.Bot/Modules/Configuration/TimeTrackingModule.cs +++ b/Zhongli.Bot/Modules/Configuration/TimeTrackingModule.cs @@ -11,65 +11,64 @@ using Zhongli.Services.TimeTracking; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Configuration +namespace Zhongli.Bot.Modules.Configuration; + +[Name("Time Tracking")] +[Group("time")] +[Summary("Time tracking module.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class TimeTrackingModule : ModuleBase { - [Name("Time Tracking")] - [Group("time")] - [Summary("Time tracking module.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class TimeTrackingModule : ModuleBase + private readonly CommandErrorHandler _error; + private readonly GenshinTimeTrackingService _time; + private readonly ZhongliContext _db; + + public TimeTrackingModule(CommandErrorHandler error, GenshinTimeTrackingService time, ZhongliContext db) + { + _error = error; + _time = time; + _db = db; + } + + [Command("genshin")] + public async Task GenshinAsync(ITextChannel channel) + { + var message = await channel.SendMessageAsync("Setting up..."); + await GenshinAsync(message); + } + + [Command("genshin")] + public async Task GenshinAsync(IUserMessage? message = null) { - private readonly CommandErrorHandler _error; - private readonly GenshinTimeTrackingService _time; - private readonly ZhongliContext _db; + message ??= await ReplyAsync("Setting up..."); - public TimeTrackingModule(CommandErrorHandler error, GenshinTimeTrackingService time, ZhongliContext db) + if (message.Channel is SocketGuildChannel channel + && channel.Guild.Id != Context.Guild.Id) { - _error = error; - _time = time; - _db = db; + await _error.AssociateError(Context.Message, "Invalid message."); + return; } - [Command("genshin")] - public async Task GenshinAsync(ITextChannel channel) + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.GenshinRules ??= new GenshinTimeTrackingRules(); + + var serverStatus = guild.GenshinRules.ServerStatus; + if (serverStatus is not null) { - var message = await channel.SendMessageAsync("Setting up..."); - await GenshinAsync(message); + var removed = _db.Remove(serverStatus).Entity; + RecurringJob.RemoveIfExists(removed.Id.ToString()); } - [Command("genshin")] - public async Task GenshinAsync(IUserMessage? message = null) + guild.GenshinRules.ServerStatus = new MessageTimeTracking { - message ??= await ReplyAsync("Setting up..."); - - if (message.Channel is SocketGuildChannel channel - && channel.Guild.Id != Context.Guild.Id) - { - await _error.AssociateError(Context.Message, "Invalid message."); - return; - } - - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.GenshinRules ??= new GenshinTimeTrackingRules(); + GuildId = guild.Id, + ChannelId = message.Channel.Id, + MessageId = message.Id + }; - var serverStatus = guild.GenshinRules.ServerStatus; - if (serverStatus is not null) - { - var removed = _db.Remove(serverStatus).Entity; - RecurringJob.RemoveIfExists(removed.Id.ToString()); - } + await _db.SaveChangesAsync(); + _time.TrackGenshinTime(guild.GenshinRules); - guild.GenshinRules.ServerStatus = new MessageTimeTracking - { - GuildId = guild.Id, - ChannelId = message.Channel.Id, - MessageId = message.Id - }; - - await _db.SaveChangesAsync(); - _time.TrackGenshinTime(guild.GenshinRules); - - await Context.Message.AddReactionAsync(new Emoji("✅")); - } + await Context.Message.AddReactionAsync(new Emoji("✅")); } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/HelpModule.cs b/Zhongli.Bot/Modules/HelpModule.cs index b79351a..0d46a7e 100644 --- a/Zhongli.Bot/Modules/HelpModule.cs +++ b/Zhongli.Bot/Modules/HelpModule.cs @@ -10,119 +10,118 @@ using Zhongli.Services.Interactive.Paginator; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules +namespace Zhongli.Bot.Modules; + +[Name("Help")] +[Group("help")] +[Summary("Provides commands for helping users to understand how to interact with the bot.")] +public sealed class HelpModule : InteractiveBase { - [Name("Help")] - [Group("help")] - [Summary("Provides commands for helping users to understand how to interact with the bot.")] - public sealed class HelpModule : InteractiveBase - { - private readonly ICommandHelpService _commandHelpService; + private readonly ICommandHelpService _commandHelpService; - public HelpModule(ICommandHelpService commandHelpService) { _commandHelpService = commandHelpService; } + public HelpModule(ICommandHelpService commandHelpService) { _commandHelpService = commandHelpService; } - [Command] - [Summary("Prints a neat list of all commands.")] - public async Task HelpAsync() - { - var modules = _commandHelpService.GetModuleHelpData() - .OrderBy(d => d.Name) - .Select(d => Format.Bold(Format.Code(d.Name))); - - var prefix = ZhongliConfig.Configuration.Prefix; - var descriptionBuilder = new StringBuilder() - .AppendLine("Modules:") - .AppendJoin(", ", modules) - .AppendLine().AppendLine() - .AppendLine($"Do {Format.Code($"{prefix}help dm")} to have everything DMed to you.") - .AppendLine($"Do {Format.Code($"{prefix}help [module name]")} to have that module's commands listed."); - - var argumentBuilder = new StringBuilder() - .AppendLine($"{Format.Code("[ ]")}: Optional arguments.") - .AppendLine($"{Format.Code("< >")}: Required arguments.") - .AppendLine($"{Format.Code("[...]")}: List of arguments separated by {Format.Code(",")}.") - .AppendLine( - $"▌Provide values by doing {Format.Code("name: value")} " + - $"or {Format.Code("name: \"value with spaces\"")}."); - - var embed = new EmbedBuilder() - .WithTitle("Help") - .WithCurrentTimestamp() - .WithDescription(descriptionBuilder.ToString()) - .AddField("Arguments", argumentBuilder.ToString()) - .WithGuildAsAuthor(Context.Guild, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } + [Command] + [Summary("Prints a neat list of all commands.")] + public async Task HelpAsync() + { + var modules = _commandHelpService.GetModuleHelpData() + .OrderBy(d => d.Name) + .Select(d => Format.Bold(Format.Code(d.Name))); + + var prefix = ZhongliConfig.Configuration.Prefix; + var descriptionBuilder = new StringBuilder() + .AppendLine("Modules:") + .AppendJoin(", ", modules) + .AppendLine().AppendLine() + .AppendLine($"Do {Format.Code($"{prefix}help dm")} to have everything DMed to you.") + .AppendLine($"Do {Format.Code($"{prefix}help [module name]")} to have that module's commands listed."); + + var argumentBuilder = new StringBuilder() + .AppendLine($"{Format.Code("[ ]")}: Optional arguments.") + .AppendLine($"{Format.Code("< >")}: Required arguments.") + .AppendLine($"{Format.Code("[...]")}: List of arguments separated by {Format.Code(",")}.") + .AppendLine( + $"▌Provide values by doing {Format.Code("name: value")} " + + $"or {Format.Code("name: \"value with spaces\"")}."); + + var embed = new EmbedBuilder() + .WithTitle("Help") + .WithCurrentTimestamp() + .WithDescription(descriptionBuilder.ToString()) + .AddField("Arguments", argumentBuilder.ToString()) + .WithGuildAsAuthor(Context.Guild, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } - [Command] - [Summary("Retrieves help from a specific module or command.")] - [Priority(-10)] - public async Task HelpAsync( - [Remainder] [Summary("Name of the module or command to query.")] - string query) - { - await HelpAsync(query, HelpDataType.Command | HelpDataType.Module); - } + [Command] + [Summary("Retrieves help from a specific module or command.")] + [Priority(-10)] + public async Task HelpAsync( + [Remainder] [Summary("Name of the module or command to query.")] + string query) + { + await HelpAsync(query, HelpDataType.Command | HelpDataType.Module); + } - [Command("command")] - [Alias("commands")] - [Summary("Retrieves help from a specific command. Useful for commands that have an overlapping module name.")] - public async Task HelpCommandAsync( - [Remainder] [Summary("Name of the module to query.")] - string query) - { - await HelpAsync(query, HelpDataType.Command); - } + [Command("command")] + [Alias("commands")] + [Summary("Retrieves help from a specific command. Useful for commands that have an overlapping module name.")] + public async Task HelpCommandAsync( + [Remainder] [Summary("Name of the module to query.")] + string query) + { + await HelpAsync(query, HelpDataType.Command); + } + + [Command("dm")] + [Summary("Spams the user's DMs with a list of every command available.")] + public async Task HelpDMAsync() + { + var userDM = await Context.User.GetOrCreateDMChannelAsync(); - [Command("dm")] - [Summary("Spams the user's DMs with a list of every command available.")] - public async Task HelpDMAsync() + foreach (var module in _commandHelpService.GetModuleHelpData().OrderBy(x => x.Name)) { - var userDM = await Context.User.GetOrCreateDMChannelAsync(); + var embed = _commandHelpService.GetEmbedForModule(module); - foreach (var module in _commandHelpService.GetModuleHelpData().OrderBy(x => x.Name)) + try { - var embed = _commandHelpService.GetEmbedForModule(module); - - try - { - await this.PagedDMAsync(embed); - } - catch (HttpException ex) when (ex.DiscordCode == 50007) - { - await ReplyAsync( - $"You have private messages for this server disabled, {Context.User.Mention}. Please enable them so that I can send you help."); - return; - } + await this.PagedDMAsync(embed); + } + catch (HttpException ex) when (ex.DiscordCode == 50007) + { + await ReplyAsync( + $"You have private messages for this server disabled, {Context.User.Mention}. Please enable them so that I can send you help."); + return; } - - await ReplyAsync($"Check your private messages, {Context.User.Mention}."); } - [Command("module")] - [Alias("modules")] - [Summary("Retrieves help from a specific module. Useful for modules that have an overlapping command name.")] - public async Task HelpModuleAsync( - [Remainder] [Summary("Name of the module to query.")] - string query) - { - await HelpAsync(query, HelpDataType.Module); - } + await ReplyAsync($"Check your private messages, {Context.User.Mention}."); + } - private async Task HelpAsync(string query, HelpDataType type) - { - var sanitizedQuery = FormatUtilities.SanitizeAllMentions(query); + [Command("module")] + [Alias("modules")] + [Summary("Retrieves help from a specific module. Useful for modules that have an overlapping command name.")] + public async Task HelpModuleAsync( + [Remainder] [Summary("Name of the module to query.")] + string query) + { + await HelpAsync(query, HelpDataType.Module); + } - if (_commandHelpService.TryGetEmbed(query, type, out var paginated)) - { - paginated.Content = $"Results for \"{sanitizedQuery}\":"; + private async Task HelpAsync(string query, HelpDataType type) + { + var sanitizedQuery = FormatUtilities.SanitizeAllMentions(query); - await PagedReplyAsync(paginated); - return; - } + if (_commandHelpService.TryGetEmbed(query, type, out var paginated)) + { + paginated.Content = $"Results for \"{sanitizedQuery}\":"; - await ReplyAsync($"Sorry, I couldn't find help related to \"{sanitizedQuery}\"."); + await PagedReplyAsync(paginated); + return; } + + await ReplyAsync($"Sorry, I couldn't find help related to \"{sanitizedQuery}\"."); } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Logging/LoggingExclusionsModule.cs b/Zhongli.Bot/Modules/Logging/LoggingExclusionsModule.cs index 7e13fdd..bce4a51 100644 --- a/Zhongli.Bot/Modules/Logging/LoggingExclusionsModule.cs +++ b/Zhongli.Bot/Modules/Logging/LoggingExclusionsModule.cs @@ -16,85 +16,84 @@ using Zhongli.Services.Utilities; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Bot.Modules.Logging +namespace Zhongli.Bot.Modules.Logging; + +[Name("Logging Exclusions")] +[Group("log")] +[Alias("logs", "logging")] +[Summary("Manages logging exclusions.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class LoggingExclusionsModule : InteractiveEntity { - [Name("Logging Exclusions")] - [Group("log")] - [Alias("logs", "logging")] - [Summary("Manages logging exclusions.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class LoggingExclusionsModule : InteractiveEntity + private readonly ZhongliContext _db; + + public LoggingExclusionsModule(CommandErrorHandler error, ZhongliContext db) : base(error, db) { _db = db; } + + protected override string Title => "Censor Exclusions"; + + [Command("exclude")] + [Alias("ignore")] + [Summary("Exclude the set criteria globally in logging.")] + public async Task ExcludeAsync(Exclusions exclusions) + { + var collection = await GetCollectionAsync(); + collection.AddCriteria(exclusions); + + await _db.SaveChangesAsync(); + + var embed = new EmbedBuilder() + .WithTitle("Logging exclusions added") + .WithColor(Color.Green) + .AddField("Excluded: ", exclusions.ToCriteria().Humanize()) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } + + [Command("include")] + [Summary("Remove a global logging exclusion by ID.")] + protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); + + [Command("exclusions")] + [Alias("view exclusions", "list exclusions")] + [Summary("View the configured logging exclusions.")] + protected async Task ViewExclusionsAsync() + { + var collection = await GetCollectionAsync(); + await PagedViewAsync(collection); + } + + protected override (string Title, StringBuilder Value) EntityViewer(Criterion entity) + => (entity.Id.ToString(), new StringBuilder($"{entity}")); + + protected override bool IsMatch(Criterion entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + + protected override async Task RemoveEntityAsync(Criterion criterion) + { + _db.Remove(criterion); + await _db.SaveChangesAsync(); + } + + protected override async Task> GetCollectionAsync() { - private readonly ZhongliContext _db; - - public LoggingExclusionsModule(CommandErrorHandler error, ZhongliContext db) : base(error, db) { _db = db; } - - protected override string Title => "Censor Exclusions"; - - [Command("exclude")] - [Alias("ignore")] - [Summary("Exclude the set criteria globally in logging.")] - public async Task ExcludeAsync(Exclusions exclusions) - { - var collection = await GetCollectionAsync(); - collection.AddCriteria(exclusions); - - await _db.SaveChangesAsync(); - - var embed = new EmbedBuilder() - .WithTitle("Logging exclusions added") - .WithColor(Color.Green) - .AddField("Excluded: ", exclusions.ToCriteria().Humanize()) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } - - [Command("include")] - [Summary("Remove a global logging exclusion by ID.")] - protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); - - [Command("exclusions")] - [Alias("view exclusions", "list exclusions")] - [Summary("View the configured logging exclusions.")] - protected async Task ViewExclusionsAsync() - { - var collection = await GetCollectionAsync(); - await PagedViewAsync(collection); - } - - protected override (string Title, StringBuilder Value) EntityViewer(Criterion entity) - => (entity.Id.ToString(), new StringBuilder($"{entity}")); - - protected override bool IsMatch(Criterion entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); - - protected override async Task RemoveEntityAsync(Criterion criterion) - { - _db.Remove(criterion); - await _db.SaveChangesAsync(); - } - - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - return guild.LoggingRules.LoggingExclusions; - } - - [NamedArgumentType] - public class Exclusions : ICriteriaOptions - { - [HelpSummary("The permissions that the user must have.")] - public GuildPermission Permission { get; set; } = GuildPermission.None; - - [HelpSummary("The text or category channels that will be excluded.")] - public IEnumerable? Channels { get; set; } - - [HelpSummary("The users that are excluded.")] - public IEnumerable? Users { get; set; } - - [HelpSummary("The roles that are excluded.")] - public IEnumerable? Roles { get; set; } - } + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + return guild.LoggingRules.LoggingExclusions; + } + + [NamedArgumentType] + public class Exclusions : ICriteriaOptions + { + [HelpSummary("The permissions that the user must have.")] + public GuildPermission Permission { get; set; } = GuildPermission.None; + + [HelpSummary("The text or category channels that will be excluded.")] + public IEnumerable? Channels { get; set; } + + [HelpSummary("The users that are excluded.")] + public IEnumerable? Users { get; set; } + + [HelpSummary("The roles that are excluded.")] + public IEnumerable? Roles { get; set; } } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Logging/LoggingModule.cs b/Zhongli.Bot/Modules/Logging/LoggingModule.cs index 20483c9..202f42f 100644 --- a/Zhongli.Bot/Modules/Logging/LoggingModule.cs +++ b/Zhongli.Bot/Modules/Logging/LoggingModule.cs @@ -13,161 +13,160 @@ using Zhongli.Services.Core.Preconditions; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Logging +namespace Zhongli.Bot.Modules.Logging; + +[Name("Logging Configuration")] +[Group("log")] +[Alias("logs", "logging")] +[Summary("Logging configuration.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class LoggingModule : ModuleBase { - [Name("Logging Configuration")] - [Group("log")] - [Alias("logs", "logging")] - [Summary("Logging configuration.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class LoggingModule : ModuleBase - { - private readonly ZhongliContext _db; + private readonly ZhongliContext _db; - public LoggingModule(ZhongliContext db) { _db = db; } + public LoggingModule(ZhongliContext db) { _db = db; } - [Command("appeal")] - [Summary("Show the appeal message on a reprimand type.")] - public async Task ConfigureAppealAsync( - [Summary("The type of reprimand to show the appeal message on. Leave blank to view current settings.")] - ReprimandNoticeType type = ReprimandNoticeType.None, - [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] - bool? showAppeal = null) + [Command("appeal")] + [Summary("Show the appeal message on a reprimand type.")] + public async Task ConfigureAppealAsync( + [Summary("The type of reprimand to show the appeal message on. Leave blank to view current settings.")] + ReprimandNoticeType type = ReprimandNoticeType.None, + [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] + bool? showAppeal = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + if (type is not ReprimandNoticeType.None) { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - if (type is not ReprimandNoticeType.None) - { - var reprimands = guild.LoggingRules.ShowAppealOnReprimands.SetValue(type, showAppeal); - guild.LoggingRules.ShowAppealOnReprimands = reprimands; - await _db.SaveChangesAsync(); - } - - await ReplyAsync($"Current value: {guild.LoggingRules.ShowAppealOnReprimands.Humanize()}"); + var reprimands = guild.LoggingRules.ShowAppealOnReprimands.SetValue(type, showAppeal); + guild.LoggingRules.ShowAppealOnReprimands = reprimands; + await _db.SaveChangesAsync(); } - [Command("appeal message")] - [Summary("Set the appeal message when someone is reprimanded.")] - public async Task ConfigureAppealMessageAsync( - [Remainder] [Summary("Leave empty to disable the appeal message.")] - string? message = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - guild.LoggingRules.ReprimandAppealMessage = message; + await ReplyAsync($"Current value: {guild.LoggingRules.ShowAppealOnReprimands.Humanize()}"); + } - await _db.SaveChangesAsync(); + [Command("appeal message")] + [Summary("Set the appeal message when someone is reprimanded.")] + public async Task ConfigureAppealMessageAsync( + [Remainder] [Summary("Leave empty to disable the appeal message.")] + string? message = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + guild.LoggingRules.ReprimandAppealMessage = message; - var embed = new EmbedBuilder() - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + await _db.SaveChangesAsync(); - if (message is null) - { - embed.WithDescription("The appeal message has been succesfully removed.") - .WithColor(Color.LightGrey) - .WithTitle("Appeal Message Cleared"); - } - else - { - embed.WithDescription($"The appeal message has been set to: {Format.Bold(message)}") - .WithColor(Color.Green) - .WithTitle("Appeal Message Set"); - } + var embed = new EmbedBuilder() + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - await ReplyAsync(embed: embed.Build()); + if (message is null) + { + embed.WithDescription("The appeal message has been succesfully removed.") + .WithColor(Color.LightGrey) + .WithTitle("Appeal Message Cleared"); } - - [Command("moderation rules")] - [Summary("Configure the moderation logging options.")] - public async Task ConfigureLoggingChannelAsync( - [Summary("The logging option to configure. Leave blank to view current settings.")] - ReprimandOptions type = ReprimandOptions.None, - [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] - bool? state = null) + else { - var guild = await _db.Guilds.FindAsync(Context.Guild.Id); - if (type is not ReprimandOptions.None) - { - var options = guild.ModerationRules.Options.SetValue(type, state); - guild.ModerationRules.Options = options; - await _db.SaveChangesAsync(); - } - - await ReplyAsync($"Current value: {guild.ModerationRules.Options.Humanize()}"); + embed.WithDescription($"The appeal message has been set to: {Format.Bold(message)}") + .WithColor(Color.Green) + .WithTitle("Appeal Message Set"); } - [Command("event")] - [Summary("Enable or disable specific events to be logged.")] - public async Task ConfigureLoggingChannelAsync( - [Summary("The type of log event to configure. Comma separated.")] - IReadOnlyCollection types, - [Summary("Leave empty to disable these events.")] - ITextChannel? channel = null) + await ReplyAsync(embed: embed.Build()); + } + + [Command("moderation rules")] + [Summary("Configure the moderation logging options.")] + public async Task ConfigureLoggingChannelAsync( + [Summary("The logging option to configure. Leave blank to view current settings.")] + ReprimandOptions type = ReprimandOptions.None, + [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] + bool? state = null) + { + var guild = await _db.Guilds.FindAsync(Context.Guild.Id); + if (type is not ReprimandOptions.None) { - if (!types.Any()) return; + var options = guild.ModerationRules.Options.SetValue(type, state); + guild.ModerationRules.Options = options; + await _db.SaveChangesAsync(); + } - var guild = await _db.Guilds.FindAsync(Context.Guild.Id); - var rules = guild.LoggingRules.LoggingChannels; - await SetLoggingChannelAsync(channel, types, rules); + await ReplyAsync($"Current value: {guild.ModerationRules.Options.Humanize()}"); + } - if (channel is not null) - await ReplyAsync($"Set the log events {types.Humanize()} to be sent to {channel.Mention}."); - else - await ReplyAsync($"Disabled the log events {types.Humanize()}."); - } + [Command("event")] + [Summary("Enable or disable specific events to be logged.")] + public async Task ConfigureLoggingChannelAsync( + [Summary("The type of log event to configure. Comma separated.")] + IReadOnlyCollection types, + [Summary("Leave empty to disable these events.")] + ITextChannel? channel = null) + { + if (!types.Any()) return; - [Command("reprimand")] - [Summary("Enable or disable specific reprimands to be logged.")] - public async Task ConfigureLoggingChannelAsync( - [Summary("Set to 'null' to disable these events.")] - ITextChannel? channel, - [Summary("The type of log event to configure. Space separated.")] - params ReprimandType[] types) - { - if (!types.Any()) return; + var guild = await _db.Guilds.FindAsync(Context.Guild.Id); + var rules = guild.LoggingRules.LoggingChannels; + await SetLoggingChannelAsync(channel, types, rules); - var guild = await _db.Guilds.FindAsync(Context.Guild.Id); - var rules = guild.ModerationRules.LoggingChannels; - await SetLoggingChannelAsync(channel, types, rules); + if (channel is not null) + await ReplyAsync($"Set the log events {types.Humanize()} to be sent to {channel.Mention}."); + else + await ReplyAsync($"Disabled the log events {types.Humanize()}."); + } - if (channel is not null) - await ReplyAsync($"Set the log events {types.Humanize()} to be sent to {channel.Mention}."); - else - await ReplyAsync($"Disabled the log events {types.Humanize()}."); - } + [Command("reprimand")] + [Summary("Enable or disable specific reprimands to be logged.")] + public async Task ConfigureLoggingChannelAsync( + [Summary("Set to 'null' to disable these events.")] + ITextChannel? channel, + [Summary("The type of log event to configure. Space separated.")] + params ReprimandType[] types) + { + if (!types.Any()) return; - [Command("notify")] - [Summary("Notify user on a reprimand type.")] - public async Task ConfigureNotificationAsync( - [Summary("The type of reprimand to notify on. Leave blank to view current settings.")] - ReprimandNoticeType type = ReprimandNoticeType.None, - [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] - bool? notifyUser = null) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - if (type is not ReprimandNoticeType.None) - { - var notify = guild.LoggingRules.NotifyReprimands.SetValue(type, notifyUser); - guild.LoggingRules.NotifyReprimands = notify; - await _db.SaveChangesAsync(); - } + var guild = await _db.Guilds.FindAsync(Context.Guild.Id); + var rules = guild.ModerationRules.LoggingChannels; + await SetLoggingChannelAsync(channel, types, rules); - await ReplyAsync($"Current value: {guild.LoggingRules.NotifyReprimands.Humanize()}"); - } + if (channel is not null) + await ReplyAsync($"Set the log events {types.Humanize()} to be sent to {channel.Mention}."); + else + await ReplyAsync($"Disabled the log events {types.Humanize()}."); + } - private async Task SetLoggingChannelAsync( - IChannel? channel, IEnumerable types, - ICollection> collection) where T : Enum + [Command("notify")] + [Summary("Notify user on a reprimand type.")] + public async Task ConfigureNotificationAsync( + [Summary("The type of reprimand to notify on. Leave blank to view current settings.")] + ReprimandNoticeType type = ReprimandNoticeType.None, + [Summary("Set to 'true' or 'false'. Leave blank to toggle.")] + bool? notifyUser = null) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + if (type is not ReprimandNoticeType.None) { - _db.RemoveRange(collection.Where(rule => types.Contains(rule.Type))); + var notify = guild.LoggingRules.NotifyReprimands.SetValue(type, notifyUser); + guild.LoggingRules.NotifyReprimands = notify; + await _db.SaveChangesAsync(); + } - if (channel is not null) + await ReplyAsync($"Current value: {guild.LoggingRules.NotifyReprimands.Humanize()}"); + } + + private async Task SetLoggingChannelAsync( + IChannel? channel, IEnumerable types, + ICollection> collection) where T : Enum + { + _db.RemoveRange(collection.Where(rule => types.Contains(rule.Type))); + + if (channel is not null) + { + foreach (var type in types) { - foreach (var type in types) - { - collection.Add(new EnumChannel(type, channel)); - } + collection.Add(new EnumChannel(type, channel)); } - - await _db.SaveChangesAsync(); } + + await _db.SaveChangesAsync(); } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Moderation/ModerationModule.cs b/Zhongli.Bot/Modules/Moderation/ModerationModule.cs index c2b5d80..b9f88a5 100644 --- a/Zhongli.Bot/Modules/Moderation/ModerationModule.cs +++ b/Zhongli.Bot/Modules/Moderation/ModerationModule.cs @@ -1,5 +1,4 @@ using System; -using System.Text; using System.Linq; using System.Threading.Tasks; using Discord; @@ -17,292 +16,291 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Moderation +namespace Zhongli.Bot.Modules.Moderation; + +[Name("Moderation")] +[Summary("Guild moderation commands.")] +public class ModerationModule : InteractiveBase { - [Name("Moderation")] - [Summary("Guild moderation commands.")] - public class ModerationModule : InteractiveBase + private readonly CommandErrorHandler _error; + private readonly ModerationLoggingService _logging; + private readonly ModerationService _moderation; + private readonly ZhongliContext _db; + + public ModerationModule( + CommandErrorHandler error, + ZhongliContext db, + ModerationLoggingService logging, + ModerationService moderation) { - private readonly CommandErrorHandler _error; - private readonly ModerationLoggingService _logging; - private readonly ModerationService _moderation; - private readonly ZhongliContext _db; - - public ModerationModule( - CommandErrorHandler error, - ZhongliContext db, - ModerationLoggingService logging, - ModerationService moderation) - { - _error = error; - _db = db; + _error = error; + _db = db; - _logging = logging; - _moderation = moderation; - } + _logging = logging; + _moderation = moderation; + } - [Command("ban")] - [Summary("Ban a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Ban)] - public async Task BanAsync(IUser user, uint deleteDays = 0, TimeSpan? length = null, - [Remainder] string? reason = null) - { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.TryBanAsync(deleteDays, length, details); + [Command("ban")] + [Summary("Ban a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Ban)] + public async Task BanAsync(IUser user, uint deleteDays = 0, TimeSpan? length = null, + [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.TryBanAsync(deleteDays, length, details); - if (result is null) - await _error.AssociateError(Context.Message, "Failed to ban user."); - else - await ReplyReprimandAsync(result, details); - } + if (result is null) + await _error.AssociateError(Context.Message, "Failed to ban user."); + else + await ReplyReprimandAsync(result, details); + } - [Command("ban")] - [HiddenFromHelp] - [Summary("Ban a user permanently from the current guild.")] - [RequireAuthorization(AuthorizationScope.Ban)] - public Task BanAsync(IUser user, [Remainder] string? reason = null) - => BanAsync(user, 0, null, reason); - - [Command("ban")] - [HiddenFromHelp] - [Summary("Ban a user permanently from the current guild, and delete messages.")] - [RequireAuthorization(AuthorizationScope.Ban)] - public Task BanAsync(IUser user, uint deleteDays = 0, [Remainder] string? reason = null) - => BanAsync(user, deleteDays, null, reason); - - [Command("kick")] - [Summary("Kick a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Kick)] - public async Task KickAsync(IGuildUser user, [Remainder] string? reason = null) - { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.TryKickAsync(details); + [Command("ban")] + [HiddenFromHelp] + [Summary("Ban a user permanently from the current guild.")] + [RequireAuthorization(AuthorizationScope.Ban)] + public Task BanAsync(IUser user, [Remainder] string? reason = null) + => BanAsync(user, 0, null, reason); + + [Command("ban")] + [HiddenFromHelp] + [Summary("Ban a user permanently from the current guild, and delete messages.")] + [RequireAuthorization(AuthorizationScope.Ban)] + public Task BanAsync(IUser user, uint deleteDays = 0, [Remainder] string? reason = null) + => BanAsync(user, deleteDays, null, reason); + + [Command("kick")] + [Summary("Kick a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Kick)] + public async Task KickAsync(IGuildUser user, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.TryKickAsync(details); - if (result is null) - await _error.AssociateError(Context.Message, "Failed to kick user."); - else - await ReplyReprimandAsync(result, details); - } + if (result is null) + await _error.AssociateError(Context.Message, "Failed to kick user."); + else + await ReplyReprimandAsync(result, details); + } + + [Command("mute")] + [Summary("Mute a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Mute)] + public async Task MuteAsync(IGuildUser user, TimeSpan? length = null, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.TryMuteAsync(length, details); - [Command("mute")] - [Summary("Mute a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Mute)] - public async Task MuteAsync(IGuildUser user, TimeSpan? length = null, [Remainder] string? reason = null) + if (result is null) { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.TryMuteAsync(length, details); + await _error.AssociateError(Context.Message, "Failed to mute user. " + + "Either the user is already muted or there is no mute role configured. " + + "Configure the mute role by running the 'configure mute' command."); + } + else + await ReplyReprimandAsync(result, details); + } + + [Command("mute list")] + [Summary("View active mutes on the current guild.")] + [RequireAuthorization(AuthorizationScope.Moderator)] + public async Task MuteListAsync() + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var history = guild.ReprimandHistory + .Where(r => r.Status is not ReprimandStatus.Deleted && r.Status is not ReprimandStatus.Expired) + .OfType(InfractionType.Mute); - if (result is null) + var reprimands = new EmbedBuilder().Fields; + + var pages = history + .OrderByDescending(r => r.Action?.Date) + .Select(r => CreateEmbed(r)); + + var message = new PaginatedMessage + { + Title = "Currently Active Mutes", + Pages = reprimands.Concat(pages), + Options = new PaginatedAppearanceOptions { - await _error.AssociateError(Context.Message, "Failed to mute user. " + - "Either the user is already muted or there is no mute role configured. " + - "Configure the mute role by running the 'configure mute' command."); + FieldsPerPage = 10, + Timeout = TimeSpan.FromMinutes(10) } - else - await ReplyReprimandAsync(result, details); - } + }; - [Command("note")] - [Summary("Add a note to a user. Notes are always silent.")] - [RequireAuthorization(AuthorizationScope.Warning)] - public async Task NoteAsync(IGuildUser user, [Remainder] string? note = null) - { - var details = await GetDetailsAsync(user, note); - await _moderation.NoteAsync(details); + await PagedReplyAsync(message); + } - await Context.Message.DeleteAsync(); - } + [Command("note")] + [Summary("Add a note to a user. Notes are always silent.")] + [RequireAuthorization(AuthorizationScope.Warning)] + public async Task NoteAsync(IGuildUser user, [Remainder] string? note = null) + { + var details = await GetDetailsAsync(user, note); + await _moderation.NoteAsync(details); + + await Context.Message.DeleteAsync(); + } - [Command("notice")] - [Summary("Add a notice to a user. This counts as a minor warning.")] - [RequireAuthorization(AuthorizationScope.Warning)] - public async Task NoticeAsync(IGuildUser user, [Remainder] string? reason = null) + [Command("notice")] + [Summary("Add a notice to a user. This counts as a minor warning.")] + [RequireAuthorization(AuthorizationScope.Warning)] + public async Task NoticeAsync(IGuildUser user, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.NoticeAsync(details); + await ReplyReprimandAsync(result, details); + } + + [Command("say")] + [Summary("Make the bot send a message to the specified channel")] + [RequireAuthorization(AuthorizationScope.Helper)] + public async Task SayAsync(ITextChannel? channel, [Remainder] string message) + { + channel ??= (ITextChannel) Context.Channel; + await Context.Channel.DeleteMessageAsync(Context.Message.Id); + await channel.SendMessageAsync(message, allowedMentions: AllowedMentions.None); + } + + [Command("say")] + [HiddenFromHelp] + [Summary("Make the bot send a message to the specified channel")] + [RequireAuthorization(AuthorizationScope.Helper)] + public async Task SayAsync([Remainder] string message) + => SayAsync(null, message); + + [Command("slowmode")] + [Summary("Set a slowmode in the channel.")] + [RequireBotPermission(ChannelPermission.ManageChannels)] + [RequireAuthorization(AuthorizationScope.Helper)] + public async Task SlowmodeAsync(TimeSpan? length = null, ITextChannel? channel = null) + { + if (length is null && channel is null) { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.NoticeAsync(details); - await ReplyReprimandAsync(result, details); + var channels = Context.Guild.Channels.OfType() + .Where(c => c is not INewsChannel) + .Where(c => c.SlowModeInterval is not 0) + .OrderBy(c => c.Position); + + var embed = new EmbedBuilder() + .WithTitle("List of channels with slowmode active") + .AddItemsIntoFields("Channels", channels, + c => $"{c.Mention} => {c.SlowModeInterval.Seconds().Humanize()}") + .WithColor(Color.Green) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); } - - [Command("say")] - [Summary("Make the bot send a message to the specified channel")] - [RequireAuthorization(AuthorizationScope.Helper)] - public async Task SayAsync(ITextChannel? channel, [Remainder] string message) + else { + length ??= TimeSpan.Zero; channel ??= (ITextChannel) Context.Channel; - await Context.Channel.DeleteMessageAsync(Context.Message.Id); - await channel.SendMessageAsync(message, allowedMentions: AllowedMentions.None); - } + var seconds = (int) length.Value.TotalSeconds; + await channel.ModifyAsync(c => c.SlowModeInterval = seconds); - [Command("say")] - [HiddenFromHelp] - [Summary("Make the bot send a message to the specified channel")] - [RequireAuthorization(AuthorizationScope.Helper)] - public async Task SayAsync([Remainder] string message) - => SayAsync(null, message); - - [Command("slowmode")] - [Summary("Set a slowmode in the channel.")] - [RequireBotPermission(ChannelPermission.ManageChannels)] - [RequireAuthorization(AuthorizationScope.Helper)] - public async Task SlowmodeAsync(TimeSpan? length = null, ITextChannel? channel = null) - { - if (length is null && channel is null) + if (seconds is 0) + await ReplyAsync($"Slowmode disabled for {channel.Mention}"); + else { - var channels = Context.Guild.Channels.OfType() - .Where(c => c is not INewsChannel) - .Where(c => c.SlowModeInterval is not 0) - .OrderBy(c => c.Position); - var embed = new EmbedBuilder() - .WithTitle("List of channels with slowmode active") - .AddItemsIntoFields("Channels", channels, - c => $"{c.Mention} => {c.SlowModeInterval.Seconds().Humanize()}") + .WithTitle("Slowmode enabled") + .AddField("Channel", channel.Mention, true) + .AddField("Delay", length.Value.Humanize(3), true) .WithColor(Color.Green) .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); await ReplyAsync(embed: embed.Build()); } - else - { - length ??= TimeSpan.Zero; - channel ??= (ITextChannel) Context.Channel; - var seconds = (int) length.Value.TotalSeconds; - await channel.ModifyAsync(c => c.SlowModeInterval = seconds); - - if (seconds is 0) - await ReplyAsync($"Slowmode disabled for {channel.Mention}"); - else - { - var embed = new EmbedBuilder() - .WithTitle("Slowmode enabled") - .AddField("Channel", channel.Mention, true) - .AddField("Delay", length.Value.Humanize(3), true) - .WithColor(Color.Green) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - - await ReplyAsync(embed: embed.Build()); - } - } - } - - [Command("slowmode")] - [HiddenFromHelp] - [Summary("Set a slowmode in the channel.")] - [RequireBotPermission(ChannelPermission.ManageChannels)] - [RequireAuthorization(AuthorizationScope.Helper)] - public Task SlowmodeAsync(ITextChannel? channel = null, TimeSpan? length = null) - => SlowmodeAsync(length, channel); - - [Command("unban")] - [Summary("Unban a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Ban)] - public async Task UnbanAsync(IUser user, [Remainder] string? reason = null) - { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.TryUnbanAsync(details); - - if (result is not null) - await ReplyUpdatedReprimandAsync(result, details); - else - await _error.AssociateError(Context.Message, "This user has no ban logs. Forced unban."); } + } - [Command("unmute")] - [Summary("Unmute a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Mute)] - public async Task UnmuteAsync(IGuildUser user, [Remainder] string? reason = null) - { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.TryUnmuteAsync(details); - - if (result is not null) - await ReplyUpdatedReprimandAsync(result, details); - else - await _error.AssociateError(Context.Message, "Unmute failed."); - } + [Command("slowmode")] + [HiddenFromHelp] + [Summary("Set a slowmode in the channel.")] + [RequireBotPermission(ChannelPermission.ManageChannels)] + [RequireAuthorization(AuthorizationScope.Helper)] + public Task SlowmodeAsync(ITextChannel? channel = null, TimeSpan? length = null) + => SlowmodeAsync(length, channel); + + [Command("unban")] + [Summary("Unban a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Ban)] + public async Task UnbanAsync(IUser user, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.TryUnbanAsync(details); - [Command("warn")] - [Summary("Warn a user from the current guild.")] - [RequireAuthorization(AuthorizationScope.Warning)] - public async Task WarnAsync(IGuildUser user, uint amount = 1, [Remainder] string? reason = null) - { - var details = await GetDetailsAsync(user, reason); - var result = await _moderation.WarnAsync(amount, details); - await ReplyReprimandAsync(result, details); - } + if (result is not null) + await ReplyUpdatedReprimandAsync(result, details); + else + await _error.AssociateError(Context.Message, "This user has no ban logs. Forced unban."); + } - [Command("warn")] - [Summary("Warn a user from the current guild once.")] - [RequireAuthorization(AuthorizationScope.Warning)] - public Task WarnAsync(IGuildUser user, [Remainder] string? reason = null) - => WarnAsync(user, 1, reason); + [Command("unmute")] + [Summary("Unmute a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Mute)] + public async Task UnmuteAsync(IGuildUser user, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.TryUnmuteAsync(details); - [Command("mute list")] - [Summary("View active mutes on the current guild.")] - [RequireAuthorization(AuthorizationScope.Moderator)] - public async Task MuteListAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var history = guild.ReprimandHistory - .Where(r => r.Status is not ReprimandStatus.Deleted && r.Status is not ReprimandStatus.Expired) - .OfType(InfractionType.Mute); + if (result is not null) + await ReplyUpdatedReprimandAsync(result, details); + else + await _error.AssociateError(Context.Message, "Unmute failed."); + } - var reprimands = new EmbedBuilder().Fields; + [Command("warn")] + [Summary("Warn a user from the current guild.")] + [RequireAuthorization(AuthorizationScope.Warning)] + public async Task WarnAsync(IGuildUser user, uint amount = 1, [Remainder] string? reason = null) + { + var details = await GetDetailsAsync(user, reason); + var result = await _moderation.WarnAsync(amount, details); + await ReplyReprimandAsync(result, details); + } - var pages = history - .OrderByDescending(r => r.Action?.Date) - .Select(r => CreateEmbed(r)); + [Command("warn")] + [Summary("Warn a user from the current guild once.")] + [RequireAuthorization(AuthorizationScope.Warning)] + public Task WarnAsync(IGuildUser user, [Remainder] string? reason = null) + => WarnAsync(user, 1, reason); - var message = new PaginatedMessage - { - Title = "Currently Active Mutes", - Pages = reprimands.Concat(pages), - Options = new PaginatedAppearanceOptions - { - FieldsPerPage = 10, - Timeout = TimeSpan.FromMinutes(10) - } - }; - - await PagedReplyAsync(message); - } + private static EmbedFieldBuilder CreateEmbed(Reprimand r) => new EmbedFieldBuilder() + .WithName(r.GetTitle()) + .WithValue(r.GetReprimandExpiration()); - private async Task ReplyReprimandAsync(ReprimandResult result, ReprimandDetails details) + private async Task ReplyReprimandAsync(ReprimandResult result, ReprimandDetails details) + { + var guild = await result.Primary.GetGuildAsync(_db); + if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) { - var guild = await result.Primary.GetGuildAsync(_db); - if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) - { - var embed = await _logging.CreateEmbedAsync(result, details); - await ReplyAsync(embed: embed.Build()); - } - else - await Context.Message.DeleteAsync(); + var embed = await _logging.CreateEmbedAsync(result, details); + await ReplyAsync(embed: embed.Build()); } + else + await Context.Message.DeleteAsync(); + } - private async Task ReplyUpdatedReprimandAsync(Reprimand reprimand, ReprimandDetails details) + private async Task ReplyUpdatedReprimandAsync(Reprimand reprimand, ReprimandDetails details) + { + var guild = await reprimand.GetGuildAsync(_db); + if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) { - var guild = await reprimand.GetGuildAsync(_db); - if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) - { - var embed = await _logging.UpdatedEmbedAsync(reprimand, details); - await ReplyAsync(embed: embed.Build()); - } - else - await Context.Message.DeleteAsync(); + var embed = await _logging.UpdatedEmbedAsync(reprimand, details); + await ReplyAsync(embed: embed.Build()); } + else + await Context.Message.DeleteAsync(); + } - private async Task GetDetailsAsync(IUser user, string? reason) - { - var details = new ReprimandDetails(user, (IGuildUser) Context.User, reason); - - await _db.Users.TrackUserAsync(details); - await _db.SaveChangesAsync(); + private async Task GetDetailsAsync(IUser user, string? reason) + { + var details = new ReprimandDetails(user, (IGuildUser) Context.User, reason); - return details; - } + await _db.Users.TrackUserAsync(details); + await _db.SaveChangesAsync(); - private static EmbedFieldBuilder CreateEmbed(Reprimand r) => new EmbedFieldBuilder() - .WithName(r.GetTitle()) - .WithValue(r.GetReprimandExpiration()); + return details; } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Moderation/ModifyReprimandsModule.cs b/Zhongli.Bot/Modules/Moderation/ModifyReprimandsModule.cs index 50d624e..0c8212c 100644 --- a/Zhongli.Bot/Modules/Moderation/ModifyReprimandsModule.cs +++ b/Zhongli.Bot/Modules/Moderation/ModifyReprimandsModule.cs @@ -15,112 +15,108 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Moderation +namespace Zhongli.Bot.Modules.Moderation; + +[Name("Reprimand Modification")] +[Summary("Modification of reprimands. Provide a partial ID with at least the 2 starting characters.")] +[RequireAuthorization(AuthorizationScope.Moderator)] +public class ModifyReprimandsModule : InteractiveEntity { - [Name("Reprimand Modification")] - [Summary("Modification of reprimands. Provide a partial ID with at least the 2 starting characters.")] - [RequireAuthorization(AuthorizationScope.Moderator)] - public class ModifyReprimandsModule : InteractiveEntity + private readonly CommandErrorHandler _error; + private readonly ModerationLoggingService _logging; + private readonly ModerationService _moderation; + private readonly ZhongliContext _db; + + public ModifyReprimandsModule( + CommandErrorHandler error, ZhongliContext db, + ModerationLoggingService logging, ModerationService moderation) + : base(error, db) { - private readonly CommandErrorHandler _error; - private readonly ModerationLoggingService _logging; - private readonly ModerationService _moderation; - private readonly ZhongliContext _db; - - public ModifyReprimandsModule( - CommandErrorHandler error, ZhongliContext db, - ModerationLoggingService logging, ModerationService moderation) - : base(error, db) - { - _error = error; - _db = db; + _error = error; + _db = db; - _logging = logging; - _moderation = moderation; - } + _logging = logging; + _moderation = moderation; + } - [Command("hide")] - [Alias("pardon")] - [Summary("Hide a reprimand, this would mean they are not counted towards triggers.")] - public async Task HideReprimandAsync(string id, [Remainder] string? reason = null) - { - var reprimand = await TryFindEntityAsync(id); - await ModifyReprimandAsync(reprimand, _moderation.HideReprimandAsync, reason); - } + [Command("hide")] + [Alias("pardon")] + [Summary("Hide a reprimand, this would mean they are not counted towards triggers.")] + public async Task HideReprimandAsync(string id, [Remainder] string? reason = null) + { + var reprimand = await TryFindEntityAsync(id); + await ModifyReprimandAsync(reprimand, _moderation.HideReprimandAsync, reason); + } - [Command("update")] - [Summary("Update a reprimand's reason.")] - public async Task UpdateReprimandAsync(string id, [Remainder] string? reason = null) - { - var reprimand = await TryFindEntityAsync(id); - await ModifyReprimandAsync(reprimand, _moderation.UpdateReprimandAsync, reason); - } + [Command("update")] + [Summary("Update a reprimand's reason.")] + public async Task UpdateReprimandAsync(string id, [Remainder] string? reason = null) + { + var reprimand = await TryFindEntityAsync(id); + await ModifyReprimandAsync(reprimand, _moderation.UpdateReprimandAsync, reason); + } - [Command("remove")] - [Alias("delete", "purgewarn")] - [Summary("Delete a reprimand, this completely removes the data.")] - protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); - - [Command("reprimand history")] - [Alias("warnlist all")] - [Summary("Views the entire reprimand history of the server.")] - protected async Task ViewEntityAsync( - [Summary("Leave empty to show everything.")] - InfractionType type = InfractionType.All) - { - var collection = await GetCollectionAsync(); - await PagedViewAsync(collection.OfType(type)); - } + [Command("remove")] + [Alias("delete", "purgewarn")] + [Summary("Delete a reprimand, this completely removes the data.")] + protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); + + [Command("reprimand history")] + [Alias("warnlist all")] + [Summary("Views the entire reprimand history of the server.")] + protected async Task ViewEntityAsync( + [Summary("Leave empty to show everything.")] + InfractionType type = InfractionType.All) + { + var collection = await GetCollectionAsync(); + await PagedViewAsync(collection.OfType(type)); + } - protected override (string Title, StringBuilder Value) EntityViewer(Reprimand r) - => (r.GetTitle(), r.GetReprimandDetails()); + protected override (string Title, StringBuilder Value) EntityViewer(Reprimand r) + => (r.GetTitle(), r.GetReprimandDetails()); - protected override bool IsMatch(Reprimand entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + protected override bool IsMatch(Reprimand entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); - protected override async Task RemoveEntityAsync(Reprimand entity) - { - await ModifyReprimandAsync(entity, _moderation.DeleteReprimandAsync); - } + protected override async Task RemoveEntityAsync(Reprimand entity) { await ModifyReprimandAsync(entity, _moderation.DeleteReprimandAsync); } - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - return guild.ReprimandHistory; - } + protected override async Task> GetCollectionAsync() + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + return guild.ReprimandHistory; + } - private ReprimandDetails GetDetails(IUser user, string? reason) - => new(user, (IGuildUser) Context.User, reason); + private ReprimandDetails GetDetails(IUser user, string? reason) + => new(user, (IGuildUser) Context.User, reason); - private async Task ModifyReprimandAsync(Reprimand? reprimand, - UpdateReprimandDelegate update, string? reason = null) + private async Task ModifyReprimandAsync(Reprimand? reprimand, + UpdateReprimandDelegate update, string? reason = null) + { + if (reprimand is null || reprimand.GuildId != Context.Guild.Id) { - if (reprimand is null || reprimand.GuildId != Context.Guild.Id) - { - await _error.AssociateError(Context.Message, EmptyMatchMessage); - return; - } + await _error.AssociateError(Context.Message, EmptyMatchMessage); + return; + } - var user = Context.Client.GetUser(reprimand.UserId); - var details = GetDetails(user, reason); + var user = Context.Client.GetUser(reprimand.UserId); + var details = GetDetails(user, reason); - await update(reprimand, details); - await ReplyReprimandAsync(reprimand, details); - } + await update(reprimand, details); + await ReplyReprimandAsync(reprimand, details); + } - private async Task ReplyReprimandAsync(Reprimand reprimand, ReprimandDetails details) + private async Task ReplyReprimandAsync(Reprimand reprimand, ReprimandDetails details) + { + var guild = await reprimand.GetGuildAsync(_db); + if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) { - var guild = await reprimand.GetGuildAsync(_db); - if (!guild.ModerationRules.Options.HasFlag(ReprimandOptions.Silent)) - { - var embed = await _logging.UpdatedEmbedAsync(reprimand, details); - await ReplyAsync(embed: embed.Build()); - } - else - await Context.Message.DeleteAsync(); + var embed = await _logging.UpdatedEmbedAsync(reprimand, details); + await ReplyAsync(embed: embed.Build()); } - - private delegate Task UpdateReprimandDelegate(Reprimand reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default); + else + await Context.Message.DeleteAsync(); } + + private delegate Task UpdateReprimandDelegate(Reprimand reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Moderation/PermissionsModule.cs b/Zhongli.Bot/Modules/Moderation/PermissionsModule.cs index 6cfca9a..cc31bc8 100644 --- a/Zhongli.Bot/Modules/Moderation/PermissionsModule.cs +++ b/Zhongli.Bot/Modules/Moderation/PermissionsModule.cs @@ -23,210 +23,209 @@ using Zhongli.Services.Utilities; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Bot.Modules.Moderation +namespace Zhongli.Bot.Modules.Moderation; + +[Group("permissions")] +[Name("Permissions")] +[Alias("perms", "perm")] +[Summary("Manages guild permissions.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class PermissionsModule : InteractiveEntity { - [Group("permissions")] - [Name("Permissions")] - [Alias("perms", "perm")] - [Summary("Manages guild permissions.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class PermissionsModule : InteractiveEntity + private readonly AuthorizationService _auth; + private readonly CommandErrorHandler _error; + private readonly ZhongliContext _db; + + public PermissionsModule(AuthorizationService auth, CommandErrorHandler error, ZhongliContext db) : base(error, + db) { - private readonly AuthorizationService _auth; - private readonly CommandErrorHandler _error; - private readonly ZhongliContext _db; + _auth = auth; + _error = error; + _db = db; + } - public PermissionsModule(AuthorizationService auth, CommandErrorHandler error, ZhongliContext db) : base(error, - db) + [Command("add")] + [Summary( + "Add a permission for a specific scope. At least one rule option must be filled. " + + "Filling multiple options make an Authorization Group. " + + "An Authorization Group must have all pass before the permission is allowed.")] + public async Task AddPermissionAsync(AuthorizationScope scope, RuleOptions options) + { + var rules = options.ToCriteria(); + if (rules.Count == 0) { - _auth = auth; - _error = error; - _db = db; + await _error.AssociateError(Context.Message, "You must provide at least one restricting permission."); + return; } - [Command("add")] - [Summary( - "Add a permission for a specific scope. At least one rule option must be filled. " + - "Filling multiple options make an Authorization Group. " + - "An Authorization Group must have all pass before the permission is allowed.")] - public async Task AddPermissionAsync(AuthorizationScope scope, RuleOptions options) - { - var rules = options.ToCriteria(); - if (rules.Count == 0) - { - await _error.AssociateError(Context.Message, "You must provide at least one restricting permission."); - return; - } + var moderator = (IGuildUser) Context.User; + var group = new AuthorizationGroup(scope, options.AccessType, rules).WithModerator(moderator); - var moderator = (IGuildUser) Context.User; - var group = new AuthorizationGroup(scope, options.AccessType, rules).WithModerator(moderator); - - await AddEntityAsync(group); - } + await AddEntityAsync(group); + } - [Command("configure")] - [Alias("config")] - [Summary("Interactively configure the permissions. This uses a template of having an admin and mod role.")] - public async Task InteractiveConfigureAsync() + [Command("configure")] + [Alias("config")] + [Summary("Interactively configure the permissions. This uses a template of having an admin and mod role.")] + public async Task InteractiveConfigureAsync() + { + var fields = new List(); + foreach (var field in Enum.GetValues()) { - var fields = new List(); - foreach (var field in Enum.GetValues()) - { - var description = field.GetAttributeOfEnum(); - if (!string.IsNullOrWhiteSpace(description?.Description)) - fields.Add(CreateField(field.ToString(), description.Description)); - } - - static EmbedFieldBuilder CreateField(string name, string value) - => new EmbedFieldBuilder().WithName(name).WithValue(value); - - var prompts = CreatePromptCollection() - .WithPrompt(ConfigureOptions.Admin, - "Please enter the role name, ID, or mention the role that will be the admin.") - .ThatHas(new RoleTypeReader()) - .WithPrompt(ConfigureOptions.Moderator, - "Please enter the role name, ID, or mention the role that will be the moderator.") - .ThatHas(new RoleTypeReader()) - .WithPrompt(ConfigureOptions.Permissions, - "What kind of permissions would you like moderators to have? Separate with spaces.", - fields) - .ThatHas(new EnumFlagsTypeReader()); - - var results = await prompts.GetAnswersAsync(); - - var moderator = (IGuildUser) Context.User; - _ = await _db.Users.TrackUserAsync(moderator); - var guild = await _auth.AutoConfigureGuild(Context.Guild); - - guild.AuthorizationGroups.AddRules(AuthorizationScope.All, moderator, AccessType.Allow, - new RoleCriterion(results.Get(ConfigureOptions.Admin))); - - guild.AuthorizationGroups.AddRules(results.Get(ConfigureOptions.Permissions), - moderator, AccessType.Allow, - new RoleCriterion(results.Get(ConfigureOptions.Moderator))); - - _db.Update(guild); - await _db.SaveChangesAsync(); - - await Context.Message.AddReactionAsync(new Emoji("✅")); + var description = field.GetAttributeOfEnum(); + if (!string.IsNullOrWhiteSpace(description?.Description)) + fields.Add(CreateField(field.ToString(), description.Description)); } - [Command] - [Alias("view", "list")] - [Summary("View the configured authorization groups.")] - public async Task ViewPermissionsAsync() - { - var collection = await GetCollectionAsync(); - await PagedViewAsync(collection); - } + static EmbedFieldBuilder CreateField(string name, string value) + => new EmbedFieldBuilder().WithName(name).WithValue(value); - [Command("remove")] - [Alias("delete", "del")] - [Summary("Remove an authorization group.")] - protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); + var prompts = CreatePromptCollection() + .WithPrompt(ConfigureOptions.Admin, + "Please enter the role name, ID, or mention the role that will be the admin.") + .ThatHas(new RoleTypeReader()) + .WithPrompt(ConfigureOptions.Moderator, + "Please enter the role name, ID, or mention the role that will be the moderator.") + .ThatHas(new RoleTypeReader()) + .WithPrompt(ConfigureOptions.Permissions, + "What kind of permissions would you like moderators to have? Separate with spaces.", + fields) + .ThatHas(new EnumFlagsTypeReader()); - protected override (string Title, StringBuilder Value) EntityViewer(AuthorizationGroup entity) - { - var details = GetAuthorizationGroupDetails(entity); - return (entity.Id.ToString(), details); - } + var results = await prompts.GetAnswersAsync(); - protected override bool IsMatch(AuthorizationGroup entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + var moderator = (IGuildUser) Context.User; + _ = await _db.Users.TrackUserAsync(moderator); + var guild = await _auth.AutoConfigureGuild(Context.Guild); - protected override async Task RemoveEntityAsync(AuthorizationGroup censor) - { - _db.RemoveRange(censor.Collection); - _db.Remove(censor.Action); - _db.Remove(censor); + guild.AuthorizationGroups.AddRules(AuthorizationScope.All, moderator, AccessType.Allow, + new RoleCriterion(results.Get(ConfigureOptions.Admin))); - await _db.SaveChangesAsync(); - } + guild.AuthorizationGroups.AddRules(results.Get(ConfigureOptions.Permissions), + moderator, AccessType.Allow, + new RoleCriterion(results.Get(ConfigureOptions.Moderator))); - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - return guild.AuthorizationGroups; - } + _db.Update(guild); + await _db.SaveChangesAsync(); + + await Context.Message.AddReactionAsync(new Emoji("✅")); + } + + [Command] + [Alias("view", "list")] + [Summary("View the configured authorization groups.")] + public async Task ViewPermissionsAsync() + { + var collection = await GetCollectionAsync(); + await PagedViewAsync(collection); + } + + [Command("remove")] + [Alias("delete", "del")] + [Summary("Remove an authorization group.")] + protected override Task RemoveEntityAsync(string id) => base.RemoveEntityAsync(id); + + protected override (string Title, StringBuilder Value) EntityViewer(AuthorizationGroup entity) + { + var details = GetAuthorizationGroupDetails(entity); + return (entity.Id.ToString(), details); + } - private static StringBuilder GetAuthorizationGroupDetails(AuthorizationGroup group) + protected override bool IsMatch(AuthorizationGroup entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + + protected override async Task RemoveEntityAsync(AuthorizationGroup censor) + { + _db.RemoveRange(censor.Collection); + _db.Remove(censor.Action); + _db.Remove(censor); + + await _db.SaveChangesAsync(); + } + + protected override async Task> GetCollectionAsync() + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + return guild.AuthorizationGroups; + } + + private static StringBuilder GetAuthorizationGroupDetails(AuthorizationGroup group) + { + var sb = new StringBuilder() + .AppendLine($"▌Scope: {Format.Bold(group.Scope.Humanize())}") + .AppendLine($"▌Type: {Format.Bold(group.Access.Humanize())}") + .AppendLine($"▌Moderator: {group.GetModerator()}") + .AppendLine($"▌Date: {group.GetDate()}"); + + foreach (var rules in group.Collection.ToLookup(g => g.GetCriterionType())) { - var sb = new StringBuilder() - .AppendLine($"▌Scope: {Format.Bold(group.Scope.Humanize())}") - .AppendLine($"▌Type: {Format.Bold(group.Access.Humanize())}") - .AppendLine($"▌Moderator: {group.GetModerator()}") - .AppendLine($"▌Date: {group.GetDate()}"); - - foreach (var rules in group.Collection.ToLookup(g => g.GetCriterionType())) - { - string GetGroupingName() => rules.Key.Name - .Replace(nameof(Criterion), string.Empty) - .Pluralize().Humanize(LetterCasing.Title); - - sb.AppendLine($"▉ {GetGroupingName()}: {rules.Humanize()}"); - } - - return sb; + string GetGroupingName() => rules.Key.Name + .Replace(nameof(Criterion), string.Empty) + .Pluralize().Humanize(LetterCasing.Title); + + sb.AppendLine($"▉ {GetGroupingName()}: {rules.Humanize()}"); } - private async Task TryFindAuthorizationGroup(string id, - CancellationToken cancellationToken = default) - { - if (id.Length < 2) - return null; + return sb; + } - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild, cancellationToken); - var group = await guild.AuthorizationGroups.ToAsyncEnumerable() - .Where(r => r.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase)) - .ToListAsync(cancellationToken); + private async Task TryFindAuthorizationGroup(string id, + CancellationToken cancellationToken = default) + { + if (id.Length < 2) + return null; - if (group.Count <= 1) - return group.Count == 1 ? group.First() : null; + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild, cancellationToken); + var group = await guild.AuthorizationGroups.ToAsyncEnumerable() + .Where(r => r.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase)) + .ToListAsync(cancellationToken); - var lines = group.Select(r - => new StringBuilder() - .AppendLine($"{r.Id}") - .Append(GetAuthorizationGroupDetails(r)) - .ToString()); + if (group.Count <= 1) + return group.Count == 1 ? group.First() : null; - var embed = new EmbedBuilder() - .WithTitle("Multiple authorization groups found. Reply with the number of the group that you want.") - .AddItemsIntoFields("Reprimands", lines); + var lines = group.Select(r + => new StringBuilder() + .AppendLine($"{r.Id}") + .Append(GetAuthorizationGroupDetails(r)) + .ToString()); - await ReplyAsync(embed: embed.Build()); + var embed = new EmbedBuilder() + .WithTitle("Multiple authorization groups found. Reply with the number of the group that you want.") + .AddItemsIntoFields("Reprimands", lines); - var containsCriterion = new FuncCriterion(m => - int.TryParse(m.Content, out var selection) - && selection < group.Count && selection > -1); + await ReplyAsync(embed: embed.Build()); - var selected = await NextMessageAsync(containsCriterion, token: cancellationToken); - return selected is null ? null : group.ElementAtOrDefault(int.Parse(selected.Content)); - } + var containsCriterion = new FuncCriterion(m => + int.TryParse(m.Content, out var selection) + && selection < group.Count && selection > -1); - [NamedArgumentType] - public class RuleOptions : ICriteriaOptions - { - [HelpSummary("Set 'allow' or 'deny' the matched criteria. Defaults to allow.")] - public AccessType AccessType { get; set; } = AccessType.Allow; + var selected = await NextMessageAsync(containsCriterion, token: cancellationToken); + return selected is null ? null : group.ElementAtOrDefault(int.Parse(selected.Content)); + } + + [NamedArgumentType] + public class RuleOptions : ICriteriaOptions + { + [HelpSummary("Set 'allow' or 'deny' the matched criteria. Defaults to allow.")] + public AccessType AccessType { get; set; } = AccessType.Allow; - [HelpSummary("The permissions that the user must have.")] - public GuildPermission Permission { get; set; } + [HelpSummary("The permissions that the user must have.")] + public GuildPermission Permission { get; set; } - [HelpSummary("The text or category channels this permission will work on.")] - public IEnumerable? Channels { get; set; } + [HelpSummary("The text or category channels this permission will work on.")] + public IEnumerable? Channels { get; set; } - [HelpSummary("The users that are allowed to use the command.")] - public IEnumerable? Users { get; set; } + [HelpSummary("The users that are allowed to use the command.")] + public IEnumerable? Users { get; set; } - [HelpSummary("The roles that the user must have.")] - public IEnumerable? Roles { get; set; } - } + [HelpSummary("The roles that the user must have.")] + public IEnumerable? Roles { get; set; } + } - private enum ConfigureOptions - { - Admin, - Moderator, - Permissions - } + private enum ConfigureOptions + { + Admin, + Moderator, + Permissions } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/Moderation/PurgeModule.cs b/Zhongli.Bot/Modules/Moderation/PurgeModule.cs index 27db9e6..44b40e8 100644 --- a/Zhongli.Bot/Modules/Moderation/PurgeModule.cs +++ b/Zhongli.Bot/Modules/Moderation/PurgeModule.cs @@ -9,164 +9,163 @@ using Zhongli.Services.Utilities; using static Zhongli.Bot.Modules.Moderation.PurgeModule.PurgeFilters; -namespace Zhongli.Bot.Modules.Moderation +namespace Zhongli.Bot.Modules.Moderation; + +public class PurgeModule : ModuleBase { - public class PurgeModule : ModuleBase + [Command("purge")] + [Alias("clean")] + [Summary("Purges messages based on set rules.")] + [RequireUserPermission(ChannelPermission.ManageMessages)] + [RequireBotPermission(ChannelPermission.ManageMessages)] + public async Task PurgeAsync(int amount, PurgeFilters? options = null) { - [Command("purge")] - [Alias("clean")] - [Summary("Purges messages based on set rules.")] - [RequireUserPermission(ChannelPermission.ManageMessages)] - [RequireBotPermission(ChannelPermission.ManageMessages)] - public async Task PurgeAsync(int amount, PurgeFilters? options = null) + var messages = await Context.Channel + .GetMessagesAsync(amount) + .Flatten().ToListAsync(); + + var channel = (ITextChannel) Context.Channel; + if (options is null) + await channel.DeleteMessagesAsync(messages); + else { - var messages = await Context.Channel - .GetMessagesAsync(amount) - .Flatten().ToListAsync(); - - var channel = (ITextChannel) Context.Channel; - if (options is null) - await channel.DeleteMessagesAsync(messages); - else + var rules = options.GetRules(); + var result = options.FilterMode switch { - var rules = options.GetRules(); - var result = options.FilterMode switch - { - FilterType.All => messages.Where(m => rules.All(rule => rule(m))), - FilterType.Any or _ => messages.Where(m => rules.Any(rule => rule(m))) - }; - - if (options.Invert ?? false) - result = messages.Except(result); + FilterType.All => messages.Where(m => rules.All(rule => rule(m))), + FilterType.Any or _ => messages.Where(m => rules.Any(rule => rule(m))) + }; - var deleted = result.ToList(); - await channel.DeleteMessagesAsync(deleted); - amount = deleted.Count; - } + if (options.Invert ?? false) + result = messages.Except(result); - await ReplyAsync($"Deleted {amount} messages."); + var deleted = result.ToList(); + await channel.DeleteMessagesAsync(deleted); + amount = deleted.Count; } - [NamedArgumentType] - public class PurgeFilters + await ReplyAsync($"Deleted {amount} messages."); + } + + [NamedArgumentType] + public class PurgeFilters + { + public enum FilterType { - public enum FilterType - { - [HelpSummary("Match any of the rules.")] - Any, + [HelpSummary("Match any of the rules.")] + Any, - [HelpSummary("Match all of the rules.")] - All - } + [HelpSummary("Match all of the rules.")] + All + } - [HelpSummary("Include messages with attachments.")] - public bool? HasAttachments { get; set; } + [HelpSummary("Include messages with attachments.")] + public bool? HasAttachments { get; set; } - [HelpSummary("Include message with embeds.")] - public bool? HasEmbeds { get; set; } + [HelpSummary("Include message with embeds.")] + public bool? HasEmbeds { get; set; } - [HelpSummary("Any roles that are mentioned in the message.")] - public bool? HasInvites { get; set; } + [HelpSummary("Any roles that are mentioned in the message.")] + public bool? HasInvites { get; set; } - [HelpSummary("Invert the results.")] public bool? Invert { get; set; } + [HelpSummary("Invert the results.")] public bool? Invert { get; set; } - [HelpSummary("Include messages with bots.")] - public bool? IsBot { get; set; } + [HelpSummary("Include messages with bots.")] + public bool? IsBot { get; set; } - [HelpSummary("Defaults to 'Any'.")] public FilterType FilterMode { get; set; } + [HelpSummary("Defaults to 'Any'.")] public FilterType FilterMode { get; set; } - [HelpSummary("Include messages that mention these roles.")] - public IEnumerable? MentionedRoles { get; set; } + [HelpSummary("Include messages that mention these roles.")] + public IEnumerable? MentionedRoles { get; set; } - [HelpSummary("Include message authors that contain these roles.")] - public IEnumerable? Roles { get; set; } + [HelpSummary("Include message authors that contain these roles.")] + public IEnumerable? Roles { get; set; } - [HelpSummary("Include messages that mention these users.")] - public IEnumerable? MentionedUsers { get; set; } + [HelpSummary("Include messages that mention these users.")] + public IEnumerable? MentionedUsers { get; set; } - [HelpSummary("Include messages that contain these users.")] - public IEnumerable? Users { get; set; } + [HelpSummary("Include messages that contain these users.")] + public IEnumerable? Users { get; set; } - [HelpSummary("Include messages that contain this string.")] - public string? Contains { get; set; } + [HelpSummary("Include messages that contain this string.")] + public string? Contains { get; set; } - [HelpSummary("Include messages that ends with this string.")] - public string? EndsWith { get; set; } + [HelpSummary("Include messages that ends with this string.")] + public string? EndsWith { get; set; } - [HelpSummary("Include messages that match this regex pattern. Ignores case.")] - public string? RegexPattern { get; set; } + [HelpSummary("Include messages that match this regex pattern. Ignores case.")] + public string? RegexPattern { get; set; } - [HelpSummary("Include messages that start with this string.")] - public string? StartsWith { get; set; } + [HelpSummary("Include messages that start with this string.")] + public string? StartsWith { get; set; } - public IEnumerable> GetRules() + public IEnumerable> GetRules() + { + if (HasAttachments is not null) { - if (HasAttachments is not null) + yield return m => { - yield return m => - { - var hasAttachments = m.Attachments.Any(); - return HasAttachments.Value == hasAttachments; - }; - } - - if (HasEmbeds is not null) + var hasAttachments = m.Attachments.Any(); + return HasAttachments.Value == hasAttachments; + }; + } + + if (HasEmbeds is not null) + { + yield return m => { - yield return m => - { - var hasEmbeds = m.Embeds.Any(); - return HasEmbeds.Value == hasEmbeds; - }; - } - - if (HasInvites is not null) + var hasEmbeds = m.Embeds.Any(); + return HasEmbeds.Value == hasEmbeds; + }; + } + + if (HasInvites is not null) + { + yield return m => { - yield return m => - { - var match = RegexUtilities.DiscordInvite.IsMatch(m.Content); - return HasInvites.Value == match; - }; - } - - if (IsBot is not null) + var match = RegexUtilities.DiscordInvite.IsMatch(m.Content); + return HasInvites.Value == match; + }; + } + + if (IsBot is not null) + { + yield return m => { - yield return m => - { - var isBot = m.Author.IsBot || m.Author.IsWebhook; - return IsBot.Value == isBot; - }; - } + var isBot = m.Author.IsBot || m.Author.IsWebhook; + return IsBot.Value == isBot; + }; + } - if (MentionedRoles is not null) - yield return m => m.MentionedRoleIds.Intersect(MentionedRoles.Select(r => r.Id)).Any(); + if (MentionedRoles is not null) + yield return m => m.MentionedRoleIds.Intersect(MentionedRoles.Select(r => r.Id)).Any(); - if (Roles is not null) - { - yield return m => - m.Author is IGuildUser user - && Roles.Select(r => r.Id).Intersect(user.RoleIds).Any(); - } + if (Roles is not null) + { + yield return m => + m.Author is IGuildUser user + && Roles.Select(r => r.Id).Intersect(user.RoleIds).Any(); + } - if (MentionedUsers is not null) - yield return m => m.MentionedUserIds.Intersect(MentionedUsers.Select(r => r.Id)).Any(); + if (MentionedUsers is not null) + yield return m => m.MentionedUserIds.Intersect(MentionedUsers.Select(r => r.Id)).Any(); - if (Users is not null) - yield return m => Users.Any(u => u.Id == m.Author.Id); + if (Users is not null) + yield return m => Users.Any(u => u.Id == m.Author.Id); - if (Contains is not null) - yield return m => m.Content.Contains(Contains); + if (Contains is not null) + yield return m => m.Content.Contains(Contains); - if (EndsWith is not null) - yield return m => m.Content.EndsWith(EndsWith); + if (EndsWith is not null) + yield return m => m.Content.EndsWith(EndsWith); - if (StartsWith is not null) - yield return m => m.Content.StartsWith(StartsWith); + if (StartsWith is not null) + yield return m => m.Content.StartsWith(StartsWith); - if (RegexPattern is not null) - { - yield return m => Regex.IsMatch(m.Content, RegexPattern, - RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); - } + if (RegexPattern is not null) + { + yield return m => Regex.IsMatch(m.Content, RegexPattern, + RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); } } } diff --git a/Zhongli.Bot/Modules/Moderation/ReprimandTriggersModule.cs b/Zhongli.Bot/Modules/Moderation/ReprimandTriggersModule.cs index a1c2dfe..3f47863 100644 --- a/Zhongli.Bot/Modules/Moderation/ReprimandTriggersModule.cs +++ b/Zhongli.Bot/Modules/Moderation/ReprimandTriggersModule.cs @@ -17,177 +17,176 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules.Moderation +namespace Zhongli.Bot.Modules.Moderation; + +[Group("trigger")] +[Alias("triggers")] +[Summary("Reprimand triggers.")] +[RequireAuthorization(AuthorizationScope.Configuration)] +public class ReprimandTriggersModule : InteractiveTrigger { - [Group("trigger")] - [Alias("triggers")] - [Summary("Reprimand triggers.")] - [RequireAuthorization(AuthorizationScope.Configuration)] - public class ReprimandTriggersModule : InteractiveTrigger + private readonly CommandErrorHandler _error; + private readonly ZhongliContext _db; + + public ReprimandTriggersModule(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) + : base(error, db, moderation) { - private readonly CommandErrorHandler _error; - private readonly ZhongliContext _db; + _error = error; + _db = db; + } - public ReprimandTriggersModule(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) - : base(error, db, moderation) - { - _error = error; - _db = db; - } + [Command("ban")] + public async Task BanTriggerAsync(uint amount, TriggerSource source, + uint deleteDays = 0, TimeSpan? length = null, + TriggerMode mode = TriggerMode.Exact) + { + var action = new BanAction(deleteDays, length); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("ban")] - public async Task BanTriggerAsync(uint amount, TriggerSource source, - uint deleteDays = 0, TimeSpan? length = null, - TriggerMode mode = TriggerMode.Exact) - { - var action = new BanAction(deleteDays, length); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("kick")] + public async Task KickTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) + { + var action = new KickAction(); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("kick")] - public async Task KickTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) - { - var action = new KickAction(); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("mute")] + public async Task MuteTriggerAsync(uint amount, TriggerSource source, TimeSpan? length = null, + TriggerMode mode = TriggerMode.Exact) + { + var action = new MuteAction(length); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("mute")] - public async Task MuteTriggerAsync(uint amount, TriggerSource source, TimeSpan? length = null, - TriggerMode mode = TriggerMode.Exact) - { - var action = new MuteAction(length); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("note")] + public async Task NoteTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) + { + var action = new NoteAction(); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("note")] - public async Task NoteTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) - { - var action = new NoteAction(); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("notice")] + public async Task NoticeTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) + { + var action = new NoticeAction(); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("notice")] - public async Task NoticeTriggerAsync(uint amount, TriggerSource source, TriggerMode mode = TriggerMode.Exact) - { - var action = new NoticeAction(); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("warn")] + public async Task WarnTriggerAsync(uint amount, TriggerSource source, + uint count = 1, TriggerMode mode = TriggerMode.Exact) + { + var action = new WarningAction(count); + await TryAddTriggerAsync(action, amount, source, mode); + } - [Command("warn")] - public async Task WarnTriggerAsync(uint amount, TriggerSource source, - uint count = 1, TriggerMode mode = TriggerMode.Exact) - { - var action = new WarningAction(count); - await TryAddTriggerAsync(action, amount, source, mode); - } + [Command("reprimands")] + [Alias("history")] + [Summary("Shows associated reprimands of this trigger.")] + protected async Task ViewAssociatedReprimandsAsync(string id, + [Summary("Leave empty to show everything.")] + InfractionType type = InfractionType.All) + { + var trigger = await TryFindEntityAsync(id); - [Command("reprimands")] - [Alias("history")] - [Summary("Shows associated reprimands of this trigger.")] - protected async Task ViewAssociatedReprimandsAsync(string id, - [Summary("Leave empty to show everything.")] - InfractionType type = InfractionType.All) + if (trigger is null) { - var trigger = await TryFindEntityAsync(id); - - if (trigger is null) - { - await _error.AssociateError(Context.Message, EmptyMatchMessage); - return; - } - - var reprimands = await _db.Set().AsAsyncEnumerable() - .Where(r => r.TriggerId == trigger.Id) - .ToListAsync(); - - var author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild); - await PagedViewAsync(reprimands.OfType(type), - r => (r.GetTitle(), r.GetReprimandDetails()), - "Reprimands", author); + await _error.AssociateError(Context.Message, EmptyMatchMessage); + return; } - [Command] - [Alias("list", "view")] - [Summary("View the reprimand trigger list.")] - protected override Task ViewEntityAsync() => base.ViewEntityAsync(); + var reprimands = await _db.Set().AsAsyncEnumerable() + .Where(r => r.TriggerId == trigger.Id) + .ToListAsync(); - protected override (string Title, StringBuilder Value) EntityViewer(ReprimandTrigger trigger) - { - var content = new StringBuilder() - .AppendLine($"▌Action: {trigger.Reprimand.Action}") - .AppendLine($"▌Trigger: {trigger.GetTriggerDetails()}") - .AppendLine($"▉ Active: {trigger.IsActive}") - .AppendLine($"▉ Modified by: {trigger.GetModerator()}"); + var author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild); + await PagedViewAsync(reprimands.OfType(type), + r => (r.GetTitle(), r.GetReprimandDetails()), + "Reprimands", author); + } - return ($"{trigger.Reprimand.GetTitle()}: {trigger.Id}", content); - } + [Command] + [Alias("list", "view")] + [Summary("View the reprimand trigger list.")] + protected override Task ViewEntityAsync() => base.ViewEntityAsync(); - protected override bool IsMatch(ReprimandTrigger entity, string id) - => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); + protected override (string Title, StringBuilder Value) EntityViewer(ReprimandTrigger trigger) + { + var content = new StringBuilder() + .AppendLine($"▌Action: {trigger.Reprimand.Action}") + .AppendLine($"▌Trigger: {trigger.GetTriggerDetails()}") + .AppendLine($"▉ Active: {trigger.IsActive}") + .AppendLine($"▉ Modified by: {trigger.GetModerator()}"); - protected override async Task RemoveEntityAsync(ReprimandTrigger entity) - { - var triggerHasReprimand = _db.Set() - .Any(r => r.TriggerId == entity.Id); + return ($"{trigger.Reprimand.GetTitle()}: {trigger.Id}", content); + } - if (triggerHasReprimand) - entity.IsActive = false; - else - _db.Remove(entity); + protected override bool IsMatch(ReprimandTrigger entity, string id) + => entity.Id.ToString().StartsWith(id, StringComparison.OrdinalIgnoreCase); - await _db.SaveChangesAsync(); - } + protected override async Task RemoveEntityAsync(ReprimandTrigger entity) + { + var triggerHasReprimand = _db.Set() + .Any(r => r.TriggerId == entity.Id); - protected override async Task> GetCollectionAsync() - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var rules = guild.ModerationRules; + if (triggerHasReprimand) + entity.IsActive = false; + else + _db.Remove(entity); - return rules.Triggers.OfType().ToArray(); - } + await _db.SaveChangesAsync(); + } - private async Task TryAddTriggerAsync(ReprimandAction action, uint amount, TriggerSource source, - TriggerMode mode) - { - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var rules = guild.ModerationRules; + protected override async Task> GetCollectionAsync() + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var rules = guild.ModerationRules; - var options = new TriggerOptions(amount, source, mode); - var trigger = new ReprimandTrigger(options, options.Source, action); + return rules.Triggers.OfType().ToArray(); + } - var existing = rules.Triggers.OfType() - .Where(t => t.IsActive) - .FirstOrDefault(t => t.Source == options.Source && t.Amount == trigger.Amount); + private async Task TryAddTriggerAsync(ReprimandAction action, uint amount, TriggerSource source, + TriggerMode mode) + { + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var rules = guild.ModerationRules; - if (existing is not null) await RemoveEntityAsync(existing); + var options = new TriggerOptions(amount, source, mode); + var trigger = new ReprimandTrigger(options, options.Source, action); - rules.Triggers.Add(trigger.WithModerator(Context)); - await _db.SaveChangesAsync(); + var existing = rules.Triggers.OfType() + .Where(t => t.IsActive) + .FirstOrDefault(t => t.Source == options.Source && t.Amount == trigger.Amount); - var embed = new EmbedBuilder() - .WithTitle("Trigger added") - .WithColor(Color.Green) - .AddField("Action", trigger.Reprimand.Action) - .AddField("Trigger", trigger.GetTriggerDetails()) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + if (existing is not null) await RemoveEntityAsync(existing); - await ReplyAsync(embed: embed.Build()); - } + rules.Triggers.Add(trigger.WithModerator(Context)); + await _db.SaveChangesAsync(); - private class TriggerOptions : ITrigger + var embed = new EmbedBuilder() + .WithTitle("Trigger added") + .WithColor(Color.Green) + .AddField("Action", trigger.Reprimand.Action) + .AddField("Trigger", trigger.GetTriggerDetails()) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + + await ReplyAsync(embed: embed.Build()); + } + + private class TriggerOptions : ITrigger + { + public TriggerOptions(uint amount, TriggerSource source, TriggerMode mode) { - public TriggerOptions(uint amount, TriggerSource source, TriggerMode mode) - { - Mode = mode; - Amount = amount; - Source = source; - } + Mode = mode; + Amount = amount; + Source = source; + } - public TriggerSource Source { get; } + public TriggerSource Source { get; } - public TriggerMode Mode { get; set; } + public TriggerMode Mode { get; set; } - public uint Amount { get; set; } - } + public uint Amount { get; set; } } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/RoleModule.cs b/Zhongli.Bot/Modules/RoleModule.cs index d6d291e..47bd663 100644 --- a/Zhongli.Bot/Modules/RoleModule.cs +++ b/Zhongli.Bot/Modules/RoleModule.cs @@ -15,308 +15,307 @@ using Zhongli.Services.Expirable; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules +namespace Zhongli.Bot.Modules; + +[Group("role")] +[Name("Role Management")] +[Summary("Manages roles.")] +[RequireBotPermission(GuildPermission.ManageRoles)] +[RequireUserPermission(GuildPermission.ManageRoles)] +public class RoleModule : InteractiveBase { - [Group("role")] - [Name("Role Management")] - [Summary("Manages roles.")] - [RequireBotPermission(GuildPermission.ManageRoles)] - [RequireUserPermission(GuildPermission.ManageRoles)] - public class RoleModule : InteractiveBase - { - private readonly TemporaryRoleMemberService _member; - private readonly TemporaryRoleService _role; + private readonly TemporaryRoleMemberService _member; + private readonly TemporaryRoleService _role; - public RoleModule(TemporaryRoleMemberService member, TemporaryRoleService role) - { - _member = member; - _role = role; - } + public RoleModule(TemporaryRoleMemberService member, TemporaryRoleService role) + { + _member = member; + _role = role; + } - [Command("add")] - [Summary("Adds specified role to a user.")] - public Task AddRoleAsync(IGuildUser user, IRole role, TimeSpan? length = null) - => length is not null ? AddTemporaryRoleMemberAsync(user, role, length.Value) : AddRolesAsync(user, role); + [Command("add")] + [Summary("Adds specified role to a user.")] + public Task AddRoleAsync(IGuildUser user, IRole role, TimeSpan? length = null) + => length is not null ? AddTemporaryRoleMemberAsync(user, role, length.Value) : AddRolesAsync(user, role); - [HiddenFromHelp] - [Command("add")] - [Summary("Adds specified roles to a user.")] - public async Task AddRolesAsync(IGuildUser user, params IRole[] roles) - { - await user.AddRolesAsync(roles); + [HiddenFromHelp] + [Command("add")] + [Summary("Adds specified roles to a user.")] + public async Task AddRolesAsync(IGuildUser user, params IRole[] roles) + { + await user.AddRolesAsync(roles); - var embed = new EmbedBuilder() - .WithDescription( - $"Added {roles.OrderByDescending(r => r.Position).Humanize(x => x.Mention)} to {user.Mention}.") - .WithColor(Color.Green); + var embed = new EmbedBuilder() + .WithDescription( + $"Added {roles.OrderByDescending(r => r.Position).Humanize(x => x.Mention)} to {user.Mention}.") + .WithColor(Color.Green); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("add everyone")] - [Summary("Adds specified roles to everyone.")] - public async Task AddRolesAsync(params SocketRole[] roles) - { - var message = await ReplyAsync("Adding roles, this might take a while..."); + [Command("add everyone")] + [Summary("Adds specified roles to everyone.")] + public async Task AddRolesAsync(params SocketRole[] roles) + { + var message = await ReplyAsync("Adding roles, this might take a while..."); - if (!Context.Guild.HasAllMembers) - await Context.Guild.DownloadUsersAsync(); + if (!Context.Guild.HasAllMembers) + await Context.Guild.DownloadUsersAsync(); - foreach (var user in Context.Guild.Users) + foreach (var user in Context.Guild.Users) + { + try { - try - { - await user.AddRolesAsync(roles); - } - catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) - { - // Ignored - } + await user.AddRolesAsync(roles); } + catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) + { + // Ignored + } + } - var embed = new EmbedBuilder() - .WithDescription( - $"Added {roles.OrderByDescending(r => r.Position).Humanize(x => x.Mention)} to everyone.") - .WithColor(Color.Green); + var embed = new EmbedBuilder() + .WithDescription( + $"Added {roles.OrderByDescending(r => r.Position).Humanize(x => x.Mention)} to everyone.") + .WithColor(Color.Green); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } + + [Command("temporary add")] + [Alias("tempadd")] + [Summary("Puts a member into a temporary role.")] + public async Task AddTemporaryRoleMemberAsync(IGuildUser user, IRole role, TimeSpan length) + { + await _member.AddTemporaryRoleMemberAsync(user, role, length); + var embed = new EmbedBuilder() + .WithDescription(new StringBuilder() + .AppendLine($"Temporarily added {role.Mention} to {user.Mention}.") + .AppendLine($"Expires {length.ToUniversalTimestamp()}.") + .ToString()) + .WithColor(Color.Green); + + await ReplyAsync(embed: embed.Build()); + } - [Command("temporary add")] - [Alias("tempadd")] - [Summary("Puts a member into a temporary role.")] - public async Task AddTemporaryRoleMemberAsync(IGuildUser user, IRole role, TimeSpan length) + [Command("color")] + [Summary("Changes specified roles colors.")] + public async Task ChangeColorsAsync(Color color, params IRole[] roles) + { + foreach (var role in roles) { - await _member.AddTemporaryRoleMemberAsync(user, role, length); - var embed = new EmbedBuilder() - .WithDescription(new StringBuilder() - .AppendLine($"Temporarily added {role.Mention} to {user.Mention}.") - .AppendLine($"Expires {length.ToUniversalTimestamp()}.") - .ToString()) - .WithColor(Color.Green); - - await ReplyAsync(embed: embed.Build()); + await role.ModifyAsync(r => r.Color = color); } - [Command("color")] - [Summary("Changes specified roles colors.")] - public async Task ChangeColorsAsync(Color color, params IRole[] roles) - { - foreach (var role in roles) - { - await role.ModifyAsync(r => r.Color = color); - } + var embed = new EmbedBuilder() + .WithDescription("Color changed successfully") + .WithColor(color); - var embed = new EmbedBuilder() - .WithDescription("Color changed successfully") - .WithColor(color); + await ReplyAsync(embed: embed.Build()); + } - await ReplyAsync(embed: embed.Build()); - } + [Command("create")] + [Summary("Creates a role.")] + public async Task CreateRoleAsync(string name, RoleCreationOptions? options = null) + { + var guildPermissions = options?.Permissions?.ToGuildPermissions(); + var role = await Context.Guild.CreateRoleAsync(name, + guildPermissions, options?.Color, + options?.IsHoisted ?? false, + options?.IsMentionable ?? false); + + var permissions = role.Permissions.ToList().Select(p => p.Humanize()); + var embed = new EmbedBuilder() + .WithColor(role.Color) + .WithDescription($"Created the following role: {Format.Bold(role.Name)} with the provided options.") + .AddField("Hoisted", role.IsHoisted, true) + .AddField("Mentionable", role.IsMentionable, true) + .AddField("Color", role.Color, true) + .AddItemsIntoFields("Permissions", permissions, ", "); + + await ReplyAsync(embed: embed.Build()); + } - [Command("create")] - [Summary("Creates a role.")] - public async Task CreateRoleAsync(string name, RoleCreationOptions? options = null) + [Command("delete")] + [Summary("Deletes the specified roles.")] + public async Task DeleteRolesAsync(params IRole[] roles) + { + foreach (var role in roles) { - var guildPermissions = options?.Permissions?.ToGuildPermissions(); - var role = await Context.Guild.CreateRoleAsync(name, - guildPermissions, options?.Color, - options?.IsHoisted ?? false, - options?.IsMentionable ?? false); - - var permissions = role.Permissions.ToList().Select(p => p.Humanize()); - var embed = new EmbedBuilder() - .WithColor(role.Color) - .WithDescription($"Created the following role: {Format.Bold(role.Name)} with the provided options.") - .AddField("Hoisted", role.IsHoisted, true) - .AddField("Mentionable", role.IsMentionable, true) - .AddField("Color", role.Color, true) - .AddItemsIntoFields("Permissions", permissions, ", "); - - await ReplyAsync(embed: embed.Build()); + await role.DeleteAsync(); } - [Command("delete")] - [Summary("Deletes the specified roles.")] - public async Task DeleteRolesAsync(params IRole[] roles) - { - foreach (var role in roles) - { - await role.DeleteAsync(); - } - - var embed = new EmbedBuilder() - .WithDescription($"Deleted the following role(s): {Format.Bold(roles.Humanize())} from the guild.") - .WithColor(Color.DarkRed); + var embed = new EmbedBuilder() + .WithDescription($"Deleted the following role(s): {Format.Bold(roles.Humanize())} from the guild.") + .WithColor(Color.DarkRed); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("remove")] - [Summary("Removes specified roles to a user.")] - public async Task RemoveRolesAsync(IGuildUser user, params IRole[] roles) - { - await user.RemoveRolesAsync(roles); + [Command("remove")] + [Summary("Removes specified roles to a user.")] + public async Task RemoveRolesAsync(IGuildUser user, params IRole[] roles) + { + await user.RemoveRolesAsync(roles); - var embed = new EmbedBuilder() - .WithDescription($"Removed {Format.Bold(roles.Humanize())} from {user.Mention}.") - .WithColor(Color.DarkRed); + var embed = new EmbedBuilder() + .WithDescription($"Removed {Format.Bold(roles.Humanize())} from {user.Mention}.") + .WithColor(Color.DarkRed); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("remove everyone")] - [Summary("Removes specified roles from everyone.")] - public async Task RemoveRolesAsync(params SocketRole[] roles) + [Command("remove everyone")] + [Summary("Removes specified roles from everyone.")] + public async Task RemoveRolesAsync(params SocketRole[] roles) + { + var message = await ReplyAsync("Removing roles, this might take a while..."); + foreach (var role in roles) { - var message = await ReplyAsync("Removing roles, this might take a while..."); - foreach (var role in roles) + foreach (var member in role.Members) { - foreach (var member in role.Members) + try { - try - { - await member.RemoveRoleAsync(role); - } - catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) - { - // Ignored - } + await member.RemoveRoleAsync(role); + } + catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) + { + // Ignored } } + } - var embed = new EmbedBuilder() - .WithDescription($"Removed {Format.Bold(roles.Humanize())} from everyone.") - .WithColor(Color.DarkRed); + var embed = new EmbedBuilder() + .WithDescription($"Removed {Format.Bold(roles.Humanize())} from everyone.") + .WithColor(Color.DarkRed); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("temporary convert")] - [Alias("tempconvert")] - [Summary("Converts a role into a temporary role.")] - public async Task TemporaryRoleConvertAsync(IRole role, TimeSpan length) - { - await _role.CreateTemporaryRoleAsync(role, length); - - var embed = new EmbedBuilder() - .WithTitle("Temporary Role") - .WithDescription($"Created a temporary role that will expire {length.ToDiscordTimestamp()}") - .AddField("Role", role.Mention, true) - .AddField("Mentionable", role.IsMentionable, true) - .AddField("Hoisted", role.IsHoisted, true) - .WithCurrentTimestamp() - .WithUserAsAuthor(Context.Message.Author, AuthorOptions.Requested | AuthorOptions.UseFooter); - - await ReplyAsync(embed: embed.Build()); - } + [Command("temporary convert")] + [Alias("tempconvert")] + [Summary("Converts a role into a temporary role.")] + public async Task TemporaryRoleConvertAsync(IRole role, TimeSpan length) + { + await _role.CreateTemporaryRoleAsync(role, length); + + var embed = new EmbedBuilder() + .WithTitle("Temporary Role") + .WithDescription($"Created a temporary role that will expire {length.ToDiscordTimestamp()}") + .AddField("Role", role.Mention, true) + .AddField("Mentionable", role.IsMentionable, true) + .AddField("Hoisted", role.IsHoisted, true) + .WithCurrentTimestamp() + .WithUserAsAuthor(Context.Message.Author, AuthorOptions.Requested | AuthorOptions.UseFooter); + + await ReplyAsync(embed: embed.Build()); + } - [Command("temporary create")] - [Alias("tempcreate")] - [Summary("Creates a temporary role that gets deleted after a specified time.")] - public async Task TemporaryRoleCreateAsync(string name, TimeSpan length, RoleCreationOptions? options = null) - { - var permissions = options?.Permissions?.ToGuildPermissions(); - var role = await Context.Guild.CreateRoleAsync(name, - permissions, options?.Color, - options?.IsHoisted ?? false, - options?.IsMentionable ?? false); + [Command("temporary create")] + [Alias("tempcreate")] + [Summary("Creates a temporary role that gets deleted after a specified time.")] + public async Task TemporaryRoleCreateAsync(string name, TimeSpan length, RoleCreationOptions? options = null) + { + var permissions = options?.Permissions?.ToGuildPermissions(); + var role = await Context.Guild.CreateRoleAsync(name, + permissions, options?.Color, + options?.IsHoisted ?? false, + options?.IsMentionable ?? false); - await TemporaryRoleConvertAsync(role, length); - } + await TemporaryRoleConvertAsync(role, length); + } - [Command] - [Summary("Adds or removes the specified roles to a user.")] - public Task ToggleRoleAsync(IGuildUser user, IRole role) - => user.HasRole(role) - ? RemoveRolesAsync(user, role) - : AddRolesAsync(user, role); - - [Command("view")] - [Summary("View the information of specified roles.")] - public async Task ViewRolesAsync( - [Summary("Leave empty to show all roles.")] - params SocketRole[] roles) + [Command] + [Summary("Adds or removes the specified roles to a user.")] + public Task ToggleRoleAsync(IGuildUser user, IRole role) + => user.HasRole(role) + ? RemoveRolesAsync(user, role) + : AddRolesAsync(user, role); + + [Command("view")] + [Summary("View the information of specified roles.")] + public async Task ViewRolesAsync( + [Summary("Leave empty to show all roles.")] + params SocketRole[] roles) + { + switch (roles.Length) { - switch (roles.Length) - { - case 0: - await ViewRolesInfoAsync(Context.Guild.Roles); - break; - case 1: - await ViewRoleInfoAsync(roles[0]); - break; - default: - await ViewRolesInfoAsync(roles); - break; - } + case 0: + await ViewRolesInfoAsync(Context.Guild.Roles); + break; + case 1: + await ViewRoleInfoAsync(roles[0]); + break; + default: + await ViewRolesInfoAsync(roles); + break; } + } - private static EmbedFieldBuilder CreateRoleEmbedField(SocketRole role) - { - var content = new StringBuilder() - .AppendLine($"▌Mention: {role.Mention}") - .AppendLine($"▌Members: {role.Members.Count()}") - .AppendLine($"▌Color: {role.Color}") - .AppendLine($"▌Hoisted: {role.IsHoisted}") - .AppendLine($"▌Mentionable: {role.IsMentionable}") - .AppendLine($"▌Permissions: {role.Permissions.ToList().Humanize(p => p.Humanize())}"); - - return new EmbedFieldBuilder() - .WithName($"{role.Name} ({role.Id})") - .WithValue(content.ToString()); - } + private static EmbedFieldBuilder CreateRoleEmbedField(SocketRole role) + { + var content = new StringBuilder() + .AppendLine($"▌Mention: {role.Mention}") + .AppendLine($"▌Members: {role.Members.Count()}") + .AppendLine($"▌Color: {role.Color}") + .AppendLine($"▌Hoisted: {role.IsHoisted}") + .AppendLine($"▌Mentionable: {role.IsMentionable}") + .AppendLine($"▌Permissions: {role.Permissions.ToList().Humanize(p => p.Humanize())}"); + + return new EmbedFieldBuilder() + .WithName($"{role.Name} ({role.Id})") + .WithValue(content.ToString()); + } - private async Task ViewRoleInfoAsync(SocketRole role) - { - var members = role.Members.ToList(); - - var embed = new EmbedBuilder() - .WithGuildAsAuthor(Context.Guild) - .WithTitle($"{role.Name} ({role.Id})") - .AddField("Mention", role.Mention, true) - .AddField("Members", members.Count, true) - .AddField("Color", role.Color, true) - .AddField("Hoisted", role.IsHoisted, true) - .AddField("Mentionable", role.IsMentionable, true) - .AddField("Managed", role.IsManaged, true) - .AddField("Permissions", role.Permissions.ToList().Humanize(p => p.Humanize())) - .AddItemsIntoFields("Members", members, r => r.Mention, " "); - - await ReplyAsync(embed: embed.Build()); - } + private async Task ViewRoleInfoAsync(SocketRole role) + { + var members = role.Members.ToList(); + + var embed = new EmbedBuilder() + .WithGuildAsAuthor(Context.Guild) + .WithTitle($"{role.Name} ({role.Id})") + .AddField("Mention", role.Mention, true) + .AddField("Members", members.Count, true) + .AddField("Color", role.Color, true) + .AddField("Hoisted", role.IsHoisted, true) + .AddField("Mentionable", role.IsMentionable, true) + .AddField("Managed", role.IsManaged, true) + .AddField("Permissions", role.Permissions.ToList().Humanize(p => p.Humanize())) + .AddItemsIntoFields("Members", members, r => r.Mention, " "); + + await ReplyAsync(embed: embed.Build()); + } - private async Task ViewRolesInfoAsync(IEnumerable roles) - { - var fields = roles.Select(CreateRoleEmbedField); + private async Task ViewRolesInfoAsync(IEnumerable roles) + { + var fields = roles.Select(CreateRoleEmbedField); - var message = new PaginatedMessage + var message = new PaginatedMessage + { + Pages = fields, + Author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild), + Options = new PaginatedAppearanceOptions { - Pages = fields, - Author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild), - Options = new PaginatedAppearanceOptions - { - DisplayInformationIcon = false, - Timeout = TimeSpan.FromMinutes(10) - } - }; + DisplayInformationIcon = false, + Timeout = TimeSpan.FromMinutes(10) + } + }; - await PagedReplyAsync(message); - } + await PagedReplyAsync(message); + } - [NamedArgumentType] - public class RoleCreationOptions - { - [HelpSummary("Hoist the role in the member list")] - public bool? IsHoisted { get; set; } + [NamedArgumentType] + public class RoleCreationOptions + { + [HelpSummary("Hoist the role in the member list")] + public bool? IsHoisted { get; set; } - [HelpSummary("Make the role mentionable")] - public bool? IsMentionable { get; set; } + [HelpSummary("Make the role mentionable")] + public bool? IsMentionable { get; set; } - [HelpSummary("Choose the color of the role")] - public Color? Color { get; set; } + [HelpSummary("Choose the color of the role")] + public Color? Color { get; set; } - [HelpSummary("List of permissions")] public IEnumerable? Permissions { get; set; } - } + [HelpSummary("List of permissions")] public IEnumerable? Permissions { get; set; } } } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/UserModule.cs b/Zhongli.Bot/Modules/UserModule.cs index 1d37ee7..b726b74 100644 --- a/Zhongli.Bot/Modules/UserModule.cs +++ b/Zhongli.Bot/Modules/UserModule.cs @@ -16,157 +16,156 @@ using Zhongli.Services.Moderation; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules +namespace Zhongli.Bot.Modules; + +[Summary("Commands to view a user's details.")] +public class UserModule : InteractiveBase { - [Summary("Commands to view a user's details.")] - public class UserModule : InteractiveBase - { - private readonly AuthorizationService _auth; - private readonly ZhongliContext _db; + private readonly AuthorizationService _auth; + private readonly ZhongliContext _db; - public UserModule(AuthorizationService auth, ZhongliContext db) - { - _auth = auth; - _db = db; - } + public UserModule(AuthorizationService auth, ZhongliContext db) + { + _auth = auth; + _db = db; + } - [Command("avatar")] - [Alias("av")] - [Summary("Get the avatar of the user. Leave empty to view your own avatar.")] - public async Task AvatarAsync( - [Summary("The mention, username or ID of the user.")] - IUser? user = null) - { - user ??= Context.User; + [Command("avatar")] + [Alias("av")] + [Summary("Get the avatar of the user. Leave empty to view your own avatar.")] + public async Task AvatarAsync( + [Summary("The mention, username or ID of the user.")] + IUser? user = null) + { + user ??= Context.User; - var embed = new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId) - .WithImageUrl(user.GetAvatarUrl(size: 2048)) - .WithColor(Color.Green) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); + var embed = new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId) + .WithImageUrl(user.GetAvatarUrl(size: 2048)) + .WithColor(Color.Green) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("history")] - [Alias("infraction", "infractions", "reprimand", "reprimands", "warnlist")] - [Summary("View a specific history of a user's infractions.")] - [RequireAuthorization(AuthorizationScope.Moderator)] - public async Task InfractionsAsync( - [Summary("The user to show the infractions of.")] - IUser? user = null, - [Summary("Leave empty to show warnings.")] - InfractionType type = InfractionType.Warning) + [Command("history")] + [Alias("infraction", "infractions", "reprimand", "reprimands", "warnlist")] + [Summary("View a specific history of a user's infractions.")] + [RequireAuthorization(AuthorizationScope.Moderator)] + public async Task InfractionsAsync( + [Summary("The user to show the infractions of.")] + IUser? user = null, + [Summary("Leave empty to show warnings.")] + InfractionType type = InfractionType.Warning) + { + user ??= Context.User; + var userEntity = _db.Users.FirstOrDefault(u => u.Id == user.Id && u.GuildId == Context.Guild.Id); + if (userEntity is null) + return; + + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var history = guild.ReprimandHistory + .Where(u => u.UserId == user.Id) + .OfType(type); + + var reprimands = new EmbedBuilder().AddReprimands(userEntity) + .AddField("Reprimands", "Active/Total", true).Fields; + var pages = history + .OrderByDescending(r => r.Action?.Date) + .Select(r => CreateEmbed(user, r)); + + var message = new PaginatedMessage { - user ??= Context.User; - var userEntity = _db.Users.FirstOrDefault(u => u.Id == user.Id && u.GuildId == Context.Guild.Id); - if (userEntity is null) - return; - - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var history = guild.ReprimandHistory - .Where(u => u.UserId == user.Id) - .OfType(type); - - var reprimands = new EmbedBuilder().AddReprimands(userEntity) - .AddField("Reprimands", "Active/Total", true).Fields; - var pages = history - .OrderByDescending(r => r.Action?.Date) - .Select(r => CreateEmbed(user, r)); - - var message = new PaginatedMessage + Pages = reprimands.Concat(pages), + Author = new EmbedAuthorBuilder().WithName($"{user}"), + Options = new PaginatedAppearanceOptions { - Pages = reprimands.Concat(pages), - Author = new EmbedAuthorBuilder().WithName($"{user}"), - Options = new PaginatedAppearanceOptions - { - DisplayInformationIcon = false, - FieldsPerPage = 8, - Timeout = TimeSpan.FromMinutes(10) - } - }; - - await PagedReplyAsync(message); - } + DisplayInformationIcon = false, + FieldsPerPage = 8, + Timeout = TimeSpan.FromMinutes(10) + } + }; - [Command("user")] - [Alias("whois")] - [Summary("Views the information of a user. Leave blank to view self.")] - public async Task UserAsync( - [Summary("The mention, username or ID of the user.")] - IUser? user = null) - { - user ??= Context.User; + await PagedReplyAsync(message); + } - var isAuthorized = await _auth - .IsAuthorizedAsync(Context, AuthorizationScope.All | AuthorizationScope.Moderator); - var userEntity = await _db.Users.FindAsync(user.Id, Context.Guild.Id); - var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); - var guildUser = user as SocketGuildUser; + [Command("user")] + [Alias("whois")] + [Summary("Views the information of a user. Leave blank to view self.")] + public async Task UserAsync( + [Summary("The mention, username or ID of the user.")] + IUser? user = null) + { + user ??= Context.User; + + var isAuthorized = await _auth + .IsAuthorizedAsync(Context, AuthorizationScope.All | AuthorizationScope.Moderator); + var userEntity = await _db.Users.FindAsync(user.Id, Context.Guild.Id); + var guild = await _db.Guilds.TrackGuildAsync(Context.Guild); + var guildUser = user as SocketGuildUser; + + var embed = new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested) + .WithDescription(user.Mention) + .AddField("Created", user.CreatedAt.ToUniversalTimestamp()); + + if (userEntity?.JoinedAt is not null) + embed.AddField("First Joined", userEntity.JoinedAt.Value.ToUniversalTimestamp()); - var embed = new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .WithUserAsAuthor(Context.User, AuthorOptions.UseFooter | AuthorOptions.Requested) - .WithDescription(user.Mention) - .AddField("Created", user.CreatedAt.ToUniversalTimestamp()); + if (guildUser is not null) + { + if (guildUser.JoinedAt is not null) + embed.AddField("Joined", guildUser.JoinedAt.Value.ToUniversalTimestamp()); + + var roles = guildUser.Roles + .OrderByDescending(r => r.Position) + .ToList(); - if (userEntity?.JoinedAt is not null) - embed.AddField("First Joined", userEntity.JoinedAt.Value.ToUniversalTimestamp()); + embed + .WithColor(roles.Select(r => r.Color).FirstOrDefault(c => c.RawValue is not 0)) + .AddField($"Roles [{guildUser.Roles.Count}]", roles.Humanize(r => r.Mention)); - if (guildUser is not null) + if (isAuthorized && guild.ModerationRules.MuteRoleId is not null) { - if (guildUser.JoinedAt is not null) - embed.AddField("Joined", guildUser.JoinedAt.Value.ToUniversalTimestamp()); - - var roles = guildUser.Roles - .OrderByDescending(r => r.Position) - .ToList(); - - embed - .WithColor(roles.Select(r => r.Color).FirstOrDefault(c => c.RawValue is not 0)) - .AddField($"Roles [{guildUser.Roles.Count}]", roles.Humanize(r => r.Mention)); - - if (isAuthorized && guild.ModerationRules.MuteRoleId is not null) - { - var isMuted = guildUser.HasRole(guild.ModerationRules.MuteRoleId.Value); - embed.AddField("Muted", isMuted, true); - } + var isMuted = guildUser.HasRole(guild.ModerationRules.MuteRoleId.Value); + embed.AddField("Muted", isMuted, true); } + } - if (isAuthorized) + if (isAuthorized) + { + var ban = await Context.Guild.GetBanAsync(user); + if (ban is not null) { - var ban = await Context.Guild.GetBanAsync(user); - if (ban is not null) - { - embed.WithColor(Color.Red); - - var banDetails = userEntity?.Reprimands() - .OrderByDescending(b => b.Action?.Date) - .FirstOrDefault(); - - if (banDetails is not null) - embed.AddField(banDetails.GetTitle(), banDetails.GetReprimandDetails().ToString()); - else - embed.AddField("Banned", $"This user is banned. Reason: {ban.Reason ?? "None"}"); - } - - if (userEntity is not null) embed.AddReprimands(userEntity); + embed.WithColor(Color.Red); + + var banDetails = userEntity?.Reprimands() + .OrderByDescending(b => b.Action?.Date) + .FirstOrDefault(); + + if (banDetails is not null) + embed.AddField(banDetails.GetTitle(), banDetails.GetReprimandDetails().ToString()); + else + embed.AddField("Banned", $"This user is banned. Reason: {ban.Reason ?? "None"}"); } - await ReplyAsync(embed: embed.Build()); + if (userEntity is not null) embed.AddReprimands(userEntity); } - [Priority(-1)] - [HiddenFromHelp] - [Command("user")] - public async Task UserAsync(ulong userId) - { - var user = Context.Client.GetUser(userId); - await UserAsync(user); - } + await ReplyAsync(embed: embed.Build()); + } - private static EmbedFieldBuilder CreateEmbed(IUser user, Reprimand r) => new EmbedFieldBuilder() - .WithName(r.GetTitle()) - .WithValue(r.GetReprimandDetails().ToString()); + [Priority(-1)] + [HiddenFromHelp] + [Command("user")] + public async Task UserAsync(ulong userId) + { + var user = Context.Client.GetUser(userId); + await UserAsync(user); } + + private static EmbedFieldBuilder CreateEmbed(IUser user, Reprimand r) => new EmbedFieldBuilder() + .WithName(r.GetTitle()) + .WithValue(r.GetReprimandDetails().ToString()); } \ No newline at end of file diff --git a/Zhongli.Bot/Modules/VoiceChatModule.cs b/Zhongli.Bot/Modules/VoiceChatModule.cs index 8e1ce4d..b8e39e4 100644 --- a/Zhongli.Bot/Modules/VoiceChatModule.cs +++ b/Zhongli.Bot/Modules/VoiceChatModule.cs @@ -10,295 +10,294 @@ using Zhongli.Services.Core.Preconditions; using Zhongli.Services.Utilities; -namespace Zhongli.Bot.Modules +namespace Zhongli.Bot.Modules; + +[Name("Voice")] +[Group("voice")] +[Summary("Commands to manage voice chats.")] +public class VoiceChatModule : ModuleBase { - [Name("Voice")] - [Group("voice")] - [Summary("Commands to manage voice chats.")] - public class VoiceChatModule : ModuleBase + private readonly ZhongliContext _db; + + public VoiceChatModule(ZhongliContext db) { _db = db; } + + [Command("ban")] + [Summary("Ban someone from your current VC.")] + public async Task BanAsync(IGuildUser user) { - private readonly ZhongliContext _db; + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - public VoiceChatModule(ZhongliContext db) { _db = db; } + if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) + return; - [Command("ban")] - [Summary("Ban someone from your current VC.")] - public async Task BanAsync(IGuildUser user) + if (user.VoiceChannel?.Id == voiceChat.VoiceChannelId) { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + await user.VoiceChannel.AddPermissionOverwriteAsync(user, + OverwritePermissions.DenyAll(user.VoiceChannel)); + await user.ModifyAsync(u => u.Channel = null); - if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) - return; + var textChannel = (IGuildChannel) Context.Channel; + await textChannel.AddPermissionOverwriteAsync(user, + OverwritePermissions.DenyAll(textChannel)); - if (user.VoiceChannel?.Id == voiceChat.VoiceChannelId) - { - await user.VoiceChannel.AddPermissionOverwriteAsync(user, - OverwritePermissions.DenyAll(user.VoiceChannel)); - await user.ModifyAsync(u => u.Channel = null); + var embed = new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .WithDescription("User has been banned from the channel.") + .WithColor(Color.DarkRed); + + await ReplyAsync(embed: embed.Build()); + } + } - var textChannel = (IGuildChannel) Context.Channel; - await textChannel.AddPermissionOverwriteAsync(user, - OverwritePermissions.DenyAll(textChannel)); + [Command("claim")] + [Summary("Claim this VC as yours. Only works if there are no other people in the VC.")] + public async Task ClaimAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - var embed = new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .WithDescription("User has been banned from the channel.") - .WithColor(Color.DarkRed); + if (voiceChat is null) + return; - await ReplyAsync(embed: embed.Build()); - } + if (voiceChat.UserId == Context.User.Id) + { + await ReplyAsync("You already are the owner of the VC."); + return; } - [Command("claim")] - [Summary("Claim this VC as yours. Only works if there are no other people in the VC.")] - public async Task ClaimAsync() + var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + if (voiceChannel.Users.Any(u => u.Id == voiceChat.UserId)) { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + await ReplyAsync("You cannot claim this VC."); + return; + } - if (voiceChat is null) - return; + var owner = Context.Guild.GetUser(voiceChat.UserId); + await voiceChannel.RemovePermissionOverwriteAsync(owner); + await voiceChannel.AddPermissionOverwriteAsync(Context.User, + new OverwritePermissions(manageChannel: PermValue.Allow, muteMembers: PermValue.Allow)); - if (voiceChat.UserId == Context.User.Id) - { - await ReplyAsync("You already are the owner of the VC."); - return; - } + voiceChat.UserId = Context.User.Id; + await _db.SaveChangesAsync(); - var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - if (voiceChannel.Users.Any(u => u.Id == voiceChat.UserId)) - { - await ReplyAsync("You cannot claim this VC."); - return; - } + await ReplyAsync("VC successfully claimed."); + } - var owner = Context.Guild.GetUser(voiceChat.UserId); - await voiceChannel.RemovePermissionOverwriteAsync(owner); - await voiceChannel.AddPermissionOverwriteAsync(Context.User, - new OverwritePermissions(manageChannel: PermValue.Allow, muteMembers: PermValue.Allow)); + [Command("clean")] + [RequireAuthorization(AuthorizationScope.Moderator)] + [Summary("Clean up unused Voice Chats.")] + public async Task CleanAsync() + { + var guild = await _db.Guilds.FindAsync(Context.Guild.Id); - voiceChat.UserId = Context.User.Id; - await _db.SaveChangesAsync(); + if (guild.VoiceChatRules is null) + return; - await ReplyAsync("VC successfully claimed."); - } + var voiceChats = guild.VoiceChatRules.VoiceChats.ToList(); - [Command("clean")] - [RequireAuthorization(AuthorizationScope.Moderator)] - [Summary("Clean up unused Voice Chats.")] - public async Task CleanAsync() - { - var guild = await _db.Guilds.FindAsync(Context.Guild.Id); + var empty = voiceChats.ToAsyncEnumerable() + .Select(v => new + { + VoiceChannel = Context.Guild.GetVoiceChannel(v.VoiceChannelId), + VoiceChat = Context.Guild.GetTextChannel(v.TextChannelId) + }) + .Where(v => v.VoiceChannel.Users.All(u => u.IsBot || u.IsWebhook)); - if (guild.VoiceChatRules is null) - return; + await foreach (var link in empty) + { + var voiceChat = voiceChats.FirstOrDefault(v => v.VoiceChannelId == link.VoiceChannel.Id); - var voiceChats = guild.VoiceChatRules.VoiceChats.ToList(); + _db.Remove(voiceChat!); - var empty = voiceChats.ToAsyncEnumerable() - .Select(v => new - { - VoiceChannel = Context.Guild.GetVoiceChannel(v.VoiceChannelId), - VoiceChat = Context.Guild.GetTextChannel(v.TextChannelId) - }) - .Where(v => v.VoiceChannel.Users.All(u => u.IsBot || u.IsWebhook)); + await link.VoiceChannel.DeleteAsync(); + await link.VoiceChat.DeleteAsync(); + } - await foreach (var link in empty) - { - var voiceChat = voiceChats.FirstOrDefault(v => v.VoiceChannelId == link.VoiceChannel.Id); + await _db.SaveChangesAsync(); - _db.Remove(voiceChat!); + await ReplyAsync($"Cleaned {Format.Bold(voiceChats.Count + " channel(s)")}."); + } - await link.VoiceChannel.DeleteAsync(); - await link.VoiceChat.DeleteAsync(); - } + [Command("hide")] + [Summary("Hide the VC from everyone. This denies view permission for everyone.")] + public async Task HideAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - await _db.SaveChangesAsync(); + if (voiceChat is null || voiceChat.UserId != Context.User.Id) + return; - await ReplyAsync($"Cleaned {Format.Bold(voiceChats.Count + " channel(s)")}."); - } + var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, + new OverwritePermissions(viewChannel: PermValue.Deny)); - [Command("hide")] - [Summary("Hide the VC from everyone. This denies view permission for everyone.")] - public async Task HideAsync() - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - - if (voiceChat is null || voiceChat.UserId != Context.User.Id) - return; + await ReplyAsync("VC hidden."); + } - var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, - new OverwritePermissions(viewChannel: PermValue.Deny)); + [Command("kick")] + [Summary("Kick someone from your VC.")] + public async Task KickAsync(IGuildUser user) + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - await ReplyAsync("VC hidden."); - } + if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) + return; - [Command("kick")] - [Summary("Kick someone from your VC.")] - public async Task KickAsync(IGuildUser user) + if (user.VoiceChannel?.Id == voiceChat.VoiceChannelId) { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - - if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) - return; + await user.ModifyAsync(u => u.Channel = null); + var embed = new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .WithDescription("User has been kicked from the channel.") + .WithColor(Color.LightOrange); - if (user.VoiceChannel?.Id == voiceChat.VoiceChannelId) - { - await user.ModifyAsync(u => u.Channel = null); - var embed = new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .WithDescription("User has been kicked from the channel.") - .WithColor(Color.LightOrange); - - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); } + } - [Command("limit")] - [Summary("Add a user limit to your VC.")] - public async Task LimitAsync(uint? limit = null) - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("limit")] + [Summary("Add a user limit to your VC.")] + public async Task LimitAsync(uint? limit = null) + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || voiceChat.UserId != Context.User.Id) - return; + if (voiceChat is null || voiceChat.UserId != Context.User.Id) + return; - var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - await channel.ModifyAsync(c => c.UserLimit = new Optional((int?) limit)); + var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + await channel.ModifyAsync(c => c.UserLimit = new Optional((int?) limit)); - if (limit is null) - await ReplyAsync("Voice user limit reset."); - else - await ReplyAsync($"User limit set to {Format.Bold(limit + " user(s)")}."); - } + if (limit is null) + await ReplyAsync("Voice user limit reset."); + else + await ReplyAsync($"User limit set to {Format.Bold(limit + " user(s)")}."); + } - [Command("lock")] - [Summary("Lock the VC. This makes it so no one else can join." + - " Leaving won't give you permission back unless you're the owner.")] - public async Task LockAsync() - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("lock")] + [Summary("Lock the VC. This makes it so no one else can join." + + " Leaving won't give you permission back unless you're the owner.")] + public async Task LockAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || voiceChat.UserId != Context.User.Id) - return; + if (voiceChat is null || voiceChat.UserId != Context.User.Id) + return; - var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); - var overwrite = everyone?.Modify(connect: PermValue.Deny) - ?? new OverwritePermissions(connect: PermValue.Deny); + var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); + var overwrite = everyone?.Modify(connect: PermValue.Deny) + ?? new OverwritePermissions(connect: PermValue.Deny); - await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); + await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); - await ReplyAsync( - $"Voice chat locked successfully. Use {Format.Code(ZhongliConfig.Configuration.Prefix + "unlock")} to unlock."); - } + await ReplyAsync( + $"Voice chat locked successfully. Use {Format.Code(ZhongliConfig.Configuration.Prefix + "unlock")} to unlock."); + } - [Command("owner")] - [Summary("Show the current owner of the VC.")] - public async Task OwnerAsync() - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("owner")] + [Summary("Show the current owner of the VC.")] + public async Task OwnerAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null) - return; + if (voiceChat is null) + return; - var owner = Context.Guild.GetUser(voiceChat.UserId); - await ReplyAsync($"The current owner is {Format.Bold(owner.GetFullUsername())}."); - } + var owner = Context.Guild.GetUser(voiceChat.UserId); + await ReplyAsync($"The current owner is {Format.Bold(owner.GetFullUsername())}."); + } - [Command("reveal")] - [Summary("Reveals the VC from everyone. This sets the inherit permission on the everyone role.")] - public async Task RevealAsync() - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("reveal")] + [Summary("Reveals the VC from everyone. This sets the inherit permission on the everyone role.")] + public async Task RevealAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || voiceChat.UserId != Context.User.Id) - return; + if (voiceChat is null || voiceChat.UserId != Context.User.Id) + return; - var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); - var overwrite = everyone?.Modify(viewChannel: PermValue.Inherit) - ?? new OverwritePermissions(viewChannel: PermValue.Inherit); + var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); + var overwrite = everyone?.Modify(viewChannel: PermValue.Inherit) + ?? new OverwritePermissions(viewChannel: PermValue.Inherit); - await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); - await ReplyAsync("Voice chat revealed."); - } + await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); + await ReplyAsync("Voice chat revealed."); + } - [Command("transfer")] - [Summary("Transfer ownership to someone else.")] - public async Task TransferAsync(IGuildUser user) - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("transfer")] + [Summary("Transfer ownership to someone else.")] + public async Task TransferAsync(IGuildUser user) + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || user.IsBot || user.IsWebhook) - return; + if (voiceChat is null || user.IsBot || user.IsWebhook) + return; - if (voiceChat.UserId == user.Id) - { - await ReplyAsync("You already are the owner of the VC."); - return; - } + if (voiceChat.UserId == user.Id) + { + await ReplyAsync("You already are the owner of the VC."); + return; + } - var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - await voiceChannel.RemovePermissionOverwriteAsync(Context.User); - await voiceChannel.AddPermissionOverwriteAsync(user, - new OverwritePermissions(manageChannel: PermValue.Allow, muteMembers: PermValue.Allow)); + await voiceChannel.RemovePermissionOverwriteAsync(Context.User); + await voiceChannel.AddPermissionOverwriteAsync(user, + new OverwritePermissions(manageChannel: PermValue.Allow, muteMembers: PermValue.Allow)); - voiceChat.UserId = user.Id; - await _db.SaveChangesAsync(); + voiceChat.UserId = user.Id; + await _db.SaveChangesAsync(); - await ReplyAsync( - $"Voice chat ownership successfully transferred to {Format.Bold(user.GetFullUsername())}."); - } + await ReplyAsync( + $"Voice chat ownership successfully transferred to {Format.Bold(user.GetFullUsername())}."); + } - [Command("unban")] - [Summary("Unban someone from your current VC.")] - public async Task UnbanAsync(IGuildUser user) - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("unban")] + [Summary("Unban someone from your current VC.")] + public async Task UnbanAsync(IGuildUser user) + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) - return; + if (voiceChat is null || voiceChat.UserId != Context.User.Id || user.Id == Context.User.Id) + return; - var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - await voiceChannel.RemovePermissionOverwriteAsync(user); + var voiceChannel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + await voiceChannel.RemovePermissionOverwriteAsync(user); - var embed = new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .WithDescription("User has been unbanned from the channel.") - .WithColor(Color.Blue); + var embed = new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .WithDescription("User has been unbanned from the channel.") + .WithColor(Color.Blue); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); + } - [Command("unlock")] - [Summary("Unlocks the VC.")] - public async Task UnlockAsync() - { - var voiceChat = await _db.Set().AsQueryable() - .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); + [Command("unlock")] + [Summary("Unlocks the VC.")] + public async Task UnlockAsync() + { + var voiceChat = await _db.Set().AsQueryable() + .FirstOrDefaultAsync(vc => vc.TextChannelId == Context.Channel.Id); - if (voiceChat is null || voiceChat.UserId != Context.User.Id) - return; + if (voiceChat is null || voiceChat.UserId != Context.User.Id) + return; - var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); - var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); - var overwrite = everyone?.Modify(connect: PermValue.Inherit) - ?? new OverwritePermissions(connect: PermValue.Inherit); + var channel = Context.Guild.GetVoiceChannel(voiceChat.VoiceChannelId); + var everyone = channel.GetPermissionOverwrite(Context.Guild.EveryoneRole); + var overwrite = everyone?.Modify(connect: PermValue.Inherit) + ?? new OverwritePermissions(connect: PermValue.Inherit); - await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); - await ReplyAsync("Voice chat unlocked successfully."); - } + await channel.AddPermissionOverwriteAsync(Context.Guild.EveryoneRole, overwrite); + await ReplyAsync("Voice chat unlocked successfully."); } } \ No newline at end of file diff --git a/Zhongli.Data/Config/BotConfig.cs b/Zhongli.Data/Config/BotConfig.cs index 8827b94..96f0dff 100644 --- a/Zhongli.Data/Config/BotConfig.cs +++ b/Zhongli.Data/Config/BotConfig.cs @@ -1,17 +1,16 @@ -namespace Zhongli.Data.Config +namespace Zhongli.Data.Config; + +public class BotConfig { - public class BotConfig - { - public int MessageCacheSize { get; set; } + public int MessageCacheSize { get; set; } - public string HangfireContext { get; init; } + public string HangfireContext { get; init; } - public string Prefix { get; init; } + public string Prefix { get; init; } - public string Token { get; init; } + public string Token { get; init; } - public string ZhongliContext { get; init; } + public string ZhongliContext { get; init; } - public ulong Owner { get; init; } - } + public ulong Owner { get; init; } } \ No newline at end of file diff --git a/Zhongli.Data/Config/ZhongliConfig.cs b/Zhongli.Data/Config/ZhongliConfig.cs index 36f7d2d..aec880e 100644 --- a/Zhongli.Data/Config/ZhongliConfig.cs +++ b/Zhongli.Data/Config/ZhongliConfig.cs @@ -1,23 +1,22 @@ using Microsoft.Extensions.Configuration; -namespace Zhongli.Data.Config +namespace Zhongli.Data.Config; + +public class ZhongliConfig { - public class ZhongliConfig - { - public static readonly IConfigurationRoot Secrets = new ConfigurationBuilder() - .AddUserSecrets().Build(); + public static readonly IConfigurationRoot Secrets = new ConfigurationBuilder() + .AddUserSecrets().Build(); - public BotConfig Debug { get; init; } + public BotConfig Debug { get; init; } - public BotConfig Release { get; init; } + public BotConfig Release { get; init; } - public static T GetValue(string key) => Secrets.GetSection("Debug").GetValue(key); + public static T GetValue(string key) => Secrets.GetSection("Debug").GetValue(key); - public static BotConfig Configuration { get; } = + public static BotConfig Configuration { get; } = #if DEBUG - Secrets.GetSection(nameof(Debug)).Get(); + Secrets.GetSection(nameof(Debug)).Get(); #else Secrets.GetSection(nameof(Release)).Get(); #endif - } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Authorization/AccessType.cs b/Zhongli.Data/Models/Authorization/AccessType.cs index 37aef80..e889a19 100644 --- a/Zhongli.Data/Models/Authorization/AccessType.cs +++ b/Zhongli.Data/Models/Authorization/AccessType.cs @@ -1,8 +1,7 @@ -namespace Zhongli.Data.Models.Authorization +namespace Zhongli.Data.Models.Authorization; + +public enum AccessType { - public enum AccessType - { - Allow, - Deny - } + Allow, + Deny } \ No newline at end of file diff --git a/Zhongli.Data/Models/Authorization/AuthorizationGroup.cs b/Zhongli.Data/Models/Authorization/AuthorizationGroup.cs index ab70ff3..2659a48 100644 --- a/Zhongli.Data/Models/Authorization/AuthorizationGroup.cs +++ b/Zhongli.Data/Models/Authorization/AuthorizationGroup.cs @@ -3,27 +3,26 @@ using Zhongli.Data.Models.Criteria; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Authorization +namespace Zhongli.Data.Models.Authorization; + +public class AuthorizationGroup : IModerationAction { - public class AuthorizationGroup : IModerationAction - { - protected AuthorizationGroup() { } + protected AuthorizationGroup() { } - public AuthorizationGroup(AuthorizationScope scope, AccessType access, ICollection rules) - { - Scope = scope; - Access = access; - Collection = rules; - } + public AuthorizationGroup(AuthorizationScope scope, AccessType access, ICollection rules) + { + Scope = scope; + Access = access; + Collection = rules; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public AccessType Access { get; set; } + public AccessType Access { get; set; } - public AuthorizationScope Scope { get; set; } + public AuthorizationScope Scope { get; set; } - public virtual ICollection Collection { get; set; } = new List(); + public virtual ICollection Collection { get; set; } = new List(); - public virtual ModerationAction Action { get; set; } - } + public virtual ModerationAction Action { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Authorization/AuthorizationScope.cs b/Zhongli.Data/Models/Authorization/AuthorizationScope.cs index 9aaa05b..a04b647 100644 --- a/Zhongli.Data/Models/Authorization/AuthorizationScope.cs +++ b/Zhongli.Data/Models/Authorization/AuthorizationScope.cs @@ -1,30 +1,29 @@ using System; using System.ComponentModel; -namespace Zhongli.Data.Models.Authorization +namespace Zhongli.Data.Models.Authorization; + +[Flags] +public enum AuthorizationScope { - [Flags] - public enum AuthorizationScope - { - None = 0, + None = 0, - [Description("All permissions. Dangerous!")] - All = 1 << 0, - Warning = 1 << 1, - Mute = 1 << 2, - Kick = 1 << 3, - Ban = 1 << 4, + [Description("All permissions. Dangerous!")] + All = 1 << 0, + Warning = 1 << 1, + Mute = 1 << 2, + Kick = 1 << 3, + Ban = 1 << 4, - [Description("Allows configuration of settings.")] - Configuration = 1 << 5, + [Description("Allows configuration of settings.")] + Configuration = 1 << 5, - [Description("Allows using the quote feature.")] - Quote = 1 << 6, + [Description("Allows using the quote feature.")] + Quote = 1 << 6, - [Description("Allows warning, mute, kick, and ban.")] - Moderator = Warning | Mute | Kick | Ban, + [Description("Allows warning, mute, kick, and ban.")] + Moderator = Warning | Mute | Kick | Ban, - [Description("Allows warning and mute.")] - Helper = Warning | Mute - } + [Description("Allows warning and mute.")] + Helper = Warning | Mute } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/ChannelCriterion.cs b/Zhongli.Data/Models/Criteria/ChannelCriterion.cs index fbc986c..c8bade3 100644 --- a/Zhongli.Data/Models/Criteria/ChannelCriterion.cs +++ b/Zhongli.Data/Models/Criteria/ChannelCriterion.cs @@ -1,21 +1,20 @@ using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public class ChannelCriterion : Criterion, IGuildChannelEntity { - public class ChannelCriterion : Criterion, IGuildChannelEntity - { - protected ChannelCriterion() { } + protected ChannelCriterion() { } - public ChannelCriterion(ulong channelId, bool isCategory) - { - ChannelId = channelId; - IsCategory = isCategory; - } + public ChannelCriterion(ulong channelId, bool isCategory) + { + ChannelId = channelId; + IsCategory = isCategory; + } - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public bool IsCategory { get; set; } + public bool IsCategory { get; set; } - public override string ToString() => this.MentionChannel(); - } + public override string ToString() => this.MentionChannel(); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/Criterion.cs b/Zhongli.Data/Models/Criteria/Criterion.cs index aa26322..e77afa2 100644 --- a/Zhongli.Data/Models/Criteria/Criterion.cs +++ b/Zhongli.Data/Models/Criteria/Criterion.cs @@ -1,9 +1,8 @@ using System; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public abstract class Criterion { - public abstract class Criterion - { - public Guid Id { get; set; } - } + public Guid Id { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/ICriteriaOptions.cs b/Zhongli.Data/Models/Criteria/ICriteriaOptions.cs index f556355..53eeeb6 100644 --- a/Zhongli.Data/Models/Criteria/ICriteriaOptions.cs +++ b/Zhongli.Data/Models/Criteria/ICriteriaOptions.cs @@ -2,16 +2,15 @@ using Discord; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public interface ICriteriaOptions { - public interface ICriteriaOptions - { - GuildPermission Permission { get; set; } + GuildPermission Permission { get; set; } - IEnumerable? Channels { get; set; } + IEnumerable? Channels { get; set; } - IEnumerable? Users { get; set; } + IEnumerable? Users { get; set; } - IEnumerable? Roles { get; set; } - } + IEnumerable? Roles { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/PermissionCriterion.cs b/Zhongli.Data/Models/Criteria/PermissionCriterion.cs index ce1e738..2e64d53 100644 --- a/Zhongli.Data/Models/Criteria/PermissionCriterion.cs +++ b/Zhongli.Data/Models/Criteria/PermissionCriterion.cs @@ -1,16 +1,15 @@ using Humanizer; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public class PermissionCriterion : Criterion, IPermissionEntity { - public class PermissionCriterion : Criterion, IPermissionEntity - { - protected PermissionCriterion() { } + protected PermissionCriterion() { } - public PermissionCriterion(GuildPermission permission) { Permission = permission; } + public PermissionCriterion(GuildPermission permission) { Permission = permission; } - public GuildPermission Permission { get; set; } + public GuildPermission Permission { get; set; } - public override string ToString() => Permission.Humanize(); - } + public override string ToString() => Permission.Humanize(); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/RoleCriterion.cs b/Zhongli.Data/Models/Criteria/RoleCriterion.cs index 20eaa8d..c1ff9da 100644 --- a/Zhongli.Data/Models/Criteria/RoleCriterion.cs +++ b/Zhongli.Data/Models/Criteria/RoleCriterion.cs @@ -1,22 +1,21 @@ using Discord; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public class RoleCriterion : Criterion, IRoleEntity { - public class RoleCriterion : Criterion, IRoleEntity - { - protected RoleCriterion() { } + protected RoleCriterion() { } - public RoleCriterion(IRole role) - { - RoleId = role.Id; - GuildId = role.Guild.Id; - } + public RoleCriterion(IRole role) + { + RoleId = role.Id; + GuildId = role.Guild.Id; + } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong RoleId { get; set; } + public ulong RoleId { get; set; } - public override string ToString() => this.MentionRole(); - } + public override string ToString() => this.MentionRole(); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Criteria/UserCriterion.cs b/Zhongli.Data/Models/Criteria/UserCriterion.cs index acb61ce..dca50d0 100644 --- a/Zhongli.Data/Models/Criteria/UserCriterion.cs +++ b/Zhongli.Data/Models/Criteria/UserCriterion.cs @@ -1,15 +1,14 @@ using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Criteria +namespace Zhongli.Data.Models.Criteria; + +public class UserCriterion : Criterion, IUserEntity { - public class UserCriterion : Criterion, IUserEntity - { - protected UserCriterion() { } + protected UserCriterion() { } - public UserCriterion(ulong userId) { UserId = userId; } + public UserCriterion(ulong userId) { UserId = userId; } - public ulong UserId { get; set; } + public ulong UserId { get; set; } - public override string ToString() => this.MentionUser(); - } + public override string ToString() => this.MentionUser(); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/EntityExtensions.cs b/Zhongli.Data/Models/Discord/EntityExtensions.cs index fdaf4e2..3cf76d8 100644 --- a/Zhongli.Data/Models/Discord/EntityExtensions.cs +++ b/Zhongli.Data/Models/Discord/EntityExtensions.cs @@ -4,32 +4,31 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Zhongli.Data.Models.Discord.Message; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public static class EntityExtensions { - public static class EntityExtensions - { - public static string JumpUrl(this IMessageEntity entity) - => $"https://discord.com/channels/{entity.GuildId}/{entity.ChannelId}/{entity.MessageId}"; + public static string JumpUrl(this IMessageEntity entity) + => $"https://discord.com/channels/{entity.GuildId}/{entity.ChannelId}/{entity.MessageId}"; - public static string MentionChannel(this IChannelEntity entity) - => $"<#{entity.ChannelId}>"; + public static string MentionChannel(this IChannelEntity entity) + => $"<#{entity.ChannelId}>"; - public static string MentionRole(this IRoleEntity entity) - => $"<@&{entity.RoleId}>"; + public static string MentionRole(this IRoleEntity entity) + => $"<@&{entity.RoleId}>"; - public static string MentionUser(this IUserEntity entity) - => $"<@{entity.UserId}>"; + public static string MentionUser(this IUserEntity entity) + => $"<@{entity.UserId}>"; - public static Thumbnail ToThumbnail(this EmbedThumbnail thumbnail) - => new(thumbnail); + public static Thumbnail ToThumbnail(this EmbedThumbnail thumbnail) + => new(thumbnail); - public static void AddUserNavigation( - this EntityTypeBuilder builder, - Expression> navigationExpression) where T : class, IGuildUserEntity - { - builder - .HasOne(navigationExpression).WithMany() - .HasForeignKey(r => new { r.UserId, r.GuildId }); - } + public static void AddUserNavigation( + this EntityTypeBuilder builder, + Expression> navigationExpression) where T : class, IGuildUserEntity + { + builder + .HasOne(navigationExpression).WithMany() + .HasForeignKey(r => new { r.UserId, r.GuildId }); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/EnumChannel.cs b/Zhongli.Data/Models/Discord/EnumChannel.cs index 7bd6dc2..bd0ef33 100644 --- a/Zhongli.Data/Models/Discord/EnumChannel.cs +++ b/Zhongli.Data/Models/Discord/EnumChannel.cs @@ -2,32 +2,31 @@ using System.ComponentModel.DataAnnotations.Schema; using Discord; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public abstract class EnumChannel { - public abstract class EnumChannel - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public int IntType { get; set; } + public int IntType { get; set; } - public ulong ChannelId { get; set; } - } + public ulong ChannelId { get; set; } +} - public class EnumChannel : EnumChannel where T : Enum - { - protected EnumChannel() { } +public class EnumChannel : EnumChannel where T : Enum +{ + protected EnumChannel() { } - public EnumChannel(T type, IChannel channel) - { - Type = type; - ChannelId = channel.Id; - } + public EnumChannel(T type, IChannel channel) + { + Type = type; + ChannelId = channel.Id; + } - [NotMapped] - public T Type - { - get => (T) (object) IntType; - set => IntType = (int) (object) value; - } + [NotMapped] + public T Type + { + get => (T) (object) IntType; + set => IntType = (int) (object) value; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/GuildEntity.cs b/Zhongli.Data/Models/Discord/GuildEntity.cs index 855779c..66ec467 100644 --- a/Zhongli.Data/Models/Discord/GuildEntity.cs +++ b/Zhongli.Data/Models/Discord/GuildEntity.cs @@ -7,44 +7,43 @@ using Zhongli.Data.Models.TimeTracking; using Zhongli.Data.Models.VoiceChat; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public class GuildEntity { - public class GuildEntity - { - protected GuildEntity() { } + protected GuildEntity() { } - public GuildEntity(ulong id) { Id = id; } + public GuildEntity(ulong id) { Id = id; } - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public ulong Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public ulong Id { get; set; } - public virtual GenshinTimeTrackingRules? GenshinRules { get; set; } + public virtual GenshinTimeTrackingRules? GenshinRules { get; set; } - public virtual ICollection AuthorizationGroups { get; set; } - = new List(); + public virtual ICollection AuthorizationGroups { get; set; } + = new List(); - public virtual ICollection DeleteLogs { get; set; } - = new List(); + public virtual ICollection DeleteLogs { get; set; } + = new List(); - public virtual ICollection MessageLogs { get; set; } - = new List(); + public virtual ICollection MessageLogs { get; set; } + = new List(); - public virtual ICollection ReactionLogs { get; set; } - = new List(); + public virtual ICollection ReactionLogs { get; set; } + = new List(); - public virtual ICollection ReprimandHistory { get; set; } - = new List(); + public virtual ICollection ReprimandHistory { get; set; } + = new List(); - public virtual ICollection TemporaryRoles { get; set; } - = new List(); + public virtual ICollection TemporaryRoles { get; set; } + = new List(); - public virtual ICollection TemporaryRoleMembers { get; set; } - = new List(); + public virtual ICollection TemporaryRoleMembers { get; set; } + = new List(); - public virtual LoggingRules LoggingRules { get; set; } = null!; + public virtual LoggingRules LoggingRules { get; set; } = null!; - public virtual ModerationRules ModerationRules { get; set; } = null!; + public virtual ModerationRules ModerationRules { get; set; } = null!; - public virtual VoiceChatRules? VoiceChatRules { get; set; } - } + public virtual VoiceChatRules? VoiceChatRules { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/GuildPermission.cs b/Zhongli.Data/Models/Discord/GuildPermission.cs index 895aa1a..1351c70 100644 --- a/Zhongli.Data/Models/Discord/GuildPermission.cs +++ b/Zhongli.Data/Models/Discord/GuildPermission.cs @@ -1,91 +1,90 @@ using System; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +[Flags] +public enum GuildPermission { - [Flags] - public enum GuildPermission - { - None = 0, + None = 0, - /// Allows kicking members. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - KickMembers = 2, + /// Allows kicking members. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + KickMembers = 2, - /// Allows banning members. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - BanMembers = 4, + /// Allows banning members. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + BanMembers = 4, - /// - /// Allows all permissions and bypasses channel permission overwrites. - /// - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - Administrator = 8, + /// + /// Allows all permissions and bypasses channel permission overwrites. + /// + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + Administrator = 8, - /// Allows management and editing of channels. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageChannels = 16, // 0x0000000000000010 + /// Allows management and editing of channels. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageChannels = 16, // 0x0000000000000010 - /// Allows management and editing of the guild. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageGuild = 32, // 0x0000000000000020 + /// Allows management and editing of the guild. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageGuild = 32, // 0x0000000000000020 - /// Allows for deletion of other users messages. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageMessages = 8192, // 0x0000000000002000 + /// Allows for deletion of other users messages. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageMessages = 8192, // 0x0000000000002000 - /// Allows for muting members in a voice channel. - MuteMembers = 4194304, // 0x0000000000400000 + /// Allows for muting members in a voice channel. + MuteMembers = 4194304, // 0x0000000000400000 - /// - /// Allows for deafening of members in a voice channel. - /// - DeafenMembers = 8388608, // 0x0000000000800000 + /// + /// Allows for deafening of members in a voice channel. + /// + DeafenMembers = 8388608, // 0x0000000000800000 - /// - /// Allows for moving of members between voice channels. - /// - MoveMembers = 16777216, // 0x0000000001000000 + /// + /// Allows for moving of members between voice channels. + /// + MoveMembers = 16777216, // 0x0000000001000000 - /// Allows for modification of other users nicknames. - ManageNicknames = 134217728, // 0x0000000008000000 + /// Allows for modification of other users nicknames. + ManageNicknames = 134217728, // 0x0000000008000000 - /// Allows management and editing of roles. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageRoles = 268435456, // 0x0000000010000000 + /// Allows management and editing of roles. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageRoles = 268435456, // 0x0000000010000000 - /// Allows management and editing of webhooks. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageWebhooks = 536870912, // 0x0000000020000000 + /// Allows management and editing of webhooks. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageWebhooks = 536870912, // 0x0000000020000000 - /// Allows management and editing of emojis. - /// - /// This permission requires the owner account to use two-factor - /// authentication when used on a guild that has server-wide 2FA enabled. - /// - ManageEmojis = 1073741824 // 0x0000000040000000 - } + /// Allows management and editing of emojis. + /// + /// This permission requires the owner account to use two-factor + /// authentication when used on a guild that has server-wide 2FA enabled. + /// + ManageEmojis = 1073741824 // 0x0000000040000000 } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/GuildUserEntity.cs b/Zhongli.Data/Models/Discord/GuildUserEntity.cs index 40b6b2a..79b31f1 100644 --- a/Zhongli.Data/Models/Discord/GuildUserEntity.cs +++ b/Zhongli.Data/Models/Discord/GuildUserEntity.cs @@ -4,54 +4,50 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public class GuildUserEntity { - public class GuildUserEntity - { - protected GuildUserEntity() { } + protected GuildUserEntity() { } - public GuildUserEntity(IGuildUser user) : this(user, user.Guild) - { - JoinedAt = user.JoinedAt?.ToUniversalTime(); - Nickname = user.Nickname; - } + public GuildUserEntity(IGuildUser user) : this(user, user.Guild) + { + JoinedAt = user.JoinedAt?.ToUniversalTime(); + Nickname = user.Nickname; + } - public GuildUserEntity(IUser user, IGuild guild) - { - Id = user.Id; - GuildId = guild.Id; + public GuildUserEntity(IUser user, IGuild guild) + { + Id = user.Id; + GuildId = guild.Id; - CreatedAt = user.CreatedAt.ToUniversalTime(); - Username = user.Username; - DiscriminatorValue = user.DiscriminatorValue; - } + CreatedAt = user.CreatedAt.ToUniversalTime(); + Username = user.Username; + DiscriminatorValue = user.DiscriminatorValue; + } - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public ulong Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public ulong Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset? JoinedAt { get; set; } + public DateTimeOffset? JoinedAt { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public string Username { get; set; } + public string Username { get; set; } - public string? Nickname { get; set; } + public string? Nickname { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public ulong GuildId { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public ulong GuildId { get; set; } - public ushort DiscriminatorValue { get; set; } + public ushort DiscriminatorValue { get; set; } - public override string ToString() => $"{Username}#{DiscriminatorValue}"; - } + public override string ToString() => $"{Username}#{DiscriminatorValue}"; +} - public class GuildUserEntityConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(w => new { w.Id, w.GuildId }); - } - } +public class GuildUserEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.HasKey(w => new { w.Id, w.GuildId }); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IChannelEntity.cs b/Zhongli.Data/Models/Discord/IChannelEntity.cs index e92bbb4..8a59712 100644 --- a/Zhongli.Data/Models/Discord/IChannelEntity.cs +++ b/Zhongli.Data/Models/Discord/IChannelEntity.cs @@ -1,12 +1,11 @@ -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public interface IGuildChannelEntity : IChannelEntity { - public interface IGuildChannelEntity : IChannelEntity - { - bool IsCategory { get; set; } - } + bool IsCategory { get; set; } +} - public interface IChannelEntity - { - ulong ChannelId { get; set; } - } +public interface IChannelEntity +{ + ulong ChannelId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IGuildEntity.cs b/Zhongli.Data/Models/Discord/IGuildEntity.cs index 97058ec..fa7b958 100644 --- a/Zhongli.Data/Models/Discord/IGuildEntity.cs +++ b/Zhongli.Data/Models/Discord/IGuildEntity.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public interface IGuildEntity { - public interface IGuildEntity - { - ulong GuildId { get; set; } - } + ulong GuildId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IGuildUserEntity.cs b/Zhongli.Data/Models/Discord/IGuildUserEntity.cs index 8c763bb..5164751 100644 --- a/Zhongli.Data/Models/Discord/IGuildUserEntity.cs +++ b/Zhongli.Data/Models/Discord/IGuildUserEntity.cs @@ -1,4 +1,3 @@ -namespace Zhongli.Data.Models.Discord -{ - public interface IGuildUserEntity : IGuildEntity, IUserEntity { } -} \ No newline at end of file +namespace Zhongli.Data.Models.Discord; + +public interface IGuildUserEntity : IGuildEntity, IUserEntity { } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IPermissionEntity.cs b/Zhongli.Data/Models/Discord/IPermissionEntity.cs index 56f8f8b..e51605d 100644 --- a/Zhongli.Data/Models/Discord/IPermissionEntity.cs +++ b/Zhongli.Data/Models/Discord/IPermissionEntity.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public interface IPermissionEntity { - public interface IPermissionEntity - { - GuildPermission Permission { get; set; } - } + GuildPermission Permission { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IRoleEntity.cs b/Zhongli.Data/Models/Discord/IRoleEntity.cs index 6a20cb6..a5d395e 100644 --- a/Zhongli.Data/Models/Discord/IRoleEntity.cs +++ b/Zhongli.Data/Models/Discord/IRoleEntity.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public interface IRoleEntity : IGuildEntity { - public interface IRoleEntity : IGuildEntity - { - ulong RoleId { get; set; } - } + ulong RoleId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/IUserEntity.cs b/Zhongli.Data/Models/Discord/IUserEntity.cs index f86225d..ca6d199 100644 --- a/Zhongli.Data/Models/Discord/IUserEntity.cs +++ b/Zhongli.Data/Models/Discord/IUserEntity.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public interface IUserEntity { - public interface IUserEntity - { - ulong UserId { get; set; } - } + ulong UserId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Message/Attachment.cs b/Zhongli.Data/Models/Discord/Message/Attachment.cs index b3e54d8..c9863b1 100644 --- a/Zhongli.Data/Models/Discord/Message/Attachment.cs +++ b/Zhongli.Data/Models/Discord/Message/Attachment.cs @@ -4,38 +4,37 @@ using Discord; using Image = Zhongli.Data.Models.Discord.Message.IImage; -namespace Zhongli.Data.Models.Discord.Message +namespace Zhongli.Data.Models.Discord.Message; + +public class Attachment : IAttachment, IImage { - public class Attachment : IAttachment, IImage - { - protected Attachment() { } + protected Attachment() { } - public Attachment(IAttachment attachment) - { - Id = attachment.Id; - Size = attachment.Size; - Height = attachment.Height; - Width = attachment.Width; - Filename = attachment.Filename; - ProxyUrl = attachment.ProxyUrl; - Url = attachment.Url; - } + public Attachment(IAttachment attachment) + { + Id = attachment.Id; + Size = attachment.Size; + Height = attachment.Height; + Width = attachment.Width; + Filename = attachment.Filename; + ProxyUrl = attachment.ProxyUrl; + Url = attachment.Url; + } - [Key] public Guid AttachmentId { get; set; } + [Key] public Guid AttachmentId { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public ulong Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public ulong Id { get; set; } - public int Size { get; set; } + public int Size { get; set; } - public int? Height { get; set; } + public int? Height { get; set; } - public int? Width { get; set; } + public int? Width { get; set; } - public string Filename { get; set; } + public string Filename { get; set; } - public string ProxyUrl { get; set; } + public string ProxyUrl { get; set; } - public string Url { get; set; } - } + public string Url { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Message/Embed.cs b/Zhongli.Data/Models/Discord/Message/Embed.cs index 18d6dba..a60ec74 100644 --- a/Zhongli.Data/Models/Discord/Message/Embed.cs +++ b/Zhongli.Data/Models/Discord/Message/Embed.cs @@ -1,34 +1,33 @@ using System; using Discord; -namespace Zhongli.Data.Models.Discord.Message +namespace Zhongli.Data.Models.Discord.Message; + +public class Embed { - public class Embed - { - protected Embed() { } + protected Embed() { } - public Embed(IEmbed embed) - { - Type = embed.Type; - Description = embed.Description; - Title = embed.Title; - Url = embed.Url; - Thumbnail = embed.Thumbnail; - Color = embed.Color?.RawValue; - } + public Embed(IEmbed embed) + { + Type = embed.Type; + Description = embed.Description; + Title = embed.Title; + Url = embed.Url; + Thumbnail = embed.Thumbnail; + Color = embed.Color?.RawValue; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public EmbedType Type { get; set; } + public EmbedType Type { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? Title { get; set; } + public string? Title { get; set; } - public string? Url { get; set; } + public string? Url { get; set; } - public virtual Thumbnail? Thumbnail { get; set; } + public virtual Thumbnail? Thumbnail { get; set; } - public uint? Color { get; set; } - } + public uint? Color { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Message/IImage.cs b/Zhongli.Data/Models/Discord/Message/IImage.cs index ed9e75a..fe6ff41 100644 --- a/Zhongli.Data/Models/Discord/Message/IImage.cs +++ b/Zhongli.Data/Models/Discord/Message/IImage.cs @@ -1,13 +1,12 @@ -namespace Zhongli.Data.Models.Discord.Message +namespace Zhongli.Data.Models.Discord.Message; + +public interface IImage { - public interface IImage - { - int? Height { get; set; } + int? Height { get; set; } - int? Width { get; set; } + int? Width { get; set; } - string ProxyUrl { get; set; } + string ProxyUrl { get; set; } - string Url { get; set; } - } + string Url { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Message/IMessageEntity.cs b/Zhongli.Data/Models/Discord/Message/IMessageEntity.cs index f581b78..4600f08 100644 --- a/Zhongli.Data/Models/Discord/Message/IMessageEntity.cs +++ b/Zhongli.Data/Models/Discord/Message/IMessageEntity.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Discord.Message +namespace Zhongli.Data.Models.Discord.Message; + +public interface IMessageEntity : IChannelEntity, IGuildUserEntity { - public interface IMessageEntity : IChannelEntity, IGuildUserEntity - { - ulong MessageId { get; set; } - } + ulong MessageId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Message/Thumbnail.cs b/Zhongli.Data/Models/Discord/Message/Thumbnail.cs index 9dbe23d..8b37880 100644 --- a/Zhongli.Data/Models/Discord/Message/Thumbnail.cs +++ b/Zhongli.Data/Models/Discord/Message/Thumbnail.cs @@ -1,30 +1,29 @@ using System; using Discord; -namespace Zhongli.Data.Models.Discord.Message +namespace Zhongli.Data.Models.Discord.Message; + +public class Thumbnail : IImage { - public class Thumbnail : IImage - { - protected Thumbnail() { } + protected Thumbnail() { } - public Thumbnail(EmbedThumbnail thumbnail) - { - Url = thumbnail.Url; - ProxyUrl = thumbnail.ProxyUrl; - Height = thumbnail.Height; - Width = thumbnail.Width; - } + public Thumbnail(EmbedThumbnail thumbnail) + { + Url = thumbnail.Url; + ProxyUrl = thumbnail.ProxyUrl; + Height = thumbnail.Height; + Width = thumbnail.Width; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public int? Height { get; set; } + public int? Height { get; set; } - public int? Width { get; set; } + public int? Width { get; set; } - public string ProxyUrl { get; set; } + public string ProxyUrl { get; set; } - public string Url { get; set; } + public string Url { get; set; } - public static implicit operator Thumbnail(EmbedThumbnail thumbnail) => new(thumbnail); - } + public static implicit operator Thumbnail(EmbedThumbnail thumbnail) => new(thumbnail); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Reaction/EmojiEntity.cs b/Zhongli.Data/Models/Discord/Reaction/EmojiEntity.cs index 571439c..7abcc07 100644 --- a/Zhongli.Data/Models/Discord/Reaction/EmojiEntity.cs +++ b/Zhongli.Data/Models/Discord/Reaction/EmojiEntity.cs @@ -1,13 +1,12 @@ using Discord; -namespace Zhongli.Data.Models.Discord.Reaction +namespace Zhongli.Data.Models.Discord.Reaction; + +public class EmojiEntity : ReactionEntity { - public class EmojiEntity : ReactionEntity - { - protected EmojiEntity() { } + protected EmojiEntity() { } - public EmojiEntity(IEmote emote) : base(emote) { } + public EmojiEntity(IEmote emote) : base(emote) { } - public override string ToString() => Name; - } + public override string ToString() => Name; } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Reaction/EmoteEntity.cs b/Zhongli.Data/Models/Discord/Reaction/EmoteEntity.cs index 1e6caf9..2d85659 100644 --- a/Zhongli.Data/Models/Discord/Reaction/EmoteEntity.cs +++ b/Zhongli.Data/Models/Discord/Reaction/EmoteEntity.cs @@ -2,32 +2,31 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Discord.Reaction +namespace Zhongli.Data.Models.Discord.Reaction; + +public class EmoteEntity : ReactionEntity { - public class EmoteEntity : ReactionEntity - { - protected EmoteEntity() { } + protected EmoteEntity() { } - public EmoteEntity(Emote emote) : base(emote) - { - EmoteId = emote.Id; - IsAnimated = emote.Animated; - } + public EmoteEntity(Emote emote) : base(emote) + { + EmoteId = emote.Id; + IsAnimated = emote.Animated; + } - public bool IsAnimated { get; set; } + public bool IsAnimated { get; set; } - public ulong EmoteId { get; set; } + public ulong EmoteId { get; set; } - public override string ToString() => $"<{(IsAnimated ? "a" : string.Empty)}:{Name}:{EmoteId}>"; - } + public override string ToString() => $"<{(IsAnimated ? "a" : string.Empty)}:{Name}:{EmoteId}>"; +} - public class EmoteReactionConfiguration : IEntityTypeConfiguration +public class EmoteReactionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .HasIndex(e => e.EmoteId) - .IsUnique(); - } + builder + .HasIndex(e => e.EmoteId) + .IsUnique(); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/Reaction/ReactionEntity.cs b/Zhongli.Data/Models/Discord/Reaction/ReactionEntity.cs index 639b7e7..84b1e30 100644 --- a/Zhongli.Data/Models/Discord/Reaction/ReactionEntity.cs +++ b/Zhongli.Data/Models/Discord/Reaction/ReactionEntity.cs @@ -1,16 +1,15 @@ using System; using Discord; -namespace Zhongli.Data.Models.Discord.Reaction +namespace Zhongli.Data.Models.Discord.Reaction; + +public abstract class ReactionEntity : IEmote { - public abstract class ReactionEntity : IEmote - { - protected ReactionEntity() { } + protected ReactionEntity() { } - protected ReactionEntity(IEmote emote) { Name = emote.Name; } + protected ReactionEntity(IEmote emote) { Name = emote.Name; } - public Guid Id { get; set; } + public Guid Id { get; set; } - public string Name { get; set; } - } + public string Name { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/TemporaryRole.cs b/Zhongli.Data/Models/Discord/TemporaryRole.cs index 3289499..dd592b6 100644 --- a/Zhongli.Data/Models/Discord/TemporaryRole.cs +++ b/Zhongli.Data/Models/Discord/TemporaryRole.cs @@ -2,36 +2,35 @@ using Discord; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public class TemporaryRole : IRoleEntity, IExpirable, IModerationAction { - public class TemporaryRole : IRoleEntity, IExpirable, IModerationAction - { - protected TemporaryRole() { } + protected TemporaryRole() { } - public TemporaryRole(IRole role, TimeSpan length) - { - RoleId = role.Id; - GuildId = role.Guild.Id; + public TemporaryRole(IRole role, TimeSpan length) + { + RoleId = role.Id; + GuildId = role.Guild.Id; - Length = length; - StartedAt = DateTimeOffset.UtcNow; - ExpireAt = StartedAt + Length; - } + Length = length; + StartedAt = DateTimeOffset.UtcNow; + ExpireAt = StartedAt + Length; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public DateTimeOffset StartedAt { get; set; } + public DateTimeOffset StartedAt { get; set; } - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset? EndedAt { get; set; } - public DateTimeOffset? ExpireAt { get; set; } + public DateTimeOffset? ExpireAt { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public TimeSpan? Length { get; set; } + public TimeSpan? Length { get; set; } - public virtual ModerationAction Action { get; set; } + public virtual ModerationAction Action { get; set; } - public ulong RoleId { get; set; } - } + public ulong RoleId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Discord/TemporaryRoleMember.cs b/Zhongli.Data/Models/Discord/TemporaryRoleMember.cs index 7d12d70..cf230f2 100644 --- a/Zhongli.Data/Models/Discord/TemporaryRoleMember.cs +++ b/Zhongli.Data/Models/Discord/TemporaryRoleMember.cs @@ -4,49 +4,45 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Discord +namespace Zhongli.Data.Models.Discord; + +public class TemporaryRoleMember : IRoleEntity, IGuildUserEntity, IExpirable, IModerationAction { - public class TemporaryRoleMember : IRoleEntity, IGuildUserEntity, IExpirable, IModerationAction - { - protected TemporaryRoleMember() { } + protected TemporaryRoleMember() { } - public TemporaryRoleMember(IGuildUser user, IRole role, TimeSpan length) - { - UserId = user.Id; - RoleId = role.Id; - GuildId = role.Guild.Id; + public TemporaryRoleMember(IGuildUser user, IRole role, TimeSpan length) + { + UserId = user.Id; + RoleId = role.Id; + GuildId = role.Guild.Id; - Length = length; - StartedAt = DateTimeOffset.UtcNow; - ExpireAt = StartedAt + Length; - } + Length = length; + StartedAt = DateTimeOffset.UtcNow; + ExpireAt = StartedAt + Length; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual GuildUserEntity User { get; set; } + public virtual GuildUserEntity User { get; set; } - public DateTimeOffset StartedAt { get; set; } + public DateTimeOffset StartedAt { get; set; } - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset? EndedAt { get; set; } - public DateTimeOffset? ExpireAt { get; set; } + public DateTimeOffset? ExpireAt { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public TimeSpan? Length { get; set; } + public TimeSpan? Length { get; set; } - public virtual ModerationAction? Action { get; set; } + public virtual ModerationAction? Action { get; set; } - public ulong RoleId { get; set; } + public ulong RoleId { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class TemporaryRoleMemberConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.AddUserNavigation(r => r.User); - } - } +public class TemporaryRoleMemberConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(r => r.User); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/DeleteLog.cs b/Zhongli.Data/Models/Logging/DeleteLog.cs index 69f0233..19c6109 100644 --- a/Zhongli.Data/Models/Logging/DeleteLog.cs +++ b/Zhongli.Data/Models/Logging/DeleteLog.cs @@ -1,22 +1,21 @@ using System; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public abstract class DeleteLog : ILog, IModerationAction { - public abstract class DeleteLog : ILog, IModerationAction - { - protected DeleteLog() { } + protected DeleteLog() { } - protected DeleteLog(ActionDetails? details) - { - LogDate = DateTimeOffset.UtcNow; - Action = details?.ToModerationAction(); - } + protected DeleteLog(ActionDetails? details) + { + LogDate = DateTimeOffset.UtcNow; + Action = details?.ToModerationAction(); + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public DateTimeOffset LogDate { get; set; } + public DateTimeOffset LogDate { get; set; } - public virtual ModerationAction? Action { get; set; } - } + public virtual ModerationAction? Action { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/Log.cs b/Zhongli.Data/Models/Logging/Log.cs index 7d0577b..3f4ede1 100644 --- a/Zhongli.Data/Models/Logging/Log.cs +++ b/Zhongli.Data/Models/Logging/Log.cs @@ -1,9 +1,8 @@ using System; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public interface ILog { - public interface ILog - { - DateTimeOffset LogDate { get; set; } - } + DateTimeOffset LogDate { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/LogType.cs b/Zhongli.Data/Models/Logging/LogType.cs index 2a23230..3a07c5b 100644 --- a/Zhongli.Data/Models/Logging/LogType.cs +++ b/Zhongli.Data/Models/Logging/LogType.cs @@ -1,11 +1,10 @@ -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public enum LogType { - public enum LogType - { - MessageUpdated, - MessageDeleted, - ReactionAdded, - ReactionRemoved, - MessagesBulkDeleted - } + MessageUpdated, + MessageDeleted, + ReactionAdded, + ReactionRemoved, + MessagesBulkDeleted } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/LoggingRules.cs b/Zhongli.Data/Models/Logging/LoggingRules.cs index c8625af..452bc26 100644 --- a/Zhongli.Data/Models/Logging/LoggingRules.cs +++ b/Zhongli.Data/Models/Logging/LoggingRules.cs @@ -4,26 +4,25 @@ using Zhongli.Data.Models.Discord; using Zhongli.Data.Models.Moderation; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public class LoggingRules { - public class LoggingRules - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual ICollection LoggingExclusions { get; set; } - = new List(); + public virtual ICollection LoggingExclusions { get; set; } + = new List(); - public virtual ICollection> LoggingChannels { get; set; } - = new List>(); + public virtual ICollection> LoggingChannels { get; set; } + = new List>(); - public ReprimandNoticeType NotifyReprimands { get; set; } = ReprimandNoticeType.None; + public ReprimandNoticeType NotifyReprimands { get; set; } = ReprimandNoticeType.None; - public ReprimandNoticeType ShowAppealOnReprimands { get; set; } = ReprimandNoticeType.All; + public ReprimandNoticeType ShowAppealOnReprimands { get; set; } = ReprimandNoticeType.All; - public string? ReprimandAppealMessage { get; set; } + public string? ReprimandAppealMessage { get; set; } - public ulong GuildId { get; set; } - } + public ulong GuildId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/MessageDeleteLog.cs b/Zhongli.Data/Models/Logging/MessageDeleteLog.cs index 04568dc..e9b8b91 100644 --- a/Zhongli.Data/Models/Logging/MessageDeleteLog.cs +++ b/Zhongli.Data/Models/Logging/MessageDeleteLog.cs @@ -3,48 +3,47 @@ using Zhongli.Data.Models.Discord.Message; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public class MessageDeleteLog : DeleteLog, IMessageEntity { - public class MessageDeleteLog : DeleteLog, IMessageEntity - { - protected MessageDeleteLog() { } + protected MessageDeleteLog() { } - public MessageDeleteLog(IMessageEntity message, ActionDetails? details) : base(details) - { - ChannelId = message.ChannelId; - GuildId = message.GuildId; - MessageId = message.MessageId; - UserId = message.UserId; - } + public MessageDeleteLog(IMessageEntity message, ActionDetails? details) : base(details) + { + ChannelId = message.ChannelId; + GuildId = message.GuildId; + MessageId = message.MessageId; + UserId = message.UserId; + } - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong MessageId { get; set; } + public ulong MessageId { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class MessageDeleteLogConfiguration : IEntityTypeConfiguration +public class MessageDeleteLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(l => l.ChannelId) - .HasColumnName(nameof(IMessageEntity.ChannelId)); - - builder - .Property(l => l.GuildId) - .HasColumnName(nameof(IMessageEntity.GuildId)); - - builder - .Property(l => l.MessageId) - .HasColumnName(nameof(IMessageEntity.MessageId)); - - builder - .Property(l => l.UserId) - .HasColumnName(nameof(IMessageEntity.UserId)); - } + builder + .Property(l => l.ChannelId) + .HasColumnName(nameof(IMessageEntity.ChannelId)); + + builder + .Property(l => l.GuildId) + .HasColumnName(nameof(IMessageEntity.GuildId)); + + builder + .Property(l => l.MessageId) + .HasColumnName(nameof(IMessageEntity.MessageId)); + + builder + .Property(l => l.UserId) + .HasColumnName(nameof(IMessageEntity.UserId)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/MessageLog.cs b/Zhongli.Data/Models/Logging/MessageLog.cs index 498c377..912bada 100644 --- a/Zhongli.Data/Models/Logging/MessageLog.cs +++ b/Zhongli.Data/Models/Logging/MessageLog.cs @@ -9,81 +9,80 @@ using Attachment = Zhongli.Data.Models.Discord.Message.Attachment; using Embed = Zhongli.Data.Models.Discord.Message.Embed; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public class MessageLog : ILog, IMessageEntity { - public class MessageLog : ILog, IMessageEntity - { - protected MessageLog() { } + protected MessageLog() { } - public MessageLog(GuildUserEntity user, IUserMessage message) - { - LogDate = DateTimeOffset.UtcNow; + public MessageLog(GuildUserEntity user, IUserMessage message) + { + LogDate = DateTimeOffset.UtcNow; - User = user; - ChannelId = message.Channel.Id; + User = user; + ChannelId = message.Channel.Id; - Content = message.Content; - Attachments = message.Attachments.Select(a => new Attachment(a)).ToList(); - Embeds = message.Embeds.Select(e => new Embed(e)).ToList(); + Content = message.Content; + Attachments = message.Attachments.Select(a => new Attachment(a)).ToList(); + Embeds = message.Embeds.Select(e => new Embed(e)).ToList(); - CreatedAt = message.CreatedAt.ToUniversalTime(); - Timestamp = message.Timestamp.ToUniversalTime(); - EditedTimestamp = message.EditedTimestamp?.ToUniversalTime(); + CreatedAt = message.CreatedAt.ToUniversalTime(); + Timestamp = message.Timestamp.ToUniversalTime(); + EditedTimestamp = message.EditedTimestamp?.ToUniversalTime(); - MessageId = message.Id; - MentionedEveryone = message.MentionedEveryone; - MentionedRolesCount = message.MentionedUserIds.Count; - MentionedUsersCount = message.MentionedUserIds.Count; + MessageId = message.Id; + MentionedEveryone = message.MentionedEveryone; + MentionedRolesCount = message.MentionedUserIds.Count; + MentionedUsersCount = message.MentionedUserIds.Count; - ReferencedMessageId = message.ReferencedMessage?.Id; - } + ReferencedMessageId = message.ReferencedMessage?.Id; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - /// - public bool MentionedEveryone { get; set; } + /// + public bool MentionedEveryone { get; set; } - /// - public DateTimeOffset CreatedAt { get; set; } + /// + public DateTimeOffset CreatedAt { get; set; } - /// - public DateTimeOffset Timestamp { get; set; } + /// + public DateTimeOffset Timestamp { get; set; } - /// - public DateTimeOffset? EditedTimestamp { get; set; } + /// + public DateTimeOffset? EditedTimestamp { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual GuildUserEntity User { get; set; } + public virtual GuildUserEntity User { get; set; } - public virtual ICollection Attachments { get; set; } + public virtual ICollection Attachments { get; set; } - public virtual ICollection Embeds { get; set; } + public virtual ICollection Embeds { get; set; } - public int MentionedRolesCount { get; set; } + public int MentionedRolesCount { get; set; } - public int MentionedUsersCount { get; set; } + public int MentionedUsersCount { get; set; } - public virtual MessageLog? UpdatedLog { get; set; } + public virtual MessageLog? UpdatedLog { get; set; } - /// - public string? Content { get; set; } + /// + public string? Content { get; set; } - public ulong? ReferencedMessageId { get; set; } + public ulong? ReferencedMessageId { get; set; } - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public DateTimeOffset LogDate { get; set; } + public DateTimeOffset LogDate { get; set; } - public ulong MessageId { get; set; } + public ulong MessageId { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class MessageEntityConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(m => m.User); } - } +public class MessageEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(m => m.User); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/MessagesDeleteLog.cs b/Zhongli.Data/Models/Logging/MessagesDeleteLog.cs index cb88a56..ae0a8dd 100644 --- a/Zhongli.Data/Models/Logging/MessagesDeleteLog.cs +++ b/Zhongli.Data/Models/Logging/MessagesDeleteLog.cs @@ -7,39 +7,38 @@ using Zhongli.Data.Models.Discord.Message; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public class MessagesDeleteLog : DeleteLog, IChannelEntity, IGuildEntity { - public class MessagesDeleteLog : DeleteLog, IChannelEntity, IGuildEntity + protected MessagesDeleteLog() { } + + public MessagesDeleteLog(IEnumerable messages, IGuildChannel channel, + ActionDetails? details) : base(details) { - protected MessagesDeleteLog() { } + ChannelId = channel.Id; + GuildId = channel.Guild.Id; - public MessagesDeleteLog(IEnumerable messages, IGuildChannel channel, - ActionDetails? details) : base(details) - { - ChannelId = channel.Id; - GuildId = channel.Guild.Id; + Messages = messages.Select(m => new MessageDeleteLog(m, details)).ToList(); + } - Messages = messages.Select(m => new MessageDeleteLog(m, details)).ToList(); - } + public virtual ICollection Messages { get; set; } - public virtual ICollection Messages { get; set; } + public ulong ChannelId { get; set; } - public ulong ChannelId { get; set; } + public ulong GuildId { get; set; } +} - public ulong GuildId { get; set; } - } - - public class MessagesBulkDeleteLogConfiguration : IEntityTypeConfiguration +public class MessagesBulkDeleteLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(l => l.ChannelId) - .HasColumnName(nameof(IChannelEntity.ChannelId)); - - builder - .Property(l => l.GuildId) - .HasColumnName(nameof(IGuildEntity.GuildId)); - } + builder + .Property(l => l.ChannelId) + .HasColumnName(nameof(IChannelEntity.ChannelId)); + + builder + .Property(l => l.GuildId) + .HasColumnName(nameof(IGuildEntity.GuildId)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/ReactionDeleteLog.cs b/Zhongli.Data/Models/Logging/ReactionDeleteLog.cs index 6bdd495..a77db8b 100644 --- a/Zhongli.Data/Models/Logging/ReactionDeleteLog.cs +++ b/Zhongli.Data/Models/Logging/ReactionDeleteLog.cs @@ -5,54 +5,53 @@ using Zhongli.Data.Models.Discord.Reaction; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public class ReactionDeleteLog : DeleteLog, IReactionEntity { - public class ReactionDeleteLog : DeleteLog, IReactionEntity - { - protected ReactionDeleteLog() { } + protected ReactionDeleteLog() { } - public ReactionDeleteLog(ReactionEntity emote, SocketReaction reaction, ActionDetails? details) : base(details) - { - Emote = emote; + public ReactionDeleteLog(ReactionEntity emote, SocketReaction reaction, ActionDetails? details) : base(details) + { + Emote = emote; - var channel = (IGuildChannel) reaction.Channel; - ChannelId = channel.Id; - GuildId = channel.Guild.Id; + var channel = (IGuildChannel) reaction.Channel; + ChannelId = channel.Id; + GuildId = channel.Guild.Id; - MessageId = reaction.MessageId; - UserId = reaction.UserId; - } + MessageId = reaction.MessageId; + UserId = reaction.UserId; + } - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong MessageId { get; set; } + public ulong MessageId { get; set; } - public virtual ReactionEntity Emote { get; set; } + public virtual ReactionEntity Emote { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class ReactionDeleteLogConfiguration : IEntityTypeConfiguration +public class ReactionDeleteLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(l => l.ChannelId) - .HasColumnName(nameof(IReactionEntity.ChannelId)); - - builder - .Property(l => l.GuildId) - .HasColumnName(nameof(IReactionEntity.GuildId)); - - builder - .Property(l => l.MessageId) - .HasColumnName(nameof(IReactionEntity.MessageId)); - - builder - .Property(l => l.UserId) - .HasColumnName(nameof(IReactionEntity.UserId)); - } + builder + .Property(l => l.ChannelId) + .HasColumnName(nameof(IReactionEntity.ChannelId)); + + builder + .Property(l => l.GuildId) + .HasColumnName(nameof(IReactionEntity.GuildId)); + + builder + .Property(l => l.MessageId) + .HasColumnName(nameof(IReactionEntity.MessageId)); + + builder + .Property(l => l.UserId) + .HasColumnName(nameof(IReactionEntity.UserId)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Logging/ReactionLog.cs b/Zhongli.Data/Models/Logging/ReactionLog.cs index f2b2ec2..2f997dd 100644 --- a/Zhongli.Data/Models/Logging/ReactionLog.cs +++ b/Zhongli.Data/Models/Logging/ReactionLog.cs @@ -6,47 +6,46 @@ using Zhongli.Data.Models.Discord.Message; using Zhongli.Data.Models.Discord.Reaction; -namespace Zhongli.Data.Models.Logging +namespace Zhongli.Data.Models.Logging; + +public interface IReactionEntity : IMessageEntity { - public interface IReactionEntity : IMessageEntity - { - ReactionEntity Emote { get; set; } - } + ReactionEntity Emote { get; set; } +} - public class ReactionLog : ILog, IReactionEntity - { - protected ReactionLog() { } +public class ReactionLog : ILog, IReactionEntity +{ + protected ReactionLog() { } - public ReactionLog(GuildUserEntity user, SocketReaction reaction, ReactionEntity emote) - { - LogDate = DateTimeOffset.UtcNow; - User = user; - ChannelId = reaction.Channel.Id; - MessageId = reaction.MessageId; - Emote = emote; - } + public ReactionLog(GuildUserEntity user, SocketReaction reaction, ReactionEntity emote) + { + LogDate = DateTimeOffset.UtcNow; + User = user; + ChannelId = reaction.Channel.Id; + MessageId = reaction.MessageId; + Emote = emote; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual GuildUserEntity User { get; set; } + public virtual GuildUserEntity User { get; set; } - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public DateTimeOffset LogDate { get; set; } + public DateTimeOffset LogDate { get; set; } - public ulong MessageId { get; set; } + public ulong MessageId { get; set; } - public virtual ReactionEntity Emote { get; set; } + public virtual ReactionEntity Emote { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class ReactionLogConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(r => r.User); } - } +public class ReactionLogConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(r => r.User); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/AntiSpamRules.cs b/Zhongli.Data/Models/Moderation/AntiSpamRules.cs index 13104b4..cbe2199 100644 --- a/Zhongli.Data/Models/Moderation/AntiSpamRules.cs +++ b/Zhongli.Data/Models/Moderation/AntiSpamRules.cs @@ -1,26 +1,25 @@ using System; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Moderation +namespace Zhongli.Data.Models.Moderation; + +public class AntiSpamRules { - public class AntiSpamRules - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public int? DuplicateTolerance { get; set; } + public int? DuplicateTolerance { get; set; } - public TimeSpan? DuplicateMessageTime { get; set; } + public TimeSpan? DuplicateMessageTime { get; set; } - public TimeSpan? MessageSpamTime { get; set; } + public TimeSpan? MessageSpamTime { get; set; } - public uint? EmojiLimit { get; set; } + public uint? EmojiLimit { get; set; } - public uint? MessageLimit { get; set; } + public uint? MessageLimit { get; set; } - public uint? NewlineLimit { get; set; } + public uint? NewlineLimit { get; set; } - public ulong GuildId { get; set; } - } + public ulong GuildId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/BanAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/BanAction.cs index 8e2d30d..b69441a 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/BanAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/BanAction.cs @@ -4,31 +4,30 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class BanAction : ReprimandAction, IBan { - public class BanAction : ReprimandAction, IBan + public BanAction(uint deleteDays, TimeSpan? length) { - public BanAction(uint deleteDays, TimeSpan? length) - { - DeleteDays = deleteDays; - Length = length; - } + DeleteDays = deleteDays; + Length = length; + } - public override string Action - => $"Ban {Format.Bold(Length?.Humanize() ?? "indefinitely")} and delete {Format.Bold(DeleteDays + " days")} of messages"; + public override string Action + => $"Ban {Format.Bold(Length?.Humanize() ?? "indefinitely")} and delete {Format.Bold(DeleteDays + " days")} of messages"; - public uint DeleteDays { get; set; } + public uint DeleteDays { get; set; } - public TimeSpan? Length { get; set; } - } + public TimeSpan? Length { get; set; } +} - public class BanActionConfiguration : IEntityTypeConfiguration +public class BanActionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(t => t.Length) - .HasColumnName(nameof(BanAction.Length)); - } + builder + .Property(t => t.Length) + .HasColumnName(nameof(BanAction.Length)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/KickAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/KickAction.cs index 5bf4d6b..47cc029 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/KickAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/KickAction.cs @@ -1,9 +1,8 @@ using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class KickAction : ReprimandAction, IKick { - public class KickAction : ReprimandAction, IKick - { - public override string Action => nameof(Kick); - } + public override string Action => nameof(Kick); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/MuteAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/MuteAction.cs index 32c5fb6..123cc3c 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/MuteAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/MuteAction.cs @@ -4,24 +4,23 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class MuteAction : ReprimandAction, IMute { - public class MuteAction : ReprimandAction, IMute - { - public MuteAction(TimeSpan? length) { Length = length; } + public MuteAction(TimeSpan? length) { Length = length; } - public override string Action => $"Mute {Format.Bold(Length?.Humanize() ?? "indefinitely")}"; + public override string Action => $"Mute {Format.Bold(Length?.Humanize() ?? "indefinitely")}"; - public TimeSpan? Length { get; set; } - } + public TimeSpan? Length { get; set; } +} - public class MuteActionConfiguration : IEntityTypeConfiguration +public class MuteActionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(t => t.Length) - .HasColumnName(nameof(MuteAction.Length)); - } + builder + .Property(t => t.Length) + .HasColumnName(nameof(MuteAction.Length)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/NoteAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/NoteAction.cs index 70770a2..d3c443b 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/NoteAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/NoteAction.cs @@ -1,9 +1,8 @@ using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class NoteAction : ReprimandAction, INote { - public class NoteAction : ReprimandAction, INote - { - public override string Action => nameof(Note); - } + public override string Action => nameof(Note); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/NoticeAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/NoticeAction.cs index f6c25c6..4c56897 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/NoticeAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/NoticeAction.cs @@ -1,9 +1,8 @@ using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class NoticeAction : ReprimandAction, INotice { - public class NoticeAction : ReprimandAction, INotice - { - public override string Action => nameof(Notice); - } + public override string Action => nameof(Notice); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/ReprimandAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/ReprimandAction.cs index 1a8ac46..b5d4d59 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/ReprimandAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/ReprimandAction.cs @@ -1,11 +1,10 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public abstract class ReprimandAction { - public abstract class ReprimandAction - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public abstract string Action { get; } - } + public abstract string Action { get; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Actions/WarningAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Actions/WarningAction.cs index d3fe03a..f34717d 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Actions/WarningAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Actions/WarningAction.cs @@ -1,13 +1,12 @@ using Discord; -namespace Zhongli.Data.Models.Moderation.Infractions.Actions +namespace Zhongli.Data.Models.Moderation.Infractions.Actions; + +public class WarningAction : ReprimandAction, IWarning { - public class WarningAction : ReprimandAction, IWarning - { - public WarningAction(uint count) { Count = count; } + public WarningAction(uint count) { Count = count; } - public override string Action => $"Warn {Format.Bold(Count + " times")}"; + public override string Action => $"Warn {Format.Bold(Count + " times")}"; - public uint Count { get; set; } - } + public uint Count { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Censors/Censor.cs b/Zhongli.Data/Models/Moderation/Infractions/Censors/Censor.cs index 54b8fc9..19a3b20 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Censors/Censor.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Censors/Censor.cs @@ -7,42 +7,41 @@ using Zhongli.Data.Models.Moderation.Infractions.Actions; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Data.Models.Moderation.Infractions.Censors +namespace Zhongli.Data.Models.Moderation.Infractions.Censors; + +public class Censor : Trigger, ICensor, ITriggerAction { - public class Censor : Trigger, ICensor, ITriggerAction - { - protected Censor() { } + protected Censor() { } - public Censor(string pattern, ReprimandAction? action, ICensorOptions? options) - : base(options) - { - Pattern = pattern; - Reprimand = action; + public Censor(string pattern, ReprimandAction? action, ICensorOptions? options) + : base(options) + { + Pattern = pattern; + Reprimand = action; - Options = options?.Flags ?? RegexOptions.None; - Silent = options?.Silent ?? false; - } + Options = options?.Flags ?? RegexOptions.None; + Silent = options?.Silent ?? false; + } - public Guid? ReprimandId { get; set; } + public Guid? ReprimandId { get; set; } - public bool Silent { get; set; } + public bool Silent { get; set; } - public virtual ICollection Exclusions { get; set; } + public virtual ICollection Exclusions { get; set; } - public RegexOptions Options { get; set; } + public RegexOptions Options { get; set; } - public string Pattern { get; set; } + public string Pattern { get; set; } - public virtual ReprimandAction? Reprimand { get; set; } - } + public virtual ReprimandAction? Reprimand { get; set; } +} - public class CensorConfiguration : IEntityTypeConfiguration +public class CensorConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(t => t.ReprimandId) - .HasColumnName(nameof(ITriggerAction.ReprimandId)); - } + builder + .Property(t => t.ReprimandId) + .HasColumnName(nameof(ITriggerAction.ReprimandId)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensor.cs b/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensor.cs index bbd2e54..1fbbfc6 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensor.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensor.cs @@ -1,11 +1,10 @@ using System.Text.RegularExpressions; -namespace Zhongli.Data.Models.Moderation.Infractions.Censors +namespace Zhongli.Data.Models.Moderation.Infractions.Censors; + +public interface ICensor { - public interface ICensor - { - RegexOptions Options { get; set; } + RegexOptions Options { get; set; } - string Pattern { get; set; } - } + string Pattern { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensorOptions.cs b/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensorOptions.cs index 3b18178..dc244bd 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensorOptions.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Censors/ICensorOptions.cs @@ -1,12 +1,11 @@ using System.Text.RegularExpressions; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Data.Models.Moderation.Infractions.Censors +namespace Zhongli.Data.Models.Moderation.Infractions.Censors; + +public interface ICensorOptions : ITrigger { - public interface ICensorOptions : ITrigger - { - public bool Silent { get; set; } + public bool Silent { get; set; } - public RegexOptions Flags { get; set; } - } + public RegexOptions Flags { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/IBan.cs b/Zhongli.Data/Models/Moderation/Infractions/IBan.cs index 7ad08b8..ca34d0a 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/IBan.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/IBan.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IBan : ILength { - public interface IBan : ILength - { - uint DeleteDays { get; set; } - } + uint DeleteDays { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/IExpirable.cs b/Zhongli.Data/Models/Moderation/Infractions/IExpirable.cs index c160c53..93235d7 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/IExpirable.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/IExpirable.cs @@ -1,15 +1,14 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IExpirable : ILength { - public interface IExpirable : ILength - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public DateTimeOffset StartedAt { get; set; } + public DateTimeOffset StartedAt { get; set; } - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset? EndedAt { get; set; } - public DateTimeOffset? ExpireAt { get; set; } - } + public DateTimeOffset? ExpireAt { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/IKick.cs b/Zhongli.Data/Models/Moderation/Infractions/IKick.cs index 0cbaf8a..6675d1e 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/IKick.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/IKick.cs @@ -1,4 +1,3 @@ -namespace Zhongli.Data.Models.Moderation.Infractions -{ - public interface IKick { } -} \ No newline at end of file +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IKick { } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/ILength.cs b/Zhongli.Data/Models/Moderation/Infractions/ILength.cs index d7ce26a..7ce8c7a 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/ILength.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/ILength.cs @@ -1,9 +1,8 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface ILength { - public interface ILength - { - TimeSpan? Length { get; set; } - } + TimeSpan? Length { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/IMute.cs b/Zhongli.Data/Models/Moderation/Infractions/IMute.cs index 4f7aa30..7ed14eb 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/IMute.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/IMute.cs @@ -1,4 +1,3 @@ -namespace Zhongli.Data.Models.Moderation.Infractions -{ - public interface IMute : ILength { } -} \ No newline at end of file +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IMute : ILength { } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/INote.cs b/Zhongli.Data/Models/Moderation/Infractions/INote.cs index 6a0155b..c9b5920 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/INote.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/INote.cs @@ -1,4 +1,3 @@ -namespace Zhongli.Data.Models.Moderation.Infractions -{ - public interface INote { } -} \ No newline at end of file +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface INote { } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/INotice.cs b/Zhongli.Data/Models/Moderation/Infractions/INotice.cs index 99cfb89..5905f3c 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/INotice.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/INotice.cs @@ -1,4 +1,3 @@ -namespace Zhongli.Data.Models.Moderation.Infractions -{ - public interface INotice { } -} \ No newline at end of file +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface INotice { } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/IWarning.cs b/Zhongli.Data/Models/Moderation/Infractions/IWarning.cs index 390cc1c..2c38680 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/IWarning.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/IWarning.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IWarning { - public interface IWarning - { - public uint Count { get; set; } - } + public uint Count { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/ModerationAction.cs b/Zhongli.Data/Models/Moderation/Infractions/ModerationAction.cs index 52f850e..af692b6 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/ModerationAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/ModerationAction.cs @@ -1,59 +1,54 @@ using System; -using System.ComponentModel.DataAnnotations; using Discord; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public interface IModerationAction { - public interface IModerationAction - { - public ModerationAction? Action { get; set; } - } + public ModerationAction? Action { get; set; } +} - public record ActionDetails(ulong ModeratorId, ulong GuildId, string? Reason = null) - { - public ActionDetails(IGuildUser user, string? reason) : this(user.Id, user.Guild.Id, reason) { } +public record ActionDetails(ulong ModeratorId, ulong GuildId, string? Reason = null) +{ + public ActionDetails(IGuildUser user, string? reason) : this(user.Id, user.Guild.Id, reason) { } - public ModerationAction ToModerationAction() => new(this); + public ModerationAction ToModerationAction() => new(this); - public static implicit operator ModerationAction(ActionDetails details) => new(details); - } + public static implicit operator ModerationAction(ActionDetails details) => new(details); +} - public class ModerationAction : IGuildUserEntity - { - protected ModerationAction() { } +public class ModerationAction : IGuildUserEntity +{ + protected ModerationAction() { } - public ModerationAction(IGuildUser user, string? reason = null) : this(new ActionDetails(user, reason)) { } + public ModerationAction(IGuildUser user, string? reason = null) : this(new ActionDetails(user, reason)) { } - public ModerationAction(ActionDetails details) - { - Date = DateTimeOffset.UtcNow; + public ModerationAction(ActionDetails details) + { + Date = DateTimeOffset.UtcNow; - (UserId, GuildId, Reason) = details; - } + (UserId, GuildId, Reason) = details; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public DateTimeOffset Date { get; set; } + public DateTimeOffset Date { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual GuildUserEntity Moderator { get; set; } + public virtual GuildUserEntity Moderator { get; set; } - public string? Reason { get; set; } + public string? Reason { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class ModerationActionConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.AddUserNavigation(m => m.Moderator); - } - } +public class ModerationActionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(m => m.Moderator); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/ModerationExtensions.cs b/Zhongli.Data/Models/Moderation/Infractions/ModerationExtensions.cs index 2230efb..fc60af2 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/ModerationExtensions.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/ModerationExtensions.cs @@ -1,18 +1,17 @@ using Discord; using Discord.Commands; -namespace Zhongli.Data.Models.Moderation.Infractions +namespace Zhongli.Data.Models.Moderation.Infractions; + +public static class ModerationExtensions { - public static class ModerationExtensions + public static T WithModerator(this T action, IGuildUser moderator) where T : IModerationAction { - public static T WithModerator(this T action, IGuildUser moderator) where T : IModerationAction - { - action.Action = new ModerationAction(moderator); - - return action; - } + action.Action = new ModerationAction(moderator); - public static T WithModerator(this T action, ICommandContext context) where T : IModerationAction - => action.WithModerator((IGuildUser) context.User); + return action; } + + public static T WithModerator(this T action, ICommandContext context) where T : IModerationAction + => action.WithModerator((IGuildUser) context.User); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Ban.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Ban.cs index 14ee99b..7af0679 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Ban.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Ban.cs @@ -1,16 +1,12 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Ban : ExpirableReprimand, IBan { - public class Ban : ExpirableReprimand, IBan - { - protected Ban() { } + protected Ban() { } - public Ban(uint deleteDays, TimeSpan? length, ReprimandDetails details) : base(length, details) - { - DeleteDays = deleteDays; - } + public Ban(uint deleteDays, TimeSpan? length, ReprimandDetails details) : base(length, details) { DeleteDays = deleteDays; } - public uint DeleteDays { get; set; } - } + public uint DeleteDays { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Censored.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Censored.cs index 3ecdbf1..1a19e40 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Censored.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Censored.cs @@ -1,17 +1,16 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands -{ - public class Censored : ExpirableReprimand - { - protected Censored() { } +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; - public Censored(string content, TimeSpan? length, ReprimandDetails details) - : base(length, details) - { - Content = content; - } +public class Censored : ExpirableReprimand +{ + protected Censored() { } - public string Content { get; set; } + public Censored(string content, TimeSpan? length, ReprimandDetails details) + : base(length, details) + { + Content = content; } + + public string Content { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ExpirableReprimand.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ExpirableReprimand.cs index b56495b..27cd78e 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ExpirableReprimand.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ExpirableReprimand.cs @@ -2,43 +2,42 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public abstract class ExpirableReprimand : Reprimand, IExpirable { - public abstract class ExpirableReprimand : Reprimand, IExpirable - { - protected ExpirableReprimand() { } + protected ExpirableReprimand() { } - protected ExpirableReprimand(TimeSpan? length, ReprimandDetails details) : base(details) - { - Length = length; - StartedAt = DateTimeOffset.UtcNow; - ExpireAt = StartedAt + Length; - } + protected ExpirableReprimand(TimeSpan? length, ReprimandDetails details) : base(details) + { + Length = length; + StartedAt = DateTimeOffset.UtcNow; + ExpireAt = StartedAt + Length; + } - public DateTimeOffset StartedAt { get; set; } + public DateTimeOffset StartedAt { get; set; } - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset? EndedAt { get; set; } - public DateTimeOffset? ExpireAt { get; set; } + public DateTimeOffset? ExpireAt { get; set; } - public TimeSpan? Length { get; set; } - } + public TimeSpan? Length { get; set; } +} - public class ExpireReprimandActionConfiguration : IEntityTypeConfiguration +public class ExpireReprimandActionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(r => r.EndedAt) - .HasColumnName(nameof(ExpirableReprimand.EndedAt)); - - builder - .Property(r => r.StartedAt) - .HasColumnName(nameof(ExpirableReprimand.StartedAt)); - - builder - .Property(r => r.Length) - .HasColumnName(nameof(ExpirableReprimand.Length)); - } + builder + .Property(r => r.EndedAt) + .HasColumnName(nameof(ExpirableReprimand.EndedAt)); + + builder + .Property(r => r.StartedAt) + .HasColumnName(nameof(ExpirableReprimand.StartedAt)); + + builder + .Property(r => r.Length) + .HasColumnName(nameof(ExpirableReprimand.Length)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Kick.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Kick.cs index 2ef1c2a..68125b0 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Kick.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Kick.cs @@ -1,9 +1,8 @@ -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Kick : Reprimand, IKick { - public class Kick : Reprimand, IKick - { - protected Kick() { } + protected Kick() { } - public Kick(ReprimandDetails details) : base(details) { } - } + public Kick(ReprimandDetails details) : base(details) { } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Mute.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Mute.cs index e3e0094..3e9a8c0 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Mute.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Mute.cs @@ -1,11 +1,10 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Mute : ExpirableReprimand, IMute { - public class Mute : ExpirableReprimand, IMute - { - protected Mute() { } + protected Mute() { } - public Mute(TimeSpan? length, ReprimandDetails details) : base(length, details) { } - } + public Mute(TimeSpan? length, ReprimandDetails details) : base(length, details) { } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Note.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Note.cs index ad3876e..bd2d2d1 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Note.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Note.cs @@ -1,9 +1,8 @@ -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Note : Reprimand, INote { - public class Note : Reprimand, INote - { - protected Note() { } + protected Note() { } - public Note(ReprimandDetails details) : base(details) { } - } + public Note(ReprimandDetails details) : base(details) { } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Notice.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Notice.cs index acb017f..83fd263 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Notice.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Notice.cs @@ -1,11 +1,10 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Notice : ExpirableReprimand, INotice { - public class Notice : ExpirableReprimand, INotice - { - protected Notice() { } + protected Notice() { } - public Notice(TimeSpan? length, ReprimandDetails details) : base(length, details) { } - } + public Notice(TimeSpan? length, ReprimandDetails details) : base(length, details) { } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Reprimand.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Reprimand.cs index 83c7554..6f44a09 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Reprimand.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Reprimand.cs @@ -4,48 +4,47 @@ using Zhongli.Data.Models.Discord; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public abstract class Reprimand : IModerationAction, IGuildUserEntity { - public abstract class Reprimand : IModerationAction, IGuildUserEntity - { - protected Reprimand() { } + protected Reprimand() { } - protected Reprimand(ReprimandDetails details) - { - Status = ReprimandStatus.Added; + protected Reprimand(ReprimandDetails details) + { + Status = ReprimandStatus.Added; - UserId = details.User.Id; - GuildId = details.Guild.Id; + UserId = details.User.Id; + GuildId = details.Guild.Id; - Action = details; - TriggerId = details.Trigger?.Id; - } + Action = details; + TriggerId = details.Trigger?.Id; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public Guid? TriggerId { get; set; } + public Guid? TriggerId { get; set; } - public virtual GuildEntity? Guild { get; set; } + public virtual GuildEntity? Guild { get; set; } - public virtual GuildUserEntity? User { get; set; } + public virtual GuildUserEntity? User { get; set; } - public virtual ModerationAction? ModifiedAction { get; set; } + public virtual ModerationAction? ModifiedAction { get; set; } - public ReprimandStatus Status { get; set; } + public ReprimandStatus Status { get; set; } - public virtual Trigger? Trigger { get; set; } + public virtual Trigger? Trigger { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public virtual ModerationAction? Action { get; set; } + public virtual ModerationAction? Action { get; set; } - public ulong UserId { get; set; } + public ulong UserId { get; set; } - public static implicit operator ReprimandResult(Reprimand reprimand) => new(reprimand); - } + public static implicit operator ReprimandResult(Reprimand reprimand) => new(reprimand); +} - public class ReprimandConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(r => r.User); } - } +public class ReprimandConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(r => r.User); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandDetails.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandDetails.cs index 12592e6..13360f3 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandDetails.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandDetails.cs @@ -2,13 +2,12 @@ using Discord; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public record ReprimandDetails(IUser User, IGuildUser Moderator, string? Reason, Trigger? Trigger = null) + : ActionDetails(Moderator.Id, Moderator.Guild.Id, Reason) { - public record ReprimandDetails(IUser User, IGuildUser Moderator, string? Reason, Trigger? Trigger = null) - : ActionDetails(Moderator.Id, Moderator.Guild.Id, Reason) - { - public IGuild Guild => Moderator.Guild; + public IGuild Guild => Moderator.Guild; - public async Task GetUserAsync() => await Moderator.Guild.GetUserAsync(User.Id); - } + public async Task GetUserAsync() => await Moderator.Guild.GetUserAsync(User.Id); } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandResult.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandResult.cs index 41ee5c8..66055a0 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandResult.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandResult.cs @@ -2,19 +2,18 @@ using System.Collections.Generic; using System.Linq; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class ReprimandResult { - public class ReprimandResult + public ReprimandResult(Reprimand primary, ReprimandResult? secondary = null) { - public ReprimandResult(Reprimand primary, ReprimandResult? secondary = null) - { - Primary = primary; - Secondary = secondary?.Secondary.Append(secondary.Primary) - ?? Array.Empty(); - } + Primary = primary; + Secondary = secondary?.Secondary.Append(secondary.Primary) + ?? Array.Empty(); + } - public IEnumerable Secondary { get; } + public IEnumerable Secondary { get; } - public Reprimand Primary { get; } - } + public Reprimand Primary { get; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandStatus.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandStatus.cs index 3dbc9d7..85cc82d 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandStatus.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/ReprimandStatus.cs @@ -1,12 +1,11 @@ -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public enum ReprimandStatus { - public enum ReprimandStatus - { - Unknown, - Added, - Expired, - Updated, - Hidden, - Deleted - } + Unknown, + Added, + Expired, + Updated, + Hidden, + Deleted } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Warning.cs b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Warning.cs index 068a4a1..b2e5755 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Warning.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Reprimands/Warning.cs @@ -1,16 +1,12 @@ using System; -namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands +namespace Zhongli.Data.Models.Moderation.Infractions.Reprimands; + +public class Warning : ExpirableReprimand, IWarning { - public class Warning : ExpirableReprimand, IWarning - { - protected Warning() { } + protected Warning() { } - public Warning(uint count, TimeSpan? length, ReprimandDetails details) : base(length, details) - { - Count = count; - } + public Warning(uint count, TimeSpan? length, ReprimandDetails details) : base(length, details) { Count = count; } - public uint Count { get; set; } - } + public uint Count { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITrigger.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITrigger.cs index 1412374..2e24690 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITrigger.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITrigger.cs @@ -1,9 +1,8 @@ -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public interface ITrigger { - public interface ITrigger - { - TriggerMode Mode { get; set; } + TriggerMode Mode { get; set; } - uint Amount { get; set; } - } + uint Amount { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITriggerAction.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITriggerAction.cs index e920b3b..92688c1 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITriggerAction.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ITriggerAction.cs @@ -1,12 +1,11 @@ using System; using Zhongli.Data.Models.Moderation.Infractions.Actions; -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public interface ITriggerAction { - public interface ITriggerAction - { - Guid? ReprimandId { get; set; } + Guid? ReprimandId { get; set; } - ReprimandAction? Reprimand { get; set; } - } + ReprimandAction? Reprimand { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ReprimandTrigger.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ReprimandTrigger.cs index d9d8df2..b438a04 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/ReprimandTrigger.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/ReprimandTrigger.cs @@ -3,33 +3,32 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Zhongli.Data.Models.Moderation.Infractions.Actions; -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public class ReprimandTrigger : Trigger, ITriggerAction { - public class ReprimandTrigger : Trigger, ITriggerAction - { - protected ReprimandTrigger() { } + protected ReprimandTrigger() { } - public ReprimandTrigger(ITrigger? options, TriggerSource source, - ReprimandAction reprimand) : base(options) - { - Source = source; - Reprimand = reprimand; - } + public ReprimandTrigger(ITrigger? options, TriggerSource source, + ReprimandAction reprimand) : base(options) + { + Source = source; + Reprimand = reprimand; + } - public Guid? ReprimandId { get; set; } + public Guid? ReprimandId { get; set; } - public TriggerSource Source { get; set; } + public TriggerSource Source { get; set; } - public virtual ReprimandAction Reprimand { get; set; } - } + public virtual ReprimandAction Reprimand { get; set; } +} - public class ReprimandTriggerConfiguration : IEntityTypeConfiguration +public class ReprimandTriggerConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(t => t.ReprimandId) - .HasColumnName(nameof(ITriggerAction.ReprimandId)); - } + builder + .Property(t => t.ReprimandId) + .HasColumnName(nameof(ITriggerAction.ReprimandId)); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/Trigger.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/Trigger.cs index 2e11dca..85dc310 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/Trigger.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/Trigger.cs @@ -2,36 +2,35 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public abstract class Trigger : ITrigger, IModerationAction { - public abstract class Trigger : ITrigger, IModerationAction - { - protected Trigger() { } + protected Trigger() { } - protected Trigger(ITrigger? options) - { - Mode = options?.Mode ?? TriggerMode.Exact; - Amount = options?.Amount ?? 1; - } + protected Trigger(ITrigger? options) + { + Mode = options?.Mode ?? TriggerMode.Exact; + Amount = options?.Amount ?? 1; + } - public Guid Id { get; set; } + public Guid Id { get; set; } - public bool IsActive { get; set; } + public bool IsActive { get; set; } - public virtual ModerationAction Action { get; set; } + public virtual ModerationAction Action { get; set; } - public TriggerMode Mode { get; set; } + public TriggerMode Mode { get; set; } - public uint Amount { get; set; } - } + public uint Amount { get; set; } +} - public class TriggerConfiguration : IEntityTypeConfiguration +public class TriggerConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder - .Property(t => t.IsActive) - .HasDefaultValue(true); - } + builder + .Property(t => t.IsActive) + .HasDefaultValue(true); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerMode.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerMode.cs index 31ac741..9141a9a 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerMode.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerMode.cs @@ -1,16 +1,15 @@ using System.ComponentModel; -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public enum TriggerMode { - public enum TriggerMode - { - [Description("Trigger exactly at the amount.")] - Exact, + [Description("Trigger exactly at the amount.")] + Exact, - [Description("Trigger on equal or greater than the amount.")] - Retroactive, + [Description("Trigger on equal or greater than the amount.")] + Retroactive, - [Description("Trigger on a multiple of the amount.")] - Multiple - } + [Description("Trigger on a multiple of the amount.")] + Multiple } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerSource.cs b/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerSource.cs index a7d1d77..df083c7 100644 --- a/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerSource.cs +++ b/Zhongli.Data/Models/Moderation/Infractions/Triggers/TriggerSource.cs @@ -1,9 +1,8 @@ -namespace Zhongli.Data.Models.Moderation.Infractions.Triggers +namespace Zhongli.Data.Models.Moderation.Infractions.Triggers; + +public enum TriggerSource { - public enum TriggerSource - { - Notice, - Warning, - Censored - } + Notice, + Warning, + Censored } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/ModerationRules.cs b/Zhongli.Data/Models/Moderation/ModerationRules.cs index 8016cde..a4047dd 100644 --- a/Zhongli.Data/Models/Moderation/ModerationRules.cs +++ b/Zhongli.Data/Models/Moderation/ModerationRules.cs @@ -4,37 +4,36 @@ using Zhongli.Data.Models.Discord; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Data.Models.Moderation +namespace Zhongli.Data.Models.Moderation; + +public class ModerationRules { - public class ModerationRules - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual AntiSpamRules? AntiSpamRules { get; set; } + public virtual AntiSpamRules? AntiSpamRules { get; set; } - public bool ReplaceMutes { get; set; } + public bool ReplaceMutes { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual ICollection CensorExclusions { get; set; } - = new List(); + public virtual ICollection CensorExclusions { get; set; } + = new List(); - public virtual ICollection> LoggingChannels { get; set; } - = new List>(); + public virtual ICollection> LoggingChannels { get; set; } + = new List>(); - public virtual ICollection Triggers { get; set; } - = new List(); + public virtual ICollection Triggers { get; set; } + = new List(); - public ReprimandOptions Options { get; set; } = ReprimandOptions.None; + public ReprimandOptions Options { get; set; } = ReprimandOptions.None; - public TimeSpan? CensorTimeRange { get; set; } + public TimeSpan? CensorTimeRange { get; set; } - public TimeSpan? NoticeExpiryLength { get; set; } + public TimeSpan? NoticeExpiryLength { get; set; } - public TimeSpan? WarningExpiryLength { get; set; } + public TimeSpan? WarningExpiryLength { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong? MuteRoleId { get; set; } - } + public ulong? MuteRoleId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/ReprimandNoticeType.cs b/Zhongli.Data/Models/Moderation/ReprimandNoticeType.cs index f7d4ea3..e23920e 100644 --- a/Zhongli.Data/Models/Moderation/ReprimandNoticeType.cs +++ b/Zhongli.Data/Models/Moderation/ReprimandNoticeType.cs @@ -1,17 +1,16 @@ using System; -namespace Zhongli.Data.Models.Moderation +namespace Zhongli.Data.Models.Moderation; + +[Flags] +public enum ReprimandNoticeType { - [Flags] - public enum ReprimandNoticeType - { - None = 0, - Ban = 1 << 0, - Censor = 1 << 1, - Kick = 1 << 2, - Mute = 1 << 3, - Notice = 1 << 4, - Warning = 1 << 5, - All = Ban | Censor | Kick | Mute | Notice | Warning - } + None = 0, + Ban = 1 << 0, + Censor = 1 << 1, + Kick = 1 << 2, + Mute = 1 << 3, + Notice = 1 << 4, + Warning = 1 << 5, + All = Ban | Censor | Kick | Mute | Notice | Warning } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/ReprimandOptions.cs b/Zhongli.Data/Models/Moderation/ReprimandOptions.cs index 0974449..6133b2e 100644 --- a/Zhongli.Data/Models/Moderation/ReprimandOptions.cs +++ b/Zhongli.Data/Models/Moderation/ReprimandOptions.cs @@ -1,23 +1,22 @@ using System; using System.ComponentModel; -namespace Zhongli.Data.Models.Moderation +namespace Zhongli.Data.Models.Moderation; + +[Flags] +public enum ReprimandOptions { - [Flags] - public enum ReprimandOptions - { - None = 0, + None = 0, - [Description("Separate reprimands and output verbosely.")] - Verbose = 1 << 0, + [Description("Separate reprimands and output verbosely.")] + Verbose = 1 << 0, - [Description("Silently reprimand user by deleting the message.")] - Silent = 1 << 1, + [Description("Silently reprimand user by deleting the message.")] + Silent = 1 << 1, - [Description("Notifies the user by DMing them.")] - NotifyUser = 1 << 2, + [Description("Notifies the user by DMing them.")] + NotifyUser = 1 << 2, - [Description("When notifying a user, make the moderator anonymous.")] - Anonymous = 1 << 3 - } + [Description("When notifying a user, make the moderator anonymous.")] + Anonymous = 1 << 3 } \ No newline at end of file diff --git a/Zhongli.Data/Models/Moderation/ReprimandType.cs b/Zhongli.Data/Models/Moderation/ReprimandType.cs index b8242ef..cf2afbd 100644 --- a/Zhongli.Data/Models/Moderation/ReprimandType.cs +++ b/Zhongli.Data/Models/Moderation/ReprimandType.cs @@ -1,14 +1,13 @@ -namespace Zhongli.Data.Models.Moderation +namespace Zhongli.Data.Models.Moderation; + +public enum ReprimandType { - public enum ReprimandType - { - None, - Ban, - Censored, - Kick, - Mute, - Note, - Notice, - Warning - } + None, + Ban, + Censored, + Kick, + Mute, + Note, + Notice, + Warning } \ No newline at end of file diff --git a/Zhongli.Data/Models/TimeTracking/ChannelTimeTracking.cs b/Zhongli.Data/Models/TimeTracking/ChannelTimeTracking.cs index 0635faa..020bb85 100644 --- a/Zhongli.Data/Models/TimeTracking/ChannelTimeTracking.cs +++ b/Zhongli.Data/Models/TimeTracking/ChannelTimeTracking.cs @@ -1,7 +1,6 @@ -namespace Zhongli.Data.Models.TimeTracking +namespace Zhongli.Data.Models.TimeTracking; + +public class ChannelTimeTracking : TimeTracking { - public class ChannelTimeTracking : TimeTracking - { - public ulong ChannelId { get; set; } - } + public ulong ChannelId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/TimeTracking/GenshinTimeTrackingRules.cs b/Zhongli.Data/Models/TimeTracking/GenshinTimeTrackingRules.cs index 0e5662e..93f56bb 100644 --- a/Zhongli.Data/Models/TimeTracking/GenshinTimeTrackingRules.cs +++ b/Zhongli.Data/Models/TimeTracking/GenshinTimeTrackingRules.cs @@ -1,19 +1,18 @@ using System; -namespace Zhongli.Data.Models.TimeTracking +namespace Zhongli.Data.Models.TimeTracking; + +public class GenshinTimeTrackingRules { - public class GenshinTimeTrackingRules - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual ChannelTimeTracking? AmericaChannel { get; set; } + public virtual ChannelTimeTracking? AmericaChannel { get; set; } - public virtual ChannelTimeTracking? AsiaChannel { get; set; } + public virtual ChannelTimeTracking? AsiaChannel { get; set; } - public virtual ChannelTimeTracking? EuropeChannel { get; set; } + public virtual ChannelTimeTracking? EuropeChannel { get; set; } - public virtual ChannelTimeTracking? SARChannel { get; set; } + public virtual ChannelTimeTracking? SARChannel { get; set; } - public virtual MessageTimeTracking? ServerStatus { get; set; } - } + public virtual MessageTimeTracking? ServerStatus { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/TimeTracking/MessageTimeTracking.cs b/Zhongli.Data/Models/TimeTracking/MessageTimeTracking.cs index 99a22d2..2083626 100644 --- a/Zhongli.Data/Models/TimeTracking/MessageTimeTracking.cs +++ b/Zhongli.Data/Models/TimeTracking/MessageTimeTracking.cs @@ -1,9 +1,8 @@ -namespace Zhongli.Data.Models.TimeTracking +namespace Zhongli.Data.Models.TimeTracking; + +public class MessageTimeTracking : TimeTracking { - public class MessageTimeTracking : TimeTracking - { - public ulong ChannelId { get; set; } + public ulong ChannelId { get; set; } - public ulong MessageId { get; set; } - } + public ulong MessageId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/TimeTracking/TimeTracking.cs b/Zhongli.Data/Models/TimeTracking/TimeTracking.cs index a4cbc0a..5fe979f 100644 --- a/Zhongli.Data/Models/TimeTracking/TimeTracking.cs +++ b/Zhongli.Data/Models/TimeTracking/TimeTracking.cs @@ -1,11 +1,10 @@ using System; -namespace Zhongli.Data.Models.TimeTracking +namespace Zhongli.Data.Models.TimeTracking; + +public abstract class TimeTracking { - public abstract class TimeTracking - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public ulong GuildId { get; set; } - } + public ulong GuildId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/Models/VoiceChat/VoiceChatLink.cs b/Zhongli.Data/Models/VoiceChat/VoiceChatLink.cs index b156369..38ee239 100644 --- a/Zhongli.Data/Models/VoiceChat/VoiceChatLink.cs +++ b/Zhongli.Data/Models/VoiceChat/VoiceChatLink.cs @@ -3,27 +3,26 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.VoiceChat +namespace Zhongli.Data.Models.VoiceChat; + +public class VoiceChatLink : IGuildUserEntity { - public class VoiceChatLink : IGuildUserEntity - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual GuildUserEntity Owner { get; set; } + public virtual GuildUserEntity Owner { get; set; } - public ulong TextChannelId { get; set; } + public ulong TextChannelId { get; set; } - public ulong VoiceChannelId { get; set; } + public ulong VoiceChannelId { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong UserId { get; set; } - } + public ulong UserId { get; set; } +} - public class VoiceChatLinkConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(v => v.Owner); } - } +public class VoiceChatLinkConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { builder.AddUserNavigation(v => v.Owner); } } \ No newline at end of file diff --git a/Zhongli.Data/Models/VoiceChat/VoiceChatRules.cs b/Zhongli.Data/Models/VoiceChat/VoiceChatRules.cs index 8921122..752f541 100644 --- a/Zhongli.Data/Models/VoiceChat/VoiceChatRules.cs +++ b/Zhongli.Data/Models/VoiceChat/VoiceChatRules.cs @@ -2,26 +2,25 @@ using System.Collections.Generic; using Zhongli.Data.Models.Discord; -namespace Zhongli.Data.Models.VoiceChat +namespace Zhongli.Data.Models.VoiceChat; + +public class VoiceChatRules { - public class VoiceChatRules - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public bool PurgeEmpty { get; set; } + public bool PurgeEmpty { get; set; } - public bool ShowJoinLeave { get; set; } + public bool ShowJoinLeave { get; set; } - public virtual GuildEntity Guild { get; set; } + public virtual GuildEntity Guild { get; set; } - public virtual ICollection VoiceChats { get; set; } = new List(); + public virtual ICollection VoiceChats { get; set; } = new List(); - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - public ulong HubVoiceChannelId { get; set; } + public ulong HubVoiceChannelId { get; set; } - public ulong VoiceChannelCategoryId { get; set; } + public ulong VoiceChannelCategoryId { get; set; } - public ulong VoiceChatCategoryId { get; set; } - } + public ulong VoiceChatCategoryId { get; set; } } \ No newline at end of file diff --git a/Zhongli.Data/ZhongliContext.cs b/Zhongli.Data/ZhongliContext.cs index d221045..9625212 100644 --- a/Zhongli.Data/ZhongliContext.cs +++ b/Zhongli.Data/ZhongliContext.cs @@ -9,69 +9,65 @@ using Zhongli.Data.Models.Moderation.Infractions.Triggers; using Zhongli.Data.Models.TimeTracking; -namespace Zhongli.Data +namespace Zhongli.Data; + +public class ZhongliContext : DbContext { - public class ZhongliContext : DbContext - { - public ZhongliContext(DbContextOptions options) : base(options) { } + public ZhongliContext(DbContextOptions options) : base(options) { } - public DbSet BanHistory { get; init; } + public DbSet BanHistory { get; init; } - public DbSet BanActions { get; init; } + public DbSet BanActions { get; init; } - public DbSet Censors { get; init; } + public DbSet Censors { get; init; } - public DbSet CensoredHistory { get; set; } + public DbSet CensoredHistory { get; set; } - public DbSet ChannelCriteria { get; init; } + public DbSet ChannelCriteria { get; init; } - public DbSet Emojis { get; set; } + public DbSet Emojis { get; set; } - public DbSet Emotes { get; set; } + public DbSet Emotes { get; set; } - public DbSet EnumChannels { get; set; } + public DbSet EnumChannels { get; set; } - public DbSet Guilds { get; init; } + public DbSet Guilds { get; init; } - public DbSet Users { get; init; } + public DbSet Users { get; init; } - public DbSet KickHistory { get; init; } + public DbSet KickHistory { get; init; } - public DbSet KickActions { get; init; } + public DbSet KickActions { get; init; } - public DbSet MessageDeleteLogs { get; set; } + public DbSet MessageDeleteLogs { get; set; } - public DbSet MuteHistory { get; init; } + public DbSet MuteHistory { get; init; } - public DbSet MuteActions { get; init; } + public DbSet MuteActions { get; init; } - public DbSet NoteHistory { get; init; } + public DbSet NoteHistory { get; init; } - public DbSet NoteActions { get; init; } + public DbSet NoteActions { get; init; } - public DbSet NoticeHistory { get; init; } + public DbSet NoticeHistory { get; init; } - public DbSet NoticeActions { get; init; } + public DbSet NoticeActions { get; init; } - public DbSet PermissionCriteria { get; init; } + public DbSet PermissionCriteria { get; init; } - public DbSet ReactionDeleteLogs { get; set; } + public DbSet ReactionDeleteLogs { get; set; } - public DbSet ReprimandTriggers { get; init; } + public DbSet ReprimandTriggers { get; init; } - public DbSet RoleCriteria { get; init; } + public DbSet RoleCriteria { get; init; } - public DbSet TimeTrackingRules { get; init; } + public DbSet TimeTrackingRules { get; init; } - public DbSet UserCriteria { get; init; } + public DbSet UserCriteria { get; init; } - public DbSet WarningHistory { get; init; } + public DbSet WarningHistory { get; init; } - public DbSet WarningActions { get; init; } + public DbSet WarningActions { get; init; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.ApplyConfigurationsFromAssembly(typeof(ZhongliContext).Assembly); - } - } + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(ZhongliContext).Assembly); } } \ No newline at end of file diff --git a/Zhongli.Data/ZhongliContextFactory.cs b/Zhongli.Data/ZhongliContextFactory.cs index a976c9a..b92aa36 100644 --- a/Zhongli.Data/ZhongliContextFactory.cs +++ b/Zhongli.Data/ZhongliContextFactory.cs @@ -2,16 +2,15 @@ using Microsoft.EntityFrameworkCore.Design; using Zhongli.Data.Config; -namespace Zhongli.Data +namespace Zhongli.Data; + +public class ZhongliContextFactory : IDesignTimeDbContextFactory { - public class ZhongliContextFactory : IDesignTimeDbContextFactory + public ZhongliContext CreateDbContext(string[] args) { - public ZhongliContext CreateDbContext(string[] args) - { - var optionsBuilder = new DbContextOptionsBuilder() - .UseNpgsql(ZhongliConfig.Configuration.ZhongliContext); + var optionsBuilder = new DbContextOptionsBuilder() + .UseNpgsql(ZhongliConfig.Configuration.ZhongliContext); - return new ZhongliContext(optionsBuilder.Options); - } + return new ZhongliContext(optionsBuilder.Options); } } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs index 3437ba0..4c757bc 100644 --- a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs +++ b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageHandler.cs @@ -6,77 +6,76 @@ using Microsoft.Extensions.Caching.Memory; using Zhongli.Services.Core.Messages; -namespace Zhongli.Services.AutoRemoveMessage -{ - public class AutoRemoveMessageHandler : - INotificationHandler, - INotificationHandler, - INotificationHandler - { - private static readonly MemoryCacheEntryOptions MessageCacheOptions = - new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(60)); - - private readonly IAutoRemoveMessageService _autoRemove; - private readonly IMemoryCache _cache; +namespace Zhongli.Services.AutoRemoveMessage; - public AutoRemoveMessageHandler( - IAutoRemoveMessageService autoRemove, - IMemoryCache cache) - { - _autoRemove = autoRemove; - _cache = cache; - } +public class AutoRemoveMessageHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler +{ + private static readonly MemoryCacheEntryOptions MessageCacheOptions = + new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(60)); - public async Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) - { - var key = GetKey(notification.Message.Id); + private readonly IAutoRemoveMessageService _autoRemove; + private readonly IMemoryCache _cache; - if (cancellationToken.IsCancellationRequested - || notification.Reaction.Emote.Name != "❌" - || !_cache.TryGetValue(key, out RemovableMessage cachedMessage) - || cachedMessage.Users.All(user => user.Id != notification.Reaction.UserId)) - return; + public AutoRemoveMessageHandler( + IAutoRemoveMessageService autoRemove, + IMemoryCache cache) + { + _autoRemove = autoRemove; + _cache = cache; + } - await cachedMessage.Message.DeleteAsync(); + public async Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) + { + var key = GetKey(notification.Message.Id); - _autoRemove.UnregisterRemovableMessage(cachedMessage.Message); - } + if (cancellationToken.IsCancellationRequested + || notification.Reaction.Emote.Name != "❌" + || !_cache.TryGetValue(key, out RemovableMessage cachedMessage) + || cachedMessage.Users.All(user => user.Id != notification.Reaction.UserId)) + return; - public Task Handle(RemovableMessageRemovedNotification notification, CancellationToken cancellationToken) - { - var key = GetKey(notification.Message.Id); + await cachedMessage.Message.DeleteAsync(); - if (cancellationToken.IsCancellationRequested - || !_cache.TryGetValue(key, out _)) - return Task.CompletedTask; + _autoRemove.UnregisterRemovableMessage(cachedMessage.Message); + } - _cache.Remove(key); + public Task Handle(RemovableMessageRemovedNotification notification, CancellationToken cancellationToken) + { + var key = GetKey(notification.Message.Id); + if (cancellationToken.IsCancellationRequested + || !_cache.TryGetValue(key, out _)) return Task.CompletedTask; - } - public Task Handle(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); + _cache.Remove(key); + + return Task.CompletedTask; + } + public Task Handle(RemovableMessageSentNotification notification, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) return Task.CompletedTask; - } - private static object GetKey(ulong messageId) - => new + _cache.Set( + GetKey(notification.Message.Id), + new RemovableMessage { - MessageId = messageId, - Target = "RemovableMessage" - }; + Message = notification.Message, + Users = notification.Users + }, + MessageCacheOptions); + + return Task.CompletedTask; } + + private static object GetKey(ulong messageId) + => new + { + MessageId = messageId, + Target = "RemovableMessage" + }; } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageService.cs b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageService.cs index 0477069..27b17dd 100644 --- a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageService.cs +++ b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageService.cs @@ -3,88 +3,87 @@ using Discord; using MediatR; -namespace Zhongli.Services.AutoRemoveMessage +namespace Zhongli.Services.AutoRemoveMessage; + +/// +/// Defines a service used to track removable messages. +/// +public interface IAutoRemoveMessageService { /// - /// Defines a service used to track removable messages. + /// Registers a removable message with the service and adds an indicator for this to the provided embed. /// - public interface IAutoRemoveMessageService - { - /// - /// Registers a removable message with the service and adds an indicator for this to the provided embed. - /// - /// The user who can remove the message. - /// The embed to operate on - /// - /// A callback that returns the to register as removable. The modified - /// embed is provided with this callback. - /// - /// - /// If the provided - /// - /// is null. - /// - /// A that will complete when the operation completes. - /// - Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, - Func> callback); + /// The user who can remove the message. + /// The embed to operate on + /// + /// A callback that returns the to register as removable. The modified + /// embed is provided with this callback. + /// + /// + /// If the provided + /// + /// is null. + /// + /// A that will complete when the operation completes. + /// + Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, + Func> callback); - /// - /// Registers a removable message with the service and adds an indicator for this to the provided embed. - /// - /// The users who can remove the message. - /// The embed to operate on - /// - /// A callback that returns the to register as removable. The modified - /// embed is provided with this callback. - /// - /// - /// If the provided - /// - /// is null. - /// - /// A that will complete when the operation completes. - /// - Task RegisterRemovableMessageAsync(IUser[] users, EmbedBuilder embed, - Func> callback); - - /// - /// Unregisters a removable message from the service. - /// - /// The removable message. - void UnregisterRemovableMessage(IMessage message); - } + /// + /// Registers a removable message with the service and adds an indicator for this to the provided embed. + /// + /// The users who can remove the message. + /// The embed to operate on + /// + /// A callback that returns the to register as removable. The modified + /// embed is provided with this callback. + /// + /// + /// If the provided + /// + /// is null. + /// + /// A that will complete when the operation completes. + /// + Task RegisterRemovableMessageAsync(IUser[] users, EmbedBuilder embed, + Func> callback); - /// - internal class AutoRemoveMessageService : IAutoRemoveMessageService - { - private const string FooterReactMessage = "React with ❌ to remove this embed."; - private readonly IMediator _messageDispatcher; + /// + /// Unregisters a removable message from the service. + /// + /// The removable message. + void UnregisterRemovableMessage(IMessage message); +} - public AutoRemoveMessageService(IMediator messageDispatcher) { _messageDispatcher = messageDispatcher; } +/// +internal class AutoRemoveMessageService : IAutoRemoveMessageService +{ + private const string FooterReactMessage = "React with ❌ to remove this embed."; + private readonly IMediator _messageDispatcher; - /// - public Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, - Func> callback) - => RegisterRemovableMessageAsync(new[] { user }, embed, callback); + public AutoRemoveMessageService(IMediator messageDispatcher) { _messageDispatcher = messageDispatcher; } - /// - public async Task RegisterRemovableMessageAsync(IUser[] user, EmbedBuilder embed, - Func> callback) - { - if (callback is null) - throw new ArgumentNullException(nameof(callback)); + /// + public Task RegisterRemovableMessageAsync(IUser user, EmbedBuilder embed, + Func> callback) + => RegisterRemovableMessageAsync(new[] { user }, embed, callback); - if (embed.Footer?.Text is null) - embed.WithFooter(FooterReactMessage); - else if (!embed.Footer.Text.Contains(FooterReactMessage)) embed.Footer.Text += $" | {FooterReactMessage}"; + /// + public async Task RegisterRemovableMessageAsync(IUser[] user, EmbedBuilder embed, + Func> callback) + { + if (callback is null) + throw new ArgumentNullException(nameof(callback)); - var msg = await callback(embed); - await _messageDispatcher.Publish(new RemovableMessageSentNotification(msg, user)); - } + if (embed.Footer?.Text is null) + embed.WithFooter(FooterReactMessage); + else if (!embed.Footer.Text.Contains(FooterReactMessage)) embed.Footer.Text += $" | {FooterReactMessage}"; - /// - public void UnregisterRemovableMessage(IMessage message) - => _messageDispatcher.Publish(new RemovableMessageRemovedNotification(message)); + var msg = await callback(embed); + await _messageDispatcher.Publish(new RemovableMessageSentNotification(msg, user)); } + + /// + public void UnregisterRemovableMessage(IMessage message) + => _messageDispatcher.Publish(new RemovableMessageRemovedNotification(message)); } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs index 6b1647f..4daf1ea 100644 --- a/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs +++ b/Zhongli.Services/AutoRemoveMessage/AutoRemoveMessageSetup.cs @@ -2,25 +2,24 @@ using Microsoft.Extensions.DependencyInjection; using Zhongli.Services.Core.Messages; -namespace Zhongli.Services.AutoRemoveMessage +namespace Zhongli.Services.AutoRemoveMessage; + +/// +/// Contains extension methods for configuring the auto remove message feature upon application startup. +/// +public static class AutoRemoveMessageSetup { /// - /// Contains extension methods for configuring the auto remove message feature upon application startup. + /// Adds the services and classes that make up the auto remove message service to a service collection. /// - 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>(); - } + /// 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>(); } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/RemovableMessage.cs b/Zhongli.Services/AutoRemoveMessage/RemovableMessage.cs index 9e8a987..223a1c8 100644 --- a/Zhongli.Services/AutoRemoveMessage/RemovableMessage.cs +++ b/Zhongli.Services/AutoRemoveMessage/RemovableMessage.cs @@ -1,11 +1,10 @@ using Discord; -namespace Zhongli.Services.AutoRemoveMessage +namespace Zhongli.Services.AutoRemoveMessage; + +public class RemovableMessage { - public class RemovableMessage - { - public IMessage Message { get; set; } + public IMessage Message { get; set; } - public IUser[] Users { get; set; } - } + public IUser[] Users { get; set; } } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs b/Zhongli.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs index 5ddfddd..9115994 100644 --- a/Zhongli.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs +++ b/Zhongli.Services/AutoRemoveMessage/RemovableMessageRemovedNotification.cs @@ -2,15 +2,11 @@ using Discord; using MediatR; -namespace Zhongli.Services.AutoRemoveMessage +namespace Zhongli.Services.AutoRemoveMessage; + +public class RemovableMessageRemovedNotification : INotification { - public class RemovableMessageRemovedNotification : INotification - { - public RemovableMessageRemovedNotification(IMessage message) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - } + public RemovableMessageRemovedNotification(IMessage message) { Message = message ?? throw new ArgumentNullException(nameof(message)); } - public IMessage Message { get; } - } + public IMessage Message { get; } } \ No newline at end of file diff --git a/Zhongli.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs b/Zhongli.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs index 86cc522..19ccd6c 100644 --- a/Zhongli.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs +++ b/Zhongli.Services/AutoRemoveMessage/RemovableMessageSentNotification.cs @@ -2,18 +2,17 @@ using Discord; using MediatR; -namespace Zhongli.Services.AutoRemoveMessage +namespace Zhongli.Services.AutoRemoveMessage; + +public class RemovableMessageSentNotification : INotification { - public class RemovableMessageSentNotification : INotification + public RemovableMessageSentNotification(IMessage message, IUser[] users) { - public RemovableMessageSentNotification(IMessage message, IUser[] users) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - Users = users ?? throw new ArgumentNullException(nameof(users)); - } + Message = message ?? throw new ArgumentNullException(nameof(message)); + Users = users ?? throw new ArgumentNullException(nameof(users)); + } - public IMessage Message { get; } + public IMessage Message { get; } - public IUser[] Users { get; } - } + public IUser[] Users { get; } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/CommandHelpData.cs b/Zhongli.Services/CommandHelp/CommandHelpData.cs index 4e3251f..1a46f9c 100644 --- a/Zhongli.Services/CommandHelp/CommandHelpData.cs +++ b/Zhongli.Services/CommandHelp/CommandHelpData.cs @@ -2,31 +2,30 @@ using System.Linq; using Discord.Commands; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +public class CommandHelpData { - public class CommandHelpData - { - public IReadOnlyCollection Parameters { get; set; } + public IReadOnlyCollection Parameters { get; set; } - public IReadOnlyCollection Aliases { get; set; } + public IReadOnlyCollection Aliases { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public string? Summary { get; set; } + public string? Summary { get; set; } - public static CommandHelpData FromCommandInfo(CommandInfo command) + public static CommandHelpData FromCommandInfo(CommandInfo command) + { + var ret = new CommandHelpData { - var ret = new CommandHelpData - { - Name = command.Name, - Summary = command.Summary, - Aliases = command.Aliases, - Parameters = command.Parameters - .Select(ParameterHelpData.FromParameterInfo) - .ToArray() - }; + Name = command.Name, + Summary = command.Summary, + Aliases = command.Aliases, + Parameters = command.Parameters + .Select(ParameterHelpData.FromParameterInfo) + .ToArray() + }; - return ret; - } + return ret; } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/CommandHelpDataExtensions.cs b/Zhongli.Services/CommandHelp/CommandHelpDataExtensions.cs index bcaa1b2..a473459 100644 --- a/Zhongli.Services/CommandHelp/CommandHelpDataExtensions.cs +++ b/Zhongli.Services/CommandHelp/CommandHelpDataExtensions.cs @@ -7,137 +7,136 @@ using Zhongli.Data.Config; using Zhongli.Services.Utilities; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +public static class CommandHelpDataExtensions { - public static class CommandHelpDataExtensions + public static EmbedBuilder AddCommandFields(this EmbedBuilder embed, CommandHelpData command) { - public static EmbedBuilder AddCommandFields(this EmbedBuilder embed, CommandHelpData command) - { - var builder = new StringBuilder(command.Summary ?? "No summary.").AppendLine(); - var name = command.Aliases.FirstOrDefault(); + var builder = new StringBuilder(command.Summary ?? "No summary.").AppendLine(); + var name = command.Aliases.FirstOrDefault(); - builder - .AppendAliases(command.Aliases - .Where(a => !a.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList()) - .AppendParameters(command.Parameters); + builder + .AppendAliases(command.Aliases + .Where(a => !a.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList()) + .AppendParameters(command.Parameters); - var prefix = ZhongliConfig.Configuration.Prefix; + var prefix = ZhongliConfig.Configuration.Prefix; - var lines = builder.ToString().Split(Environment.NewLine); - embed.AddItemsIntoFields($"Command: {prefix}{name} {GetParams(command)}", lines); + var lines = builder.ToString().Split(Environment.NewLine); + embed.AddItemsIntoFields($"Command: {prefix}{name} {GetParams(command)}", lines); - return embed; - } + return embed; + } - private static bool HasSummary(this IEnumerable parameters) => - parameters.Any(p => !string.IsNullOrWhiteSpace(p.Summary)); + private static bool HasSummary(this IEnumerable parameters) => + parameters.Any(p => !string.IsNullOrWhiteSpace(p.Summary)); - private static string GetParamName(ParameterHelpData parameter) => parameter.GetRealType().IsEnum - ? Surround(parameter.Name, parameter.IsOptional) - : Surround($"{parameter.Name}: {parameter.GetParamTypeName()}", parameter.IsOptional); + private static string GetParamName(ParameterHelpData parameter) => parameter.GetRealType().IsEnum + ? Surround(parameter.Name, parameter.IsOptional) + : Surround($"{parameter.Name}: {parameter.GetParamTypeName()}", parameter.IsOptional); - private static string GetParams(this CommandHelpData info) - { - var sb = new StringBuilder(); + private static string GetParams(this CommandHelpData info) + { + var sb = new StringBuilder(); - var parameterInfo = info.Parameters - .Select(p => Format.Code(GetParamName(p))); + var parameterInfo = info.Parameters + .Select(p => Format.Code(GetParamName(p))); - sb.Append(string.Join(" ", parameterInfo)); + sb.Append(string.Join(" ", parameterInfo)); - return sb.ToString(); - } + return sb.ToString(); + } - private static string GetParamTypeName(this ParameterHelpData parameter) - => parameter.Type.IsEnumerableOfT() - ? $"{parameter.GetRealType().Name} [...]" - : parameter.GetRealType().Name; + private static string GetParamTypeName(this ParameterHelpData parameter) + => parameter.Type.IsEnumerableOfT() + ? $"{parameter.GetRealType().Name} [...]" + : parameter.GetRealType().Name; - private static string Surround(string text, bool isNullable) => isNullable ? $"[{text}]" : $"<{text}>"; + private static string Surround(string text, bool isNullable) => isNullable ? $"[{text}]" : $"<{text}>"; - private static StringBuilder AppendAliases(this StringBuilder builder, IReadOnlyCollection aliases) - { - if (aliases.Count == 0) - return builder; + private static StringBuilder AppendAliases(this StringBuilder builder, IReadOnlyCollection aliases) + { + if (aliases.Count == 0) + return builder; + + builder.AppendLine(Format.Bold("Aliases:")) + .AppendLine(FormatUtilities.CollapsePlurals(aliases).Humanize(a => Format.Code(a))); - builder.AppendLine(Format.Bold("Aliases:")) - .AppendLine(FormatUtilities.CollapsePlurals(aliases).Humanize(a => Format.Code(a))); + return builder; + } + private static StringBuilder AppendParameter(this StringBuilder builder, + ParameterHelpData parameter, ISet seenTypes) + { + if (!parameter.Options.Any() || seenTypes.Contains(parameter.Type)) return builder; - } - private static StringBuilder AppendParameter(this StringBuilder builder, - ParameterHelpData parameter, ISet seenTypes) - { - if (!parameter.Options.Any() || seenTypes.Contains(parameter.Type)) - return builder; + seenTypes.Add(parameter.Type); - seenTypes.Add(parameter.Type); + builder + .AppendLine() + .AppendLine($"Arguments for {Format.Underline(Format.Bold(parameter.Name))}:"); + if (parameter.GetRealType().IsEnum) + { + var names = parameter.Options.Select(p => p.Name); + var values = Surround(string.Join("|\x200b", names), parameter.IsOptional); builder - .AppendLine() - .AppendLine($"Arguments for {Format.Underline(Format.Bold(parameter.Name))}:"); - - if (parameter.GetRealType().IsEnum) - { - var names = parameter.Options.Select(p => p.Name); - var values = Surround(string.Join("|\x200b", names), parameter.IsOptional); - builder - .AppendLine(Format.Code(values)) - .AppendSummaries(parameter.Options, true); - } - else - { - builder - .AppendSummaries(parameter.Options, false) - .AppendLine( - $"▌Provide values by doing {Format.Code("name: value")} " + - $"or {Format.Code("name: \"value with spaces\"")}."); - - foreach (var nestedParameter in parameter.Options) - { - builder.AppendParameter(nestedParameter, seenTypes); - } - } - - return builder; + .AppendLine(Format.Code(values)) + .AppendSummaries(parameter.Options, true); } - - private static StringBuilder AppendParameters(this StringBuilder builder, - IReadOnlyCollection parameters) + else { - if (parameters.Count == 0) - return builder; + builder + .AppendSummaries(parameter.Options, false) + .AppendLine( + $"▌Provide values by doing {Format.Code("name: value")} " + + $"or {Format.Code("name: \"value with spaces\"")}."); - if (parameters.HasSummary()) + foreach (var nestedParameter in parameter.Options) { - builder - .AppendLine(Format.Bold("Parameters:")) - .AppendSummaries(parameters, true); + builder.AppendParameter(nestedParameter, seenTypes); } + } - var seenTypes = new HashSet(); - foreach (var parameter in parameters) - { - builder.AppendParameter(parameter, seenTypes); - } + return builder; + } + private static StringBuilder AppendParameters(this StringBuilder builder, + IReadOnlyCollection parameters) + { + if (parameters.Count == 0) return builder; + + if (parameters.HasSummary()) + { + builder + .AppendLine(Format.Bold("Parameters:")) + .AppendSummaries(parameters, true); } - private static StringBuilder AppendSummaries(this StringBuilder builder, - IEnumerable parameters, bool hideEmpty) + var seenTypes = new HashSet(); + foreach (var parameter in parameters) { - foreach (var parameter in parameters) - { - if (string.IsNullOrEmpty(parameter.Summary) && hideEmpty) - continue; + builder.AppendParameter(parameter, seenTypes); + } - var name = Format.Code(GetParamName(parameter)); - builder.AppendLine($"\x200b\t• {name}: {parameter.Summary ?? "No Summary."}"); - } + return builder; + } - return builder; + private static StringBuilder AppendSummaries(this StringBuilder builder, + IEnumerable parameters, bool hideEmpty) + { + foreach (var parameter in parameters) + { + if (string.IsNullOrEmpty(parameter.Summary) && hideEmpty) + continue; + + var name = Format.Code(GetParamName(parameter)); + builder.AppendLine($"\x200b\t• {name}: {parameter.Summary ?? "No Summary."}"); } + + return builder; } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/CommandHelpService.cs b/Zhongli.Services/CommandHelp/CommandHelpService.cs index 49a7c29..b54137d 100644 --- a/Zhongli.Services/CommandHelp/CommandHelpService.cs +++ b/Zhongli.Services/CommandHelp/CommandHelpService.cs @@ -6,189 +6,188 @@ using Discord.Addons.Interactive.Paginator; using Discord.Commands; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +/// +/// Provides functionality to retrieve command help information. +/// +public interface ICommandHelpService { + bool TryGetEmbed(string query, HelpDataType queries, out PaginatedMessage embed); + /// - /// Provides functionality to retrieve command help information. + /// Retrieves command help data for the supplied query. /// - public interface ICommandHelpService - { - bool TryGetEmbed(string query, HelpDataType queries, out PaginatedMessage embed); - - /// - /// Retrieves command help data for the supplied query. - /// - /// A query to use to search for an applicable help module. - /// - /// Help information for the supplied query, or if no information could be found for the - /// supplied query. - /// - CommandHelpData? GetCommandHelpData(string query); - - /// - /// Retrieves help data for all available modules. - /// - /// - /// A readonly collection of data about all available modules. - /// - IReadOnlyCollection GetModuleHelpData(); - - /// - /// Retrieves module help data for the supplied query. - /// - /// A query to use to search for an applicable help module. - /// - /// Help information for the supplied query, or if no information could be found for the - /// supplied query. - /// - ModuleHelpData? GetModuleHelpData(string query); - - /// - /// Retrieves an embed from a . - /// - /// The command's help data. - /// An that contains information for the command. - PaginatedMessage GetEmbedForCommand(CommandHelpData command); - - /// - /// Retrieves an embed from a - /// - /// The module's help data. - /// An that contains information for the module. - PaginatedMessage GetEmbedForModule(ModuleHelpData module); - } + /// A query to use to search for an applicable help module. + /// + /// Help information for the supplied query, or if no information could be found for the + /// supplied query. + /// + CommandHelpData? GetCommandHelpData(string query); - /// - internal class CommandHelpService : ICommandHelpService + /// + /// Retrieves help data for all available modules. + /// + /// + /// A readonly collection of data about all available modules. + /// + IReadOnlyCollection GetModuleHelpData(); + + /// + /// Retrieves module help data for the supplied query. + /// + /// A query to use to search for an applicable help module. + /// + /// Help information for the supplied query, or if no information could be found for the + /// supplied query. + /// + ModuleHelpData? GetModuleHelpData(string query); + + /// + /// Retrieves an embed from a . + /// + /// The command's help data. + /// An that contains information for the command. + PaginatedMessage GetEmbedForCommand(CommandHelpData command); + + /// + /// Retrieves an embed from a + /// + /// The module's help data. + /// An that contains information for the module. + PaginatedMessage GetEmbedForModule(ModuleHelpData module); +} + +/// +internal class CommandHelpService : ICommandHelpService +{ + private readonly CommandService _commandService; + + private readonly PaginatedAppearanceOptions _paginatedOptions = new() { - private readonly CommandService _commandService; + DisplayInformationIcon = false, + Timeout = TimeSpan.FromMinutes(10) + }; - private readonly PaginatedAppearanceOptions _paginatedOptions = new() - { - DisplayInformationIcon = false, - Timeout = TimeSpan.FromMinutes(10) - }; + private IReadOnlyCollection _cachedHelpData = null!; - private IReadOnlyCollection _cachedHelpData = null!; + public CommandHelpService(CommandService commandService) { _commandService = commandService; } - public CommandHelpService(CommandService commandService) { _commandService = commandService; } + public bool TryGetEmbed(string query, HelpDataType queries, out PaginatedMessage message) + { + message = null!; - public bool TryGetEmbed(string query, HelpDataType queries, out PaginatedMessage message) + // Prioritize module over command. + if (queries.HasFlag(HelpDataType.Module)) { - message = null!; - - // Prioritize module over command. - if (queries.HasFlag(HelpDataType.Module)) + var byModule = GetModuleHelpData(query); + if (byModule is not null) { - var byModule = GetModuleHelpData(query); - if (byModule is not null) - { - message = GetEmbedForModule(byModule); - return true; - } + message = GetEmbedForModule(byModule); + return true; } + } - if (queries.HasFlag(HelpDataType.Command)) + if (queries.HasFlag(HelpDataType.Command)) + { + var byCommand = GetCommandHelpData(query); + if (byCommand is not null) { - var byCommand = GetCommandHelpData(query); - if (byCommand is not null) - { - message = GetEmbedForCommand(byCommand); - return true; - } + message = GetEmbedForCommand(byCommand); + return true; } - - return false; } - /// - public CommandHelpData? GetCommandHelpData(string query) - { - var allHelpData = GetModuleHelpData().SelectMany(x => x.Commands).ToList(); + return false; + } - var byModuleNameExact = allHelpData.FirstOrDefault(x => - x.Aliases.Any(y => y.Equals(query, StringComparison.OrdinalIgnoreCase))); - if (byModuleNameExact is not null) - return byModuleNameExact; + /// + public CommandHelpData? GetCommandHelpData(string query) + { + var allHelpData = GetModuleHelpData().SelectMany(x => x.Commands).ToList(); - var byNameContains = - allHelpData.FirstOrDefault(x => - x.Aliases.Any(y => y.Contains(query, StringComparison.OrdinalIgnoreCase))); - if (byNameContains is not null) - return byNameContains; + var byModuleNameExact = allHelpData.FirstOrDefault(x => + x.Aliases.Any(y => y.Equals(query, StringComparison.OrdinalIgnoreCase))); + if (byModuleNameExact is not null) + return byModuleNameExact; - return null; - } + var byNameContains = + allHelpData.FirstOrDefault(x => + x.Aliases.Any(y => y.Contains(query, StringComparison.OrdinalIgnoreCase))); + if (byNameContains is not null) + return byNameContains; - /// - public IReadOnlyCollection GetModuleHelpData() - => LazyInitializer.EnsureInitialized(ref _cachedHelpData, () => - _commandService.Modules - .Where(x => !x.Attributes.Any(attr => attr is HiddenFromHelpAttribute)) - .Select(ModuleHelpData.FromModuleInfo) - .ToArray()); + return null; + } - /// - public ModuleHelpData? GetModuleHelpData(string query) - { - var allHelpData = GetModuleHelpData(); + /// + public IReadOnlyCollection GetModuleHelpData() + => LazyInitializer.EnsureInitialized(ref _cachedHelpData, () => + _commandService.Modules + .Where(x => !x.Attributes.Any(attr => attr is HiddenFromHelpAttribute)) + .Select(ModuleHelpData.FromModuleInfo) + .ToArray()); - var byNameExact = allHelpData.FirstOrDefault(x => x.Name.Equals(query, StringComparison.OrdinalIgnoreCase)); - if (byNameExact is not null) - return byNameExact; + /// + public ModuleHelpData? GetModuleHelpData(string query) + { + var allHelpData = GetModuleHelpData(); - var byTagsExact = allHelpData.FirstOrDefault(x => - x.HelpTags.Any(y => y.Equals(query, StringComparison.OrdinalIgnoreCase))); - if (byTagsExact is not null) - return byTagsExact; + var byNameExact = allHelpData.FirstOrDefault(x => x.Name.Equals(query, StringComparison.OrdinalIgnoreCase)); + if (byNameExact is not null) + return byNameExact; - var byNameContains = - allHelpData.FirstOrDefault(x => x.Name.Contains(query, StringComparison.OrdinalIgnoreCase)); - if (byNameContains is not null) - return byNameContains; + var byTagsExact = allHelpData.FirstOrDefault(x => + x.HelpTags.Any(y => y.Equals(query, StringComparison.OrdinalIgnoreCase))); + if (byTagsExact is not null) + return byTagsExact; - var byTagsContains = allHelpData.FirstOrDefault(x => - x.HelpTags.Any(y => y.Contains(query, StringComparison.OrdinalIgnoreCase))); - if (byTagsContains is not null) - return byTagsContains; + var byNameContains = + allHelpData.FirstOrDefault(x => x.Name.Contains(query, StringComparison.OrdinalIgnoreCase)); + if (byNameContains is not null) + return byNameContains; - return null; - } + var byTagsContains = allHelpData.FirstOrDefault(x => + x.HelpTags.Any(y => y.Contains(query, StringComparison.OrdinalIgnoreCase))); + if (byTagsContains is not null) + return byTagsContains; - public PaginatedMessage GetEmbedForCommand(CommandHelpData command) - { - var embed = new EmbedBuilder() - .AddCommandFields(command); + return null; + } - return new PaginatedMessage - { - Pages = embed.Fields, - Options = _paginatedOptions - }; - } + public PaginatedMessage GetEmbedForCommand(CommandHelpData command) + { + var embed = new EmbedBuilder() + .AddCommandFields(command); - public PaginatedMessage GetEmbedForModule(ModuleHelpData module) + return new PaginatedMessage { - var embed = new EmbedBuilder(); - foreach (var command in module.Commands) - { - embed.AddCommandFields(command); - } - - return new PaginatedMessage - { - Title = $"Module: {module.Name}", - AlternateDescription = module.Summary, - Pages = embed.Fields, - Options = _paginatedOptions - }; - } + Pages = embed.Fields, + Options = _paginatedOptions + }; } - [Flags] - public enum HelpDataType + public PaginatedMessage GetEmbedForModule(ModuleHelpData module) { - Command = 1 << 1, - Module = 1 << 2 + var embed = new EmbedBuilder(); + foreach (var command in module.Commands) + { + embed.AddCommandFields(command); + } + + return new PaginatedMessage + { + Title = $"Module: {module.Name}", + AlternateDescription = module.Summary, + Pages = embed.Fields, + Options = _paginatedOptions + }; } +} + +[Flags] +public enum HelpDataType +{ + Command = 1 << 1, + Module = 1 << 2 } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/CommandHelpSetup.cs b/Zhongli.Services/CommandHelp/CommandHelpSetup.cs index 2ce9afe..65be508 100644 --- a/Zhongli.Services/CommandHelp/CommandHelpSetup.cs +++ b/Zhongli.Services/CommandHelp/CommandHelpSetup.cs @@ -1,21 +1,20 @@ using Microsoft.Extensions.DependencyInjection; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +/// +/// Contains extension methods for configuration the CommandHelp feature +/// +public static class CommandHelpSetup { /// - /// Contains extension methods for configuration the CommandHelp feature + /// Adds the services and classes that make up the Command Help feature, to a service collection. /// - public static class CommandHelpSetup - { - /// - /// Adds the services and classes that make up the Command Help feature, to a service collection. - /// - /// The to which the Command Help services are to be added. - /// - /// - /// - public static IServiceCollection AddCommandHelp(this IServiceCollection services) - => services - .AddSingleton(); - } + /// The to which the Command Help services are to be added. + /// + /// + /// + public static IServiceCollection AddCommandHelp(this IServiceCollection services) + => services + .AddSingleton(); } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/HelpSummaryAttribute.cs b/Zhongli.Services/CommandHelp/HelpSummaryAttribute.cs index cede92d..b734df3 100644 --- a/Zhongli.Services/CommandHelp/HelpSummaryAttribute.cs +++ b/Zhongli.Services/CommandHelp/HelpSummaryAttribute.cs @@ -1,11 +1,10 @@ using System; using Discord.Commands; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Enum | AttributeTargets.Field)] +public class HelpSummaryAttribute : SummaryAttribute { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Enum | AttributeTargets.Field)] - public class HelpSummaryAttribute : SummaryAttribute - { - public HelpSummaryAttribute(string text) : base(text) { } - } + public HelpSummaryAttribute(string text) : base(text) { } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/HelpTagsAttribute.cs b/Zhongli.Services/CommandHelp/HelpTagsAttribute.cs index 2e3192e..ac310e3 100644 --- a/Zhongli.Services/CommandHelp/HelpTagsAttribute.cs +++ b/Zhongli.Services/CommandHelp/HelpTagsAttribute.cs @@ -1,15 +1,14 @@ using System; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +/// +/// Indicates tags to use during help searches to increase the hit rate of the module. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class HelpTagsAttribute : Attribute { - /// - /// Indicates tags to use during help searches to increase the hit rate of the module. - /// - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class HelpTagsAttribute : Attribute - { - public HelpTagsAttribute(params string[] tags) { Tags = tags; } + public HelpTagsAttribute(params string[] tags) { Tags = tags; } - public string[] Tags { get; } - } + public string[] Tags { get; } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/HiddenFromHelpAttribute.cs b/Zhongli.Services/CommandHelp/HiddenFromHelpAttribute.cs index 7b76f46..ad62c14 100644 --- a/Zhongli.Services/CommandHelp/HiddenFromHelpAttribute.cs +++ b/Zhongli.Services/CommandHelp/HiddenFromHelpAttribute.cs @@ -1,10 +1,9 @@ using System; -namespace Zhongli.Services.CommandHelp -{ - /// - /// Hides the module or command from display - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class HiddenFromHelpAttribute : Attribute { } -} \ No newline at end of file +namespace Zhongli.Services.CommandHelp; + +/// +/// Hides the module or command from display +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class HiddenFromHelpAttribute : Attribute { } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/ModuleHelpData.cs b/Zhongli.Services/CommandHelp/ModuleHelpData.cs index 9fc0e46..9f59bae 100644 --- a/Zhongli.Services/CommandHelp/ModuleHelpData.cs +++ b/Zhongli.Services/CommandHelp/ModuleHelpData.cs @@ -4,47 +4,46 @@ using Discord.Commands; using Humanizer; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +public class ModuleHelpData { - public class ModuleHelpData - { - public IReadOnlyCollection Commands { get; set; } + public IReadOnlyCollection Commands { get; set; } + + public IReadOnlyCollection HelpTags { get; set; } - public IReadOnlyCollection HelpTags { get; set; } + public string Name { get; set; } + + public string? Summary { get; set; } + + public static ModuleHelpData FromModuleInfo(ModuleInfo module) + { + var moduleName = module.Name; - public string Name { get; set; } + var suffixPosition = moduleName.IndexOf("Module", StringComparison.Ordinal); + if (suffixPosition > -1) moduleName = module.Name[..suffixPosition].Humanize(); - public string? Summary { get; set; } + moduleName = moduleName.ApplyCase(LetterCasing.Title); - public static ModuleHelpData FromModuleInfo(ModuleInfo module) + var ret = new ModuleHelpData { - var moduleName = module.Name; - - var suffixPosition = moduleName.IndexOf("Module", StringComparison.Ordinal); - if (suffixPosition > -1) moduleName = module.Name[..suffixPosition].Humanize(); - - moduleName = moduleName.ApplyCase(LetterCasing.Title); - - var ret = new ModuleHelpData - { - Name = moduleName, - Summary = string.IsNullOrWhiteSpace(module.Summary) ? "No Summary." : module.Summary, - Commands = module.Commands - .Where(x => !ShouldBeHidden(x)) - .Select(CommandHelpData.FromCommandInfo) - .ToArray(), - HelpTags = module.Attributes - .OfType() - .SingleOrDefault() - ?.Tags - ?? Array.Empty() - }; - - return ret; - - static bool ShouldBeHidden(CommandInfo command) => - command.Preconditions.Any(x => x is RequireOwnerAttribute) - || command.Attributes.Any(x => x is HiddenFromHelpAttribute); - } + Name = moduleName, + Summary = string.IsNullOrWhiteSpace(module.Summary) ? "No Summary." : module.Summary, + Commands = module.Commands + .Where(x => !ShouldBeHidden(x)) + .Select(CommandHelpData.FromCommandInfo) + .ToArray(), + HelpTags = module.Attributes + .OfType() + .SingleOrDefault() + ?.Tags + ?? Array.Empty() + }; + + return ret; + + static bool ShouldBeHidden(CommandInfo command) => + command.Preconditions.Any(x => x is RequireOwnerAttribute) + || command.Attributes.Any(x => x is HiddenFromHelpAttribute); } } \ No newline at end of file diff --git a/Zhongli.Services/CommandHelp/ParameterHelpData.cs b/Zhongli.Services/CommandHelp/ParameterHelpData.cs index 7acdc83..887c2fe 100644 --- a/Zhongli.Services/CommandHelp/ParameterHelpData.cs +++ b/Zhongli.Services/CommandHelp/ParameterHelpData.cs @@ -7,75 +7,74 @@ using Namotion.Reflection; using Zhongli.Services.Utilities; -namespace Zhongli.Services.CommandHelp +namespace Zhongli.Services.CommandHelp; + +public class ParameterHelpData { - public class ParameterHelpData + private IEnumerable _options = null!; + + private ParameterHelpData( + string name, Type type, + string? summary = null, + bool isOptional = false) { - private IEnumerable _options = null!; + Name = name; + Summary = summary; + Type = type; + IsOptional = isOptional; + } - private ParameterHelpData( - string name, Type type, - string? summary = null, - bool isOptional = false) - { - Name = name; - Summary = summary; - Type = type; - IsOptional = isOptional; - } + public bool IsOptional { get; set; } - public bool IsOptional { get; set; } + public IEnumerable Options => LazyInitializer.EnsureInitialized(ref _options, () => + { + var type = Type.GetGenericArguments().FirstOrDefault() ?? Type; - public IEnumerable Options => LazyInitializer.EnsureInitialized(ref _options, () => + return type switch { - var type = Type.GetGenericArguments().FirstOrDefault() ?? Type; + var t when t.IsEnum => FromEnum(t), + var t when t.GetAttribute() is not null => FromNamedArgumentInfo(t), + _ => Enumerable.Empty() + }; + }); - return type switch - { - var t when t.IsEnum => FromEnum(t), - var t when t.GetAttribute() is not null => FromNamedArgumentInfo(t), - _ => Enumerable.Empty() - }; - }); + public string Name { get; set; } - public string Name { get; set; } + public string? Summary { get; set; } - public string? Summary { get; set; } + public Type Type { get; set; } - public Type Type { get; set; } + public static ParameterHelpData FromParameterInfo(ParameterInfo parameter) + { + var type = parameter.Type.ToContextualType(); + return new ParameterHelpData(parameter.Name, type.Type, parameter.Summary, + parameter.IsOptional || type.Nullability == Nullability.Nullable); + } - public static ParameterHelpData FromParameterInfo(ParameterInfo parameter) - { - var type = parameter.Type.ToContextualType(); - return new ParameterHelpData(parameter.Name, type.Type, parameter.Summary, - parameter.IsOptional || type.Nullability == Nullability.Nullable); - } + private static IEnumerable FromEnum(Type type) + { + return type.GetEnumValues() + .OfType() + .Select(e => + { + var summary = + e.GetAttributeOfEnum()?.Text ?? + e.GetAttributeOfEnum()?.Description; - private static IEnumerable FromEnum(Type type) - { - return type.GetEnumValues() - .OfType() - .Select(e => - { - var summary = - e.GetAttributeOfEnum()?.Text ?? - e.GetAttributeOfEnum()?.Description; + return new ParameterHelpData(e.ToString(), type, summary); + }); + } - return new ParameterHelpData(e.ToString(), type, summary); - }); - } + private static IEnumerable FromNamedArgumentInfo(Type type) + { + var properties = type.GetProperties(); - private static IEnumerable FromNamedArgumentInfo(Type type) + return properties.Select(p => { - var properties = type.GetProperties(); - - return properties.Select(p => - { - var info = p.ToContextualProperty(); - return new ParameterHelpData(info.Name, info.PropertyType, - info.GetContextAttribute()?.Text, - info.Nullability == Nullability.Nullable); - }); - } + var info = p.ToContextualProperty(); + return new ParameterHelpData(info.Name, info.PropertyType, + info.GetContextAttribute()?.Text, + info.Nullability == Nullability.Nullable); + }); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/AuthorizationRuleExtensions.cs b/Zhongli.Services/Core/AuthorizationRuleExtensions.cs index 575636a..368c0d5 100644 --- a/Zhongli.Services/Core/AuthorizationRuleExtensions.cs +++ b/Zhongli.Services/Core/AuthorizationRuleExtensions.cs @@ -6,22 +6,21 @@ using Zhongli.Data.Models.Criteria; using Zhongli.Data.Models.Moderation.Infractions; -namespace Zhongli.Services.Core +namespace Zhongli.Services.Core; + +public static class AuthorizationRuleExtensions { - public static class AuthorizationRuleExtensions - { - public static bool Judge(this AuthorizationGroup rules, ICommandContext context, IGuildUser user) => - rules.Collection.Any(r => r.Judge(context, user)); + public static bool Judge(this AuthorizationGroup rules, ICommandContext context, IGuildUser user) => + rules.Collection.Any(r => r.Judge(context, user)); - public static IEnumerable Scoped( - this IEnumerable rules, AuthorizationScope scope) where T : AuthorizationGroup - => rules.Where(rule => (rule.Scope & scope) != 0); + public static IEnumerable Scoped( + this IEnumerable rules, AuthorizationScope scope) where T : AuthorizationGroup + => rules.Where(rule => (rule.Scope & scope) != 0); - public static void AddRules(this ICollection group, - AuthorizationScope scope, IGuildUser moderator, AccessType accessType, - params Criterion[] rules) - { - group.Add(new AuthorizationGroup(scope, accessType, rules).WithModerator(moderator)); - } + public static void AddRules(this ICollection group, + AuthorizationScope scope, IGuildUser moderator, AccessType accessType, + params Criterion[] rules) + { + group.Add(new AuthorizationGroup(scope, accessType, rules).WithModerator(moderator)); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/AuthorizationService.cs b/Zhongli.Services/Core/AuthorizationService.cs index e39486b..0027d12 100644 --- a/Zhongli.Services/Core/AuthorizationService.cs +++ b/Zhongli.Services/Core/AuthorizationService.cs @@ -10,49 +10,48 @@ using Zhongli.Services.Utilities; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Services.Core +namespace Zhongli.Services.Core; + +public class AuthorizationService { - public class AuthorizationService + private readonly ZhongliContext _db; + + public AuthorizationService(ZhongliContext db) { _db = db; } + + public async Task IsAuthorizedAsync(ICommandContext context, AuthorizationScope scope, + CancellationToken cancellationToken = default) { - private readonly ZhongliContext _db; - - public AuthorizationService(ZhongliContext db) { _db = db; } - - public async Task IsAuthorizedAsync(ICommandContext context, AuthorizationScope scope, - CancellationToken cancellationToken = default) - { - var user = (IGuildUser) context.User; - var rules = await AutoConfigureGuild(user.Guild, cancellationToken); - return rules.AuthorizationGroups.Scoped(scope) - .OrderBy(r => r.Action.Date) - .Aggregate(false, (current, rule) => - { - var passed = rule.Judge(context, user); - return passed ? rule.Access == AccessType.Allow : current; - }); - } - - public async Task AutoConfigureGuild(IGuild guild, - CancellationToken cancellationToken = default) - { - var guildEntity = await GetGuildAsync(guild, cancellationToken); - var auth = guildEntity.AuthorizationGroups; - - if (auth.Any()) return guildEntity; - - var permission = new PermissionCriterion(GuildPermission.Administrator); - auth.AddRules(AuthorizationScope.All, await guild.GetCurrentUserAsync(), AccessType.Allow, permission); - await _db.SaveChangesAsync(cancellationToken); - - return guildEntity; - } - - private async Task GetGuildAsync(IGuild guild, CancellationToken cancellationToken = default) - { - var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); - await _db.Users.TrackUserAsync(await guild.GetCurrentUserAsync(), cancellationToken); - - return guildEntity; - } + var user = (IGuildUser) context.User; + var rules = await AutoConfigureGuild(user.Guild, cancellationToken); + return rules.AuthorizationGroups.Scoped(scope) + .OrderBy(r => r.Action.Date) + .Aggregate(false, (current, rule) => + { + var passed = rule.Judge(context, user); + return passed ? rule.Access == AccessType.Allow : current; + }); + } + + public async Task AutoConfigureGuild(IGuild guild, + CancellationToken cancellationToken = default) + { + var guildEntity = await GetGuildAsync(guild, cancellationToken); + var auth = guildEntity.AuthorizationGroups; + + if (auth.Any()) return guildEntity; + + var permission = new PermissionCriterion(GuildPermission.Administrator); + auth.AddRules(AuthorizationScope.All, await guild.GetCurrentUserAsync(), AccessType.Allow, permission); + await _db.SaveChangesAsync(cancellationToken); + + return guildEntity; + } + + private async Task GetGuildAsync(IGuild guild, CancellationToken cancellationToken = default) + { + var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); + await _db.Users.TrackUserAsync(await guild.GetCurrentUserAsync(), cancellationToken); + + return guildEntity; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/CriteriaExtensions.cs b/Zhongli.Services/Core/CriteriaExtensions.cs index 58781c6..5992199 100644 --- a/Zhongli.Services/Core/CriteriaExtensions.cs +++ b/Zhongli.Services/Core/CriteriaExtensions.cs @@ -7,83 +7,82 @@ using Zhongli.Data.Models.Discord; using GuildPermission = Zhongli.Data.Models.Discord.GuildPermission; -namespace Zhongli.Services.Core +namespace Zhongli.Services.Core; + +public static class CriteriaExtensions { - public static class CriteriaExtensions - { - public static bool Judge(this Criterion rule, ICommandContext context, IGuildUser user) - => Judge(rule, (ITextChannel) context.Channel, user); + public static bool Judge(this Criterion rule, ICommandContext context, IGuildUser user) + => Judge(rule, (ITextChannel) context.Channel, user); - public static bool Judge(this Criterion rule, INestedChannel channel, IGuildUser user) + public static bool Judge(this Criterion rule, INestedChannel channel, IGuildUser user) + { + return rule switch { - return rule switch - { - UserCriterion auth => auth.Judge(user), - RoleCriterion auth => auth.Judge(user), - PermissionCriterion auth => auth.Judge(user), - ChannelCriterion auth => auth.Judge(channel), - _ => false - }; - } + UserCriterion auth => auth.Judge(user), + RoleCriterion auth => auth.Judge(user), + PermissionCriterion auth => auth.Judge(user), + ChannelCriterion auth => auth.Judge(channel), + _ => false + }; + } - public static ICollection AddCriteria(this ICollection collection, - ICriteriaOptions options) - { - var channels = options.Channels?.Where(c => c is ICategoryChannel or ITextChannel); + public static ICollection AddCriteria(this ICollection collection, + ICriteriaOptions options) + { + var channels = options.Channels?.Where(c => c is ICategoryChannel or ITextChannel); - var rules = collection - .AddCriteria(options.Users, u => new UserCriterion(u.Id)) - .AddCriteria(channels, c => new ChannelCriterion(c.Id, c is ICategoryChannel)) - .AddCriteria(options.Roles, r => new RoleCriterion(r)); + var rules = collection + .AddCriteria(options.Users, u => new UserCriterion(u.Id)) + .AddCriteria(channels, c => new ChannelCriterion(c.Id, c is ICategoryChannel)) + .AddCriteria(options.Roles, r => new RoleCriterion(r)); - if (options.Permission is not GuildPermission.None) - rules.Add(new PermissionCriterion(options.Permission)); + if (options.Permission is not GuildPermission.None) + rules.Add(new PermissionCriterion(options.Permission)); - return rules; - } + return rules; + } - public static ICollection ToCriteria(this ICriteriaOptions options) - => new List().AddCriteria(options); + public static ICollection ToCriteria(this ICriteriaOptions options) + => new List().AddCriteria(options); - public static Type GetCriterionType(this Criterion criterion) + public static Type GetCriterionType(this Criterion criterion) + { + return criterion switch { - return criterion switch - { - UserCriterion => typeof(UserCriterion), - RoleCriterion => typeof(RoleCriterion), - PermissionCriterion => typeof(PermissionCriterion), - ChannelCriterion => typeof(ChannelCriterion), - _ => throw new ArgumentOutOfRangeException(nameof(criterion), criterion, - "Unknown kind of Criterion.") - }; - } - - private static bool Judge(this IUserEntity auth, IGuildUser user) - => auth.UserId == user.Id; - - private static bool Judge(this IRoleEntity auth, IGuildUser user) - => user.RoleIds.Contains(auth.RoleId); + UserCriterion => typeof(UserCriterion), + RoleCriterion => typeof(RoleCriterion), + PermissionCriterion => typeof(PermissionCriterion), + ChannelCriterion => typeof(ChannelCriterion), + _ => throw new ArgumentOutOfRangeException(nameof(criterion), criterion, + "Unknown kind of Criterion.") + }; + } - private static bool Judge(this IPermissionEntity auth, IGuildUser user) - => (auth.Permission & (GuildPermission) user.GuildPermissions.RawValue) != 0; + private static bool Judge(this IUserEntity auth, IGuildUser user) + => auth.UserId == user.Id; - private static bool Judge(this IGuildChannelEntity auth, INestedChannel channel) - => auth.IsCategory - ? auth.ChannelId == channel.CategoryId - : auth.ChannelId == channel.Id; + private static bool Judge(this IRoleEntity auth, IGuildUser user) + => user.RoleIds.Contains(auth.RoleId); - private static ICollection AddCriteria(this ICollection collection, - IEnumerable? source, Func func) - { - if (source is null) - return collection; + private static bool Judge(this IPermissionEntity auth, IGuildUser user) + => (auth.Permission & (GuildPermission) user.GuildPermissions.RawValue) != 0; - foreach (var item in source) - { - collection.Add(func(item)); - } + private static bool Judge(this IGuildChannelEntity auth, INestedChannel channel) + => auth.IsCategory + ? auth.ChannelId == channel.CategoryId + : auth.ChannelId == channel.Id; + private static ICollection AddCriteria(this ICollection collection, + IEnumerable? source, Func func) + { + if (source is null) return collection; + + foreach (var item in source) + { + collection.Add(func(item)); } + + return collection; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Listeners/CommandErrorHandler.cs b/Zhongli.Services/Core/Listeners/CommandErrorHandler.cs index 296b9aa..11b8c92 100644 --- a/Zhongli.Services/Core/Listeners/CommandErrorHandler.cs +++ b/Zhongli.Services/Core/Listeners/CommandErrorHandler.cs @@ -7,113 +7,112 @@ using Microsoft.Extensions.Caching.Memory; using Zhongli.Services.Core.Messages; -namespace Zhongli.Services.Core.Listeners +namespace Zhongli.Services.Core.Listeners; + +public class CommandErrorHandler : + INotificationHandler, + INotificationHandler { - public class CommandErrorHandler : - INotificationHandler, - INotificationHandler + private const string AssociatedErrorsKey = nameof(CommandErrorHandler) + ".AssociatedErrors"; + private const string ErrorRepliesKey = nameof(CommandErrorHandler) + ".ErrorReplies"; + private const string Emoji = "⚠"; + private readonly DiscordSocketClient _discordSocketClient; + private readonly IEmote _emote = new Emoji(Emoji); + private readonly IMemoryCache _memoryCache; + + public CommandErrorHandler(DiscordSocketClient discordSocketClient, IMemoryCache memoryCache) { - private const string AssociatedErrorsKey = nameof(CommandErrorHandler) + ".AssociatedErrors"; - private const string ErrorRepliesKey = nameof(CommandErrorHandler) + ".ErrorReplies"; - private const string Emoji = "⚠"; - private readonly DiscordSocketClient _discordSocketClient; - private readonly IEmote _emote = new Emoji(Emoji); - private readonly IMemoryCache _memoryCache; - - public CommandErrorHandler(DiscordSocketClient discordSocketClient, IMemoryCache memoryCache) - { - _discordSocketClient = discordSocketClient; - _memoryCache = memoryCache; - } + _discordSocketClient = discordSocketClient; + _memoryCache = memoryCache; + } - //This relates user messages with errors - private ConcurrentDictionary AssociatedErrors => - _memoryCache.GetOrCreate(AssociatedErrorsKey, _ => new ConcurrentDictionary()); + //This relates user messages with errors + private ConcurrentDictionary AssociatedErrors => + _memoryCache.GetOrCreate(AssociatedErrorsKey, _ => new ConcurrentDictionary()); - //This relates user messages to messages containing errors - private ConcurrentDictionary ErrorReplies => - _memoryCache.GetOrCreate(ErrorRepliesKey, _ => new ConcurrentDictionary()); + //This relates user messages to messages containing errors + private ConcurrentDictionary ErrorReplies => + _memoryCache.GetOrCreate(ErrorRepliesKey, _ => new ConcurrentDictionary()); - public Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) - => ReactionAdded(notification.Message, notification.Channel, notification.Reaction); + public Task Handle(ReactionAddedNotification notification, CancellationToken cancellationToken) + => ReactionAdded(notification.Message, notification.Channel, notification.Reaction); - public Task Handle(ReactionRemovedNotification notification, - CancellationToken cancellationToken) - => ReactionRemoved(notification.Message, notification.Channel, notification.Reaction); + public Task Handle(ReactionRemovedNotification notification, + CancellationToken cancellationToken) + => ReactionRemoved(notification.Message, notification.Channel, notification.Reaction); - /// - /// Associates a user message with an error - /// - /// The message containing an errored command - /// The error that occurred - /// - public async Task AssociateError(IUserMessage message, string error) - { - if (AssociatedErrors.TryAdd(message.Id, error)) await message.AddReactionAsync(new Emoji(Emoji)); - } + /// + /// Associates a user message with an error + /// + /// The message containing an errored command + /// The error that occurred + /// + public async Task AssociateError(IUserMessage message, string error) + { + if (AssociatedErrors.TryAdd(message.Id, error)) await message.AddReactionAsync(new Emoji(Emoji)); + } - private async Task ReactionAdded(Cacheable cachedMessage, ISocketMessageChannel channel, - 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 + private async Task ReactionAdded(Cacheable cachedMessage, ISocketMessageChannel channel, + 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.User.IsSpecified && reaction.User.Value.IsBot) return; - if (reaction.Emote.Name != Emoji || ErrorReplies.ContainsKey(cachedMessage.Id)) 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)) + //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 msg = await channel.SendMessageAsync("", false, new EmbedBuilder { - var msg = await channel.SendMessageAsync("", false, new EmbedBuilder + Author = new EmbedAuthorBuilder { - 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(); - } + 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(); } + } - private async Task ReactionRemoved(Cacheable cachedMessage, ISocketMessageChannel channel, - SocketReaction reaction) - { - //Bugfix for NRE? - if (reaction?.User.Value is null) return; + private async Task ReactionRemoved(Cacheable cachedMessage, ISocketMessageChannel channel, + SocketReaction reaction) + { + //Bugfix for NRE? + if (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; + //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 (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; + //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; - await channel.DeleteMessageAsync(botReplyId); + await channel.DeleteMessageAsync(botReplyId); - if - ( - AssociatedErrors.TryRemove(cachedMessage.Id, out _) && - ErrorReplies.TryRemove(cachedMessage.Id, out _) - ) - { - var originalMessage = await cachedMessage.GetOrDownloadAsync(); + 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 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); + if (reaction.User.IsSpecified) await originalMessage.RemoveReactionAsync(_emote, reaction.User.Value); - await originalMessage.RemoveReactionAsync(_emote, _discordSocketClient.CurrentUser); - } + await originalMessage.RemoveReactionAsync(_emote, _discordSocketClient.CurrentUser); } } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Listeners/CommandHandlingService.cs b/Zhongli.Services/Core/Listeners/CommandHandlingService.cs index d8c6856..061ef3e 100644 --- a/Zhongli.Services/Core/Listeners/CommandHandlingService.cs +++ b/Zhongli.Services/Core/Listeners/CommandHandlingService.cs @@ -15,97 +15,96 @@ using Zhongli.Services.Core.TypeReaders; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Core.Listeners +namespace Zhongli.Services.Core.Listeners; + +public class CommandHandlingService : INotificationHandler { - public class CommandHandlingService : INotificationHandler + private readonly CommandErrorHandler _errorHandler; + private readonly CommandService _commands; + private readonly DiscordSocketClient _discord; + private readonly ILogger _log; + private readonly IServiceProvider _services; + + public CommandHandlingService( + CommandErrorHandler errorHandler, + CommandService commands, + DiscordSocketClient discord, + ILogger log, + IServiceProvider services) { - private readonly CommandErrorHandler _errorHandler; - private readonly CommandService _commands; - private readonly DiscordSocketClient _discord; - private readonly ILogger _log; - private readonly IServiceProvider _services; - - public CommandHandlingService( - CommandErrorHandler errorHandler, - CommandService commands, - DiscordSocketClient discord, - ILogger log, - IServiceProvider services) - { - _errorHandler = errorHandler; - _commands = commands; - _discord = discord; - _log = log; - _services = services; + _errorHandler = errorHandler; + _commands = commands; + _discord = discord; + _log = log; + _services = services; - _commands.CommandExecuted += CommandExecutedAsync; - } + _commands.CommandExecuted += CommandExecutedAsync; + } - public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) - { - var rawMessage = notification.Message; - if (rawMessage is not SocketUserMessage { Source: MessageSource.User } message) - return; + public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) + { + var rawMessage = notification.Message; + if (rawMessage is not SocketUserMessage { Source: MessageSource.User } message) + return; - var argPos = 0; - var context = new SocketCommandContext(_discord, message); + var argPos = 0; + var context = new SocketCommandContext(_discord, message); - var hasPrefix = message.HasStringPrefix(ZhongliConfig.Configuration.Prefix, ref argPos, - StringComparison.OrdinalIgnoreCase); - var hasMention = message.HasMentionPrefix(_discord.CurrentUser, ref argPos); - if (hasPrefix || hasMention) + var hasPrefix = message.HasStringPrefix(ZhongliConfig.Configuration.Prefix, ref argPos, + StringComparison.OrdinalIgnoreCase); + var hasMention = message.HasMentionPrefix(_discord.CurrentUser, ref argPos); + if (hasPrefix || hasMention) + { + var result = await _commands.ExecuteAsync(context, argPos, _services, MultiMatchHandling.Best); + if (result is null) { - var result = await _commands.ExecuteAsync(context, argPos, _services, MultiMatchHandling.Best); - if (result is null) - { - _log.LogWarning("Command on guild {Guild} ran by user {Author} is null", context.Guild, - message.Author); - } - else if (!result.IsSuccess) - await CommandFailedAsync(context, result); + _log.LogWarning("Command on guild {Guild} ran by user {Author} is null", context.Guild, + message.Author); } + else if (!result.IsSuccess) + await CommandFailedAsync(context, result); } + } - public async Task InitializeAsync() - { - _commands.AddTypeReader(new HexColorTypeReader()); + public async Task InitializeAsync() + { + _commands.AddTypeReader(new HexColorTypeReader()); - _commands.AddTypeReader(new JumpUrlTypeReader()); - _commands.AddTypeReader(new TypeReaders.MessageTypeReader()); - _commands.AddTypeReader(new TypeReaders.UserTypeReader(CacheMode.AllowDownload, true)); + _commands.AddTypeReader(new JumpUrlTypeReader()); + _commands.AddTypeReader(new TypeReaders.MessageTypeReader()); + _commands.AddTypeReader(new TypeReaders.UserTypeReader(CacheMode.AllowDownload, true)); - _commands.AddTypeReader( - new EnumFlagsTypeReader( - splitOptions: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); + _commands.AddTypeReader( + new EnumFlagsTypeReader( + splitOptions: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); - _commands.AddTypeReader( - new EnumFlagsTypeReader( - splitOptions: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); + _commands.AddTypeReader( + new EnumFlagsTypeReader( + splitOptions: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); - _commands.AddEnumerableTypeReader(new EnumTryParseTypeReader()); + _commands.AddEnumerableTypeReader(new EnumTryParseTypeReader()); - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); - } + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } - private static Task CommandExecutedAsync( - Optional command, ICommandContext context, IResult result) => Task.CompletedTask; + private static Task CommandExecutedAsync( + Optional command, ICommandContext context, IResult result) => Task.CompletedTask; - private async Task CommandFailedAsync(ICommandContext context, IResult result) - { - var error = $"{result.Error}: {result.ErrorReason}"; + private async Task CommandFailedAsync(ICommandContext context, IResult result) + { + var error = $"{result.Error}: {result.ErrorReason}"; - if (string.Equals(result.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) - Log.Warning("{Error}: {ErrorReason}", result.Error, result.ErrorReason); - else - Log.Error("{Error}: {ErrorReason}", result.Error, result.ErrorReason); + if (string.Equals(result.ErrorReason, "UnknownCommand", StringComparison.OrdinalIgnoreCase)) + Log.Warning("{Error}: {ErrorReason}", result.Error, result.ErrorReason); + else + Log.Error("{Error}: {ErrorReason}", result.Error, result.ErrorReason); - if (result.Error == CommandError.Exception) - { - await context.Channel.SendMessageAsync( - $"Error: {FormatUtilities.SanitizeEveryone(result.ErrorReason)}"); - } - else - await _errorHandler.AssociateError(context.Message, error); + if (result.Error == CommandError.Exception) + { + await context.Channel.SendMessageAsync( + $"Error: {FormatUtilities.SanitizeEveryone(result.ErrorReason)}"); } + else + await _errorHandler.AssociateError(context.Message, error); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Listeners/DiscordSocketListener.cs b/Zhongli.Services/Core/Listeners/DiscordSocketListener.cs index 4b4565b..e185a1a 100644 --- a/Zhongli.Services/Core/Listeners/DiscordSocketListener.cs +++ b/Zhongli.Services/Core/Listeners/DiscordSocketListener.cs @@ -8,204 +8,158 @@ using Microsoft.Extensions.DependencyInjection; using Zhongli.Services.Core.Messages; -namespace Zhongli.Services.Core.Listeners +namespace Zhongli.Services.Core.Listeners; + +/// +/// Listens for events from an and dispatches them to the rest of the application, +/// through an . +/// +public class DiscordSocketListener { + private CancellationToken _cancellationToken; + /// - /// Listens for events from an and dispatches them to the rest of the application, - /// through an . + /// Constructs a new with the given dependencies. /// - public class DiscordSocketListener + public DiscordSocketListener(DiscordSocketClient discordSocketClient, IServiceScopeFactory serviceScope) { - private CancellationToken _cancellationToken; - - /// - /// Constructs a new with the given dependencies. - /// - public DiscordSocketListener(DiscordSocketClient discordSocketClient, IServiceScopeFactory serviceScope) - { - DiscordSocketClient = discordSocketClient; - ServiceScope = serviceScope; - } + DiscordSocketClient = discordSocketClient; + ServiceScope = serviceScope; + } - /// - /// The to be listened to. - /// - private DiscordSocketClient DiscordSocketClient { get; } + /// + /// The to be listened to. + /// + private DiscordSocketClient DiscordSocketClient { get; } - /// - /// Gets a scoped . - /// - private IMediator Mediator + /// + /// Gets a scoped . + /// + private IMediator Mediator + { + get { - get - { - var scope = ServiceScope.CreateScope(); - return scope.ServiceProvider.GetRequiredService(); - } + var scope = ServiceScope.CreateScope(); + return scope.ServiceProvider.GetRequiredService(); } + } - /// - /// The to be used. - /// - private IServiceScopeFactory ServiceScope { get; } + /// + /// The to be used. + /// + private IServiceScopeFactory ServiceScope { get; } - public Task StartAsync(CancellationToken cancellationToken) - { - _cancellationToken = cancellationToken; - DiscordSocketClient.ChannelCreated += OnChannelCreatedAsync; - DiscordSocketClient.ChannelUpdated += OnChannelUpdatedAsync; - DiscordSocketClient.GuildAvailable += OnGuildAvailableAsync; - DiscordSocketClient.GuildMemberUpdated += OnGuildMemberUpdatedAsync; - DiscordSocketClient.JoinedGuild += OnJoinedGuildAsync; - DiscordSocketClient.MessageDeleted += OnMessageDeletedAsync; - DiscordSocketClient.MessagesBulkDeleted += OnMessagesBulkDeletedAsync; - DiscordSocketClient.MessageReceived += OnMessageReceivedAsync; - DiscordSocketClient.MessageUpdated += OnMessageUpdatedAsync; - DiscordSocketClient.ReactionAdded += OnReactionAddedAsync; - DiscordSocketClient.ReactionRemoved += OnReactionRemovedAsync; - DiscordSocketClient.Ready += OnReadyAsync; - DiscordSocketClient.Connected += OnConnectedAsync; - DiscordSocketClient.Disconnected += OnDisconnectedAsync; - DiscordSocketClient.RoleCreated += OnRoleCreatedAsync; - DiscordSocketClient.RoleUpdated += OnRoleUpdatedAsync; - DiscordSocketClient.UserBanned += OnUserBannedAsync; - DiscordSocketClient.UserJoined += OnUserJoinedAsync; - DiscordSocketClient.UserLeft += OnUserLeftAsync; - DiscordSocketClient.UserVoiceStateUpdated += OnUserVoiceStateUpdatedAsync; - - return Task.CompletedTask; - } + public Task StartAsync(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + DiscordSocketClient.ChannelCreated += OnChannelCreatedAsync; + DiscordSocketClient.ChannelUpdated += OnChannelUpdatedAsync; + DiscordSocketClient.GuildAvailable += OnGuildAvailableAsync; + DiscordSocketClient.GuildMemberUpdated += OnGuildMemberUpdatedAsync; + DiscordSocketClient.JoinedGuild += OnJoinedGuildAsync; + DiscordSocketClient.MessageDeleted += OnMessageDeletedAsync; + DiscordSocketClient.MessagesBulkDeleted += OnMessagesBulkDeletedAsync; + DiscordSocketClient.MessageReceived += OnMessageReceivedAsync; + DiscordSocketClient.MessageUpdated += OnMessageUpdatedAsync; + DiscordSocketClient.ReactionAdded += OnReactionAddedAsync; + DiscordSocketClient.ReactionRemoved += OnReactionRemovedAsync; + DiscordSocketClient.Ready += OnReadyAsync; + DiscordSocketClient.Connected += OnConnectedAsync; + DiscordSocketClient.Disconnected += OnDisconnectedAsync; + DiscordSocketClient.RoleCreated += OnRoleCreatedAsync; + DiscordSocketClient.RoleUpdated += OnRoleUpdatedAsync; + DiscordSocketClient.UserBanned += OnUserBannedAsync; + DiscordSocketClient.UserJoined += OnUserJoinedAsync; + DiscordSocketClient.UserLeft += OnUserLeftAsync; + DiscordSocketClient.UserVoiceStateUpdated += OnUserVoiceStateUpdatedAsync; + + return Task.CompletedTask; + } - public Task StopAsync(CancellationToken cancellationToken) - { - DiscordSocketClient.ChannelCreated -= OnChannelCreatedAsync; - DiscordSocketClient.ChannelUpdated -= OnChannelUpdatedAsync; - DiscordSocketClient.GuildAvailable -= OnGuildAvailableAsync; - DiscordSocketClient.GuildMemberUpdated -= OnGuildMemberUpdatedAsync; - DiscordSocketClient.JoinedGuild -= OnJoinedGuildAsync; - DiscordSocketClient.MessageDeleted -= OnMessageDeletedAsync; - DiscordSocketClient.MessagesBulkDeleted -= OnMessagesBulkDeletedAsync; - DiscordSocketClient.MessageReceived -= OnMessageReceivedAsync; - DiscordSocketClient.MessageUpdated -= OnMessageUpdatedAsync; - DiscordSocketClient.ReactionAdded -= OnReactionAddedAsync; - DiscordSocketClient.ReactionRemoved -= OnReactionRemovedAsync; - DiscordSocketClient.Ready -= OnReadyAsync; - DiscordSocketClient.Connected -= OnConnectedAsync; - DiscordSocketClient.Disconnected -= OnDisconnectedAsync; - DiscordSocketClient.RoleCreated -= OnRoleCreatedAsync; - DiscordSocketClient.RoleUpdated -= OnRoleUpdatedAsync; - DiscordSocketClient.UserBanned -= OnUserBannedAsync; - DiscordSocketClient.UserJoined -= OnUserJoinedAsync; - DiscordSocketClient.UserLeft -= OnUserLeftAsync; - DiscordSocketClient.UserVoiceStateUpdated -= OnUserVoiceStateUpdatedAsync; - - return Task.CompletedTask; - } + public Task StopAsync(CancellationToken cancellationToken) + { + DiscordSocketClient.ChannelCreated -= OnChannelCreatedAsync; + DiscordSocketClient.ChannelUpdated -= OnChannelUpdatedAsync; + DiscordSocketClient.GuildAvailable -= OnGuildAvailableAsync; + DiscordSocketClient.GuildMemberUpdated -= OnGuildMemberUpdatedAsync; + DiscordSocketClient.JoinedGuild -= OnJoinedGuildAsync; + DiscordSocketClient.MessageDeleted -= OnMessageDeletedAsync; + DiscordSocketClient.MessagesBulkDeleted -= OnMessagesBulkDeletedAsync; + DiscordSocketClient.MessageReceived -= OnMessageReceivedAsync; + DiscordSocketClient.MessageUpdated -= OnMessageUpdatedAsync; + DiscordSocketClient.ReactionAdded -= OnReactionAddedAsync; + DiscordSocketClient.ReactionRemoved -= OnReactionRemovedAsync; + DiscordSocketClient.Ready -= OnReadyAsync; + DiscordSocketClient.Connected -= OnConnectedAsync; + DiscordSocketClient.Disconnected -= OnDisconnectedAsync; + DiscordSocketClient.RoleCreated -= OnRoleCreatedAsync; + DiscordSocketClient.RoleUpdated -= OnRoleUpdatedAsync; + DiscordSocketClient.UserBanned -= OnUserBannedAsync; + DiscordSocketClient.UserJoined -= OnUserJoinedAsync; + DiscordSocketClient.UserLeft -= OnUserLeftAsync; + DiscordSocketClient.UserVoiceStateUpdated -= OnUserVoiceStateUpdatedAsync; + + return Task.CompletedTask; + } - private async Task OnChannelCreatedAsync(SocketChannel channel) - { - await Mediator.Publish(new ChannelCreatedNotification(channel), _cancellationToken); - } + private async Task OnChannelCreatedAsync(SocketChannel channel) { await Mediator.Publish(new ChannelCreatedNotification(channel), _cancellationToken); } - private async Task OnChannelUpdatedAsync(SocketChannel oldChannel, SocketChannel newChannel) - { - await Mediator.Publish(new ChannelUpdatedNotification(oldChannel, newChannel), _cancellationToken); - } + private async Task OnChannelUpdatedAsync(SocketChannel oldChannel, SocketChannel newChannel) { await Mediator.Publish(new ChannelUpdatedNotification(oldChannel, newChannel), _cancellationToken); } - private async Task OnConnectedAsync() - { - await Mediator.Publish(ConnectedNotification.Default, _cancellationToken); - } + private async Task OnConnectedAsync() { await Mediator.Publish(ConnectedNotification.Default, _cancellationToken); } - private async Task OnDisconnectedAsync(Exception arg) - { - await Mediator.Publish(new DisconnectedNotification(arg), _cancellationToken); - } + private async Task OnDisconnectedAsync(Exception arg) { await Mediator.Publish(new DisconnectedNotification(arg), _cancellationToken); } - private async Task OnGuildAvailableAsync(SocketGuild guild) - { - await Mediator.Publish(new GuildAvailableNotification(guild), _cancellationToken); - } + private async Task OnGuildAvailableAsync(SocketGuild guild) { await Mediator.Publish(new GuildAvailableNotification(guild), _cancellationToken); } - private async Task OnGuildMemberUpdatedAsync(SocketGuildUser oldMember, SocketGuildUser newMember) - { - await Mediator.Publish(new GuildMemberUpdatedNotification(oldMember, newMember), _cancellationToken); - } + private async Task OnGuildMemberUpdatedAsync(SocketGuildUser oldMember, SocketGuildUser newMember) { await Mediator.Publish(new GuildMemberUpdatedNotification(oldMember, newMember), _cancellationToken); } - private async Task OnJoinedGuildAsync(SocketGuild guild) - { - await Mediator.Publish(new JoinedGuildNotification(guild), _cancellationToken); - } + private async Task OnJoinedGuildAsync(SocketGuild guild) { await Mediator.Publish(new JoinedGuildNotification(guild), _cancellationToken); } - private async Task OnMessageDeletedAsync(Cacheable message, ISocketMessageChannel channel) - { - await Mediator.Publish(new MessageDeletedNotification(message, channel), _cancellationToken); - } + private async Task OnMessageDeletedAsync(Cacheable message, ISocketMessageChannel channel) { await Mediator.Publish(new MessageDeletedNotification(message, channel), _cancellationToken); } - private async Task OnMessageReceivedAsync(SocketMessage message) - { - await Mediator.Publish(new MessageReceivedNotification(message), _cancellationToken); - } + private async Task OnMessageReceivedAsync(SocketMessage message) { await Mediator.Publish(new MessageReceivedNotification(message), _cancellationToken); } - private async Task OnMessagesBulkDeletedAsync(IReadOnlyCollection> messages, - ISocketMessageChannel channel) - { - await Mediator.Publish(new MessagesBulkDeletedNotification(messages, channel), _cancellationToken); - } + private async Task OnMessagesBulkDeletedAsync(IReadOnlyCollection> messages, + ISocketMessageChannel channel) + { + await Mediator.Publish(new MessagesBulkDeletedNotification(messages, channel), _cancellationToken); + } - private async Task OnMessageUpdatedAsync( - Cacheable oldMessage, SocketMessage newMessage, - ISocketMessageChannel channel) - { - var scope = ServiceScope.CreateScope(); - await scope.ServiceProvider.GetRequiredService().Publish( - new MessageUpdatedNotification(oldMessage, newMessage, channel), _cancellationToken); - } + private async Task OnMessageUpdatedAsync( + Cacheable oldMessage, SocketMessage newMessage, + ISocketMessageChannel channel) + { + var scope = ServiceScope.CreateScope(); + await scope.ServiceProvider.GetRequiredService().Publish( + new MessageUpdatedNotification(oldMessage, newMessage, channel), _cancellationToken); + } - private async Task OnReactionAddedAsync( - Cacheable message, ISocketMessageChannel channel, - SocketReaction reaction) - { - await Mediator.Publish(new ReactionAddedNotification(message, channel, reaction), _cancellationToken); - } + private async Task OnReactionAddedAsync( + Cacheable message, ISocketMessageChannel channel, + SocketReaction reaction) + { + await Mediator.Publish(new ReactionAddedNotification(message, channel, reaction), _cancellationToken); + } - private async Task OnReactionRemovedAsync( - Cacheable message, ISocketMessageChannel channel, - SocketReaction reaction) - { - await Mediator.Publish(new ReactionRemovedNotification(message, channel, reaction), _cancellationToken); - } + private async Task OnReactionRemovedAsync( + Cacheable message, ISocketMessageChannel channel, + SocketReaction reaction) + { + await Mediator.Publish(new ReactionRemovedNotification(message, channel, reaction), _cancellationToken); + } - private async Task OnReadyAsync() { await Mediator.Publish(ReadyNotification.Default, _cancellationToken); } + private async Task OnReadyAsync() { await Mediator.Publish(ReadyNotification.Default, _cancellationToken); } - private async Task OnRoleCreatedAsync(SocketRole role) - { - await Mediator.Publish(new RoleCreatedNotification(role), _cancellationToken); - } + private async Task OnRoleCreatedAsync(SocketRole role) { await Mediator.Publish(new RoleCreatedNotification(role), _cancellationToken); } - private async Task OnRoleUpdatedAsync(SocketRole oldRole, SocketRole newRole) - { - await Mediator.Publish(new RoleUpdatedNotification(oldRole, newRole), _cancellationToken); - } + private async Task OnRoleUpdatedAsync(SocketRole oldRole, SocketRole newRole) { await Mediator.Publish(new RoleUpdatedNotification(oldRole, newRole), _cancellationToken); } - private async Task OnUserBannedAsync(SocketUser user, SocketGuild guild) - { - await Mediator.Publish(new UserBannedNotification(user, guild), _cancellationToken); - } + private async Task OnUserBannedAsync(SocketUser user, SocketGuild guild) { await Mediator.Publish(new UserBannedNotification(user, guild), _cancellationToken); } - private async Task OnUserJoinedAsync(SocketGuildUser guildUser) - { - await Mediator.Publish(new UserJoinedNotification(guildUser), _cancellationToken); - } + private async Task OnUserJoinedAsync(SocketGuildUser guildUser) { await Mediator.Publish(new UserJoinedNotification(guildUser), _cancellationToken); } - private async Task OnUserLeftAsync(SocketGuildUser guildUser) - { - await Mediator.Publish(new UserLeftNotification(guildUser), _cancellationToken); - } + private async Task OnUserLeftAsync(SocketGuildUser guildUser) { await Mediator.Publish(new UserLeftNotification(guildUser), _cancellationToken); } - private async Task OnUserVoiceStateUpdatedAsync(SocketUser user, SocketVoiceState old, SocketVoiceState @new) - { - await Mediator.Publish(new UserVoiceStateNotification(user, old, @new), _cancellationToken); - } - } + private async Task OnUserVoiceStateUpdatedAsync(SocketUser user, SocketVoiceState old, SocketVoiceState @new) { await Mediator.Publish(new UserVoiceStateNotification(user, old, @new), _cancellationToken); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Listeners/ZhongliMediator.cs b/Zhongli.Services/Core/Listeners/ZhongliMediator.cs index 9658dab..17b9431 100644 --- a/Zhongli.Services/Core/Listeners/ZhongliMediator.cs +++ b/Zhongli.Services/Core/Listeners/ZhongliMediator.cs @@ -5,42 +5,41 @@ using MediatR; using Serilog; -namespace Zhongli.Services.Core.Listeners +namespace Zhongli.Services.Core.Listeners; + +public class ZhongliMediator : Mediator { - public class ZhongliMediator : Mediator - { - public ZhongliMediator(ServiceFactory serviceFactory) : base(serviceFactory) { } + public ZhongliMediator(ServiceFactory serviceFactory) : base(serviceFactory) { } - protected override Task PublishCore( - IEnumerable> handlers, - INotification notification, CancellationToken cancellationToken) + protected override Task PublishCore( + IEnumerable> handlers, + INotification notification, CancellationToken cancellationToken) + { + try { - try + _ = Task.Run(async () => { - _ = Task.Run(async () => + foreach (var handler in handlers) { - foreach (var handler in handlers) + try { - try - { - await handler(notification, cancellationToken); - } - catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException)) - { - Log.Error(ex, - "An unexpected error occurred within a handler for a dispatched message: {Notification}", - notification); - } + await handler(notification, cancellationToken); } - }, cancellationToken); - } - catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException)) - { - Log.Error(ex, "An unexpected error occurred while dispatching a notification: {Notification}", - notification); - } - - return Task.CompletedTask; + catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException)) + { + Log.Error(ex, + "An unexpected error occurred within a handler for a dispatched message: {Notification}", + notification); + } + } + }, cancellationToken); } + catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException)) + { + Log.Error(ex, "An unexpected error occurred while dispatching a notification: {Notification}", + notification); + } + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ChannelCreatedNotification.cs b/Zhongli.Services/Core/Messages/ChannelCreatedNotification.cs index cb259e7..c4df187 100644 --- a/Zhongli.Services/Core/Messages/ChannelCreatedNotification.cs +++ b/Zhongli.Services/Core/Messages/ChannelCreatedNotification.cs @@ -2,27 +2,23 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class ChannelCreatedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class ChannelCreatedNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public ChannelCreatedNotification(SocketChannel channel) - { - Channel = channel ?? throw new ArgumentNullException(nameof(channel)); - } + /// The value to use for . + /// Throws for . + public ChannelCreatedNotification(SocketChannel channel) { Channel = channel ?? throw new ArgumentNullException(nameof(channel)); } - /// - /// The channel that was created. - /// - public SocketChannel Channel { get; } - } + /// + /// The channel that was created. + /// + public SocketChannel Channel { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ChannelUpdatedNotification.cs b/Zhongli.Services/Core/Messages/ChannelUpdatedNotification.cs index 8a9aed7..5460f93 100644 --- a/Zhongli.Services/Core/Messages/ChannelUpdatedNotification.cs +++ b/Zhongli.Services/Core/Messages/ChannelUpdatedNotification.cs @@ -2,34 +2,33 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class ChannelUpdatedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class ChannelUpdatedNotification : INotification + /// The value to use for . + /// The value to use for . + /// Throws for and . + public ChannelUpdatedNotification(SocketChannel oldChannel, SocketChannel newChannel) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - /// Throws for and . - public ChannelUpdatedNotification(SocketChannel oldChannel, SocketChannel newChannel) - { - OldChannel = oldChannel ?? throw new ArgumentNullException(nameof(oldChannel)); - NewChannel = newChannel ?? throw new ArgumentNullException(nameof(newChannel)); - } + OldChannel = oldChannel ?? throw new ArgumentNullException(nameof(oldChannel)); + NewChannel = newChannel ?? throw new ArgumentNullException(nameof(newChannel)); + } - /// - /// The state of the channel that was updated, after the update. - /// - public SocketChannel NewChannel { get; } + /// + /// The state of the channel that was updated, after the update. + /// + public SocketChannel NewChannel { get; } - /// - /// The state of the channel that was updated, prior to the update. - /// - public SocketChannel OldChannel { get; } - } + /// + /// The state of the channel that was updated, prior to the update. + /// + public SocketChannel OldChannel { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ConnectedNotification.cs b/Zhongli.Services/Core/Messages/ConnectedNotification.cs index 6790d54..8e746a2 100644 --- a/Zhongli.Services/Core/Messages/ConnectedNotification.cs +++ b/Zhongli.Services/Core/Messages/ConnectedNotification.cs @@ -1,20 +1,19 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class ConnectedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// A default, reusable instance of the class. /// - public class ConnectedNotification : INotification - { - /// - /// A default, reusable instance of the class. - /// - public static readonly ConnectedNotification Default - = new(); + public static readonly ConnectedNotification Default + = new(); - private ConnectedNotification() { } - } + private ConnectedNotification() { } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/DisconnectedNotification.cs b/Zhongli.Services/Core/Messages/DisconnectedNotification.cs index 99dcbc6..96b63ac 100644 --- a/Zhongli.Services/Core/Messages/DisconnectedNotification.cs +++ b/Zhongli.Services/Core/Messages/DisconnectedNotification.cs @@ -2,16 +2,15 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class DisconnectedNotification : INotification { - /// - /// Describes an application-wide notification that occurs when is - /// raised. - /// - public class DisconnectedNotification : INotification - { - public DisconnectedNotification(Exception exception) { Exception = exception; } + public DisconnectedNotification(Exception exception) { Exception = exception; } - public Exception Exception { get; } - } + public Exception Exception { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/GuildAvailableNotification.cs b/Zhongli.Services/Core/Messages/GuildAvailableNotification.cs index ec45b74..dd9110a 100644 --- a/Zhongli.Services/Core/Messages/GuildAvailableNotification.cs +++ b/Zhongli.Services/Core/Messages/GuildAvailableNotification.cs @@ -2,27 +2,23 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class GuildAvailableNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class GuildAvailableNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public GuildAvailableNotification(SocketGuild guild) - { - Guild = guild ?? throw new ArgumentNullException(nameof(guild)); - } + /// The value to use for . + /// Throws for . + public GuildAvailableNotification(SocketGuild guild) { Guild = guild ?? throw new ArgumentNullException(nameof(guild)); } - /// - /// The guild whose data is now available. - /// - public SocketGuild Guild { get; } - } + /// + /// The guild whose data is now available. + /// + public SocketGuild Guild { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/GuildMemberUpdatedNotification.cs b/Zhongli.Services/Core/Messages/GuildMemberUpdatedNotification.cs index 5003530..8b93c28 100644 --- a/Zhongli.Services/Core/Messages/GuildMemberUpdatedNotification.cs +++ b/Zhongli.Services/Core/Messages/GuildMemberUpdatedNotification.cs @@ -1,35 +1,34 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class GuildMemberUpdatedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class GuildMemberUpdatedNotification : INotification + /// The value to use for . + /// The value to use for . + public GuildMemberUpdatedNotification( + SocketGuildUser oldMember, + SocketGuildUser newMember) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - public GuildMemberUpdatedNotification( - SocketGuildUser oldMember, - SocketGuildUser newMember) - { - OldMember = oldMember; - NewMember = newMember; - } + OldMember = oldMember; + NewMember = newMember; + } - /// - /// A model of the Guild Member that was updated, from after the update. - /// - public SocketGuildUser NewMember { get; } + /// + /// A model of the Guild Member that was updated, from after the update. + /// + public SocketGuildUser NewMember { get; } - /// - /// A model of the Guild Member that was updated, from before the update. - /// - public SocketGuildUser OldMember { get; } - } + /// + /// A model of the Guild Member that was updated, from before the update. + /// + public SocketGuildUser OldMember { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/JoinedGuildNotification.cs b/Zhongli.Services/Core/Messages/JoinedGuildNotification.cs index 0fa2371..a46748a 100644 --- a/Zhongli.Services/Core/Messages/JoinedGuildNotification.cs +++ b/Zhongli.Services/Core/Messages/JoinedGuildNotification.cs @@ -2,26 +2,22 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class JoinedGuildNotification : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class JoinedGuildNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public JoinedGuildNotification(SocketGuild guild) - { - Guild = guild ?? throw new ArgumentNullException(nameof(guild)); - } + /// The value to use for . + /// Throws for . + public JoinedGuildNotification(SocketGuild guild) { Guild = guild ?? throw new ArgumentNullException(nameof(guild)); } - /// - /// The guild that the bot has joined. - /// - public SocketGuild Guild { get; } - } + /// + /// The guild that the bot has joined. + /// + public SocketGuild Guild { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/MessageDeletedNotification.cs b/Zhongli.Services/Core/Messages/MessageDeletedNotification.cs index 6c919f2..c287b02 100644 --- a/Zhongli.Services/Core/Messages/MessageDeletedNotification.cs +++ b/Zhongli.Services/Core/Messages/MessageDeletedNotification.cs @@ -3,34 +3,33 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class MessageDeletedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class MessageDeletedNotification : INotification + /// The value to use for . + /// The value to use for . + /// Throws for and . + public MessageDeletedNotification(Cacheable? message, ISocketMessageChannel channel) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - /// Throws for and . - public MessageDeletedNotification(Cacheable? message, ISocketMessageChannel channel) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - Channel = channel ?? throw new ArgumentNullException(nameof(channel)); - } + Message = message ?? throw new ArgumentNullException(nameof(message)); + Channel = channel ?? throw new ArgumentNullException(nameof(channel)); + } - /// - /// A cache entry for the message that was deleted. - /// - public Cacheable Message { get; } + /// + /// A cache entry for the message that was deleted. + /// + public Cacheable Message { get; } - /// - /// The channel from which the message was deleted. - /// - public ISocketMessageChannel Channel { get; } - } + /// + /// The channel from which the message was deleted. + /// + public ISocketMessageChannel Channel { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/MessageReceivedNotification.cs b/Zhongli.Services/Core/Messages/MessageReceivedNotification.cs index 13f970b..80c5005 100644 --- a/Zhongli.Services/Core/Messages/MessageReceivedNotification.cs +++ b/Zhongli.Services/Core/Messages/MessageReceivedNotification.cs @@ -2,27 +2,23 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class MessageReceivedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class MessageReceivedNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public MessageReceivedNotification(SocketMessage message) - { - Message = message ?? throw new ArgumentNullException(nameof(message)); - } + /// The value to use for . + /// Throws for . + public MessageReceivedNotification(SocketMessage message) { Message = message ?? throw new ArgumentNullException(nameof(message)); } - /// - /// The message that was received. - /// - public SocketMessage Message { get; } - } + /// + /// The message that was received. + /// + public SocketMessage Message { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/MessageUpdatedNotification.cs b/Zhongli.Services/Core/Messages/MessageUpdatedNotification.cs index 196e963..9073e82 100644 --- a/Zhongli.Services/Core/Messages/MessageUpdatedNotification.cs +++ b/Zhongli.Services/Core/Messages/MessageUpdatedNotification.cs @@ -3,46 +3,45 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class MessageUpdatedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class MessageUpdatedNotification : INotification + /// The value to use for . + /// The value to use for . + /// The value to use for . + /// + /// Throws for , , and + /// . + /// + public MessageUpdatedNotification( + Cacheable? oldMessage, SocketMessage newMessage, + ISocketMessageChannel channel) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - /// The value to use for . - /// - /// Throws for , , and - /// . - /// - public MessageUpdatedNotification( - Cacheable? oldMessage, SocketMessage newMessage, - ISocketMessageChannel channel) - { - OldMessage = oldMessage ?? throw new ArgumentNullException(nameof(oldMessage)); - NewMessage = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); - Channel = channel ?? throw new ArgumentNullException(nameof(channel)); - } + OldMessage = oldMessage ?? throw new ArgumentNullException(nameof(oldMessage)); + NewMessage = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); + Channel = channel ?? throw new ArgumentNullException(nameof(channel)); + } - /// - /// The old version of the updated message, if it happens to have been cached. - /// - public Cacheable OldMessage { get; set; } + /// + /// The old version of the updated message, if it happens to have been cached. + /// + public Cacheable OldMessage { get; set; } - /// - /// The channel in which the message was posted. - /// - public ISocketMessageChannel Channel { get; set; } + /// + /// The channel in which the message was posted. + /// + public ISocketMessageChannel Channel { get; set; } - /// - /// The new version of the updated message. - /// - public SocketMessage NewMessage { get; set; } - } + /// + /// The new version of the updated message. + /// + public SocketMessage NewMessage { get; set; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/MessagesBulkDeletedNotification.cs b/Zhongli.Services/Core/Messages/MessagesBulkDeletedNotification.cs index fbb5656..890c6d6 100644 --- a/Zhongli.Services/Core/Messages/MessagesBulkDeletedNotification.cs +++ b/Zhongli.Services/Core/Messages/MessagesBulkDeletedNotification.cs @@ -4,35 +4,34 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class MessagesBulkDeletedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new from the given values. /// - public class MessagesBulkDeletedNotification : INotification + /// The value to use for . + /// The value to use for . + /// Throws for and . + public MessagesBulkDeletedNotification(IReadOnlyCollection> messages, + ISocketMessageChannel channel) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - /// Throws for and . - public MessagesBulkDeletedNotification(IReadOnlyCollection> messages, - ISocketMessageChannel channel) - { - Messages = messages; - Channel = channel; - } + Messages = messages; + Channel = channel; + } - /// - /// A cache entry for the messages that were deleted. - /// - public IReadOnlyCollection> Messages { get; } + /// + /// A cache entry for the messages that were deleted. + /// + public IReadOnlyCollection> Messages { get; } - /// - /// The channel from which the message was deleted. - /// - public ISocketMessageChannel Channel { get; } - } + /// + /// The channel from which the message was deleted. + /// + public ISocketMessageChannel Channel { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ReactionAddedNotification.cs b/Zhongli.Services/Core/Messages/ReactionAddedNotification.cs index 21cd582..ae12f39 100644 --- a/Zhongli.Services/Core/Messages/ReactionAddedNotification.cs +++ b/Zhongli.Services/Core/Messages/ReactionAddedNotification.cs @@ -3,43 +3,42 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class ReactionAddedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new object from the given data values. /// - public class ReactionAddedNotification : INotification + /// The value to use for . + /// The value to use for . + /// The value to use for . + /// Throws for and . + public ReactionAddedNotification( + Cacheable message, ISocketMessageChannel channel, + SocketReaction reaction) { - /// - /// 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, ISocketMessageChannel channel, - SocketReaction reaction) - { - Message = message; - Channel = channel ?? throw new ArgumentNullException(nameof(channel)); - Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); - } + Message = message; + Channel = channel ?? throw new ArgumentNullException(nameof(channel)); + Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); + } - /// - /// The message (if cached) to which a reaction was added. - /// - public Cacheable Message { get; } + /// + /// 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 ISocketMessageChannel Channel { get; } + /// + /// The channel in which a reaction was added to a message. + /// + public ISocketMessageChannel Channel { get; } - /// - /// The reaction that was added to a message. - /// - public SocketReaction Reaction { get; } - } + /// + /// The reaction that was added to a message. + /// + public SocketReaction Reaction { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ReactionRemovedNotification.cs b/Zhongli.Services/Core/Messages/ReactionRemovedNotification.cs index fa9b117..e4fa9f0 100644 --- a/Zhongli.Services/Core/Messages/ReactionRemovedNotification.cs +++ b/Zhongli.Services/Core/Messages/ReactionRemovedNotification.cs @@ -3,43 +3,42 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is +/// raised. +/// +public class ReactionRemovedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is - /// raised. + /// Constructs a new object from the given data values. /// - public class ReactionRemovedNotification : INotification + /// The value to use for . + /// The value to use for . + /// The value to use for . + /// Throws for and . + public ReactionRemovedNotification( + Cacheable message, ISocketMessageChannel channel, + SocketReaction reaction) { - /// - /// 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, ISocketMessageChannel channel, - SocketReaction reaction) - { - Message = message; - Channel = channel ?? throw new ArgumentNullException(nameof(channel)); - Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); - } + Message = message; + Channel = channel ?? throw new ArgumentNullException(nameof(channel)); + Reaction = reaction ?? throw new ArgumentNullException(nameof(reaction)); + } - /// - /// The message (if cached) to which a reaction was added. - /// - public Cacheable Message { get; } + /// + /// 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 ISocketMessageChannel Channel { get; } + /// + /// The channel in which a reaction was added to a message. + /// + public ISocketMessageChannel Channel { get; } - /// - /// The reaction that was added to a message. - /// - public SocketReaction Reaction { get; } - } + /// + /// The reaction that was added to a message. + /// + public SocketReaction Reaction { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/ReadyNotification.cs b/Zhongli.Services/Core/Messages/ReadyNotification.cs index c40356a..23c1ec4 100644 --- a/Zhongli.Services/Core/Messages/ReadyNotification.cs +++ b/Zhongli.Services/Core/Messages/ReadyNotification.cs @@ -1,19 +1,18 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class ReadyNotification : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// A default, reusable instance of the class. /// - public class ReadyNotification : INotification - { - /// - /// A default, reusable instance of the class. - /// - public static readonly ReadyNotification Default - = new(); + public static readonly ReadyNotification Default + = new(); - private ReadyNotification() { } - } + private ReadyNotification() { } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/RoleCreatedNotification.cs b/Zhongli.Services/Core/Messages/RoleCreatedNotification.cs index 585aafe..75d6f3e 100644 --- a/Zhongli.Services/Core/Messages/RoleCreatedNotification.cs +++ b/Zhongli.Services/Core/Messages/RoleCreatedNotification.cs @@ -2,27 +2,26 @@ using MediatR; #nullable enable -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class RoleCreatedNotification + : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class RoleCreatedNotification - : INotification + /// The value to use for . + public RoleCreatedNotification( + SocketRole role) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - public RoleCreatedNotification( - SocketRole role) - { - Role = role; - } - - /// - /// The role that was created. - /// - public SocketRole Role { get; } + Role = role; } + + /// + /// The role that was created. + /// + public SocketRole Role { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/RoleUpdatedNotification.cs b/Zhongli.Services/Core/Messages/RoleUpdatedNotification.cs index 05952f6..9fdc094 100644 --- a/Zhongli.Services/Core/Messages/RoleUpdatedNotification.cs +++ b/Zhongli.Services/Core/Messages/RoleUpdatedNotification.cs @@ -2,35 +2,34 @@ using MediatR; #nullable enable -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class RoleUpdatedNotification + : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class RoleUpdatedNotification - : INotification + /// The value to use for . + /// The value to use for . + public RoleUpdatedNotification( + SocketRole oldRole, + SocketRole newRole) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - public RoleUpdatedNotification( - SocketRole oldRole, - SocketRole newRole) - { - OldRole = oldRole; - NewRole = newRole; - } + OldRole = oldRole; + NewRole = newRole; + } - /// - /// The state of the role that was updated, after the update. - /// - public SocketRole NewRole { get; } + /// + /// The state of the role that was updated, after the update. + /// + public SocketRole NewRole { get; } - /// - /// The state of the role that was updated, prior to the update. - /// - public SocketRole OldRole { get; } - } + /// + /// The state of the role that was updated, prior to the update. + /// + public SocketRole OldRole { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/UserBannedNotification.cs b/Zhongli.Services/Core/Messages/UserBannedNotification.cs index f42a460..a193380 100644 --- a/Zhongli.Services/Core/Messages/UserBannedNotification.cs +++ b/Zhongli.Services/Core/Messages/UserBannedNotification.cs @@ -2,33 +2,32 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class UserBannedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class UserBannedNotification : INotification + /// The value to use for . + /// The value to use for . + /// Throws for and . + public UserBannedNotification(SocketUser user, SocketGuild guild) { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// The value to use for . - /// Throws for and . - public UserBannedNotification(SocketUser user, SocketGuild guild) - { - User = user ?? throw new ArgumentNullException(nameof(user)); - Guild = guild ?? throw new ArgumentNullException(nameof(guild)); - } + User = user ?? throw new ArgumentNullException(nameof(user)); + Guild = guild ?? throw new ArgumentNullException(nameof(guild)); + } - /// - /// The guild from which the user was banned. - /// - public SocketGuild Guild { get; } + /// + /// The guild from which the user was banned. + /// + public SocketGuild Guild { get; } - /// - /// The user that was banned. - /// - public SocketUser User { get; } - } + /// + /// The user that was banned. + /// + public SocketUser User { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/UserJoinedNotification.cs b/Zhongli.Services/Core/Messages/UserJoinedNotification.cs index 7ce4dbb..8097b05 100644 --- a/Zhongli.Services/Core/Messages/UserJoinedNotification.cs +++ b/Zhongli.Services/Core/Messages/UserJoinedNotification.cs @@ -2,26 +2,22 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class UserJoinedNotification : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class UserJoinedNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public UserJoinedNotification(SocketGuildUser guildUser) - { - GuildUser = guildUser ?? throw new ArgumentNullException(nameof(guildUser)); - } + /// The value to use for . + /// Throws for . + public UserJoinedNotification(SocketGuildUser guildUser) { GuildUser = guildUser ?? throw new ArgumentNullException(nameof(guildUser)); } - /// - /// The user that joined, and the guild that was joined. - /// - public SocketGuildUser GuildUser { get; } - } + /// + /// The user that joined, and the guild that was joined. + /// + public SocketGuildUser GuildUser { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/UserLeftNotification.cs b/Zhongli.Services/Core/Messages/UserLeftNotification.cs index a1b4f30..7a9a0bd 100644 --- a/Zhongli.Services/Core/Messages/UserLeftNotification.cs +++ b/Zhongli.Services/Core/Messages/UserLeftNotification.cs @@ -2,26 +2,22 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +/// +/// Describes an application-wide notification that occurs when is raised. +/// +public class UserLeftNotification : INotification { /// - /// Describes an application-wide notification that occurs when is raised. + /// Constructs a new from the given values. /// - public class UserLeftNotification : INotification - { - /// - /// Constructs a new from the given values. - /// - /// The value to use for . - /// Throws for . - public UserLeftNotification(SocketGuildUser guildUser) - { - GuildUser = guildUser ?? throw new ArgumentNullException(nameof(guildUser)); - } + /// The value to use for . + /// Throws for . + public UserLeftNotification(SocketGuildUser guildUser) { GuildUser = guildUser ?? throw new ArgumentNullException(nameof(guildUser)); } - /// - /// The user that left, and the guild that was left. - /// - public SocketGuildUser GuildUser { get; } - } + /// + /// The user that left, and the guild that was left. + /// + public SocketGuildUser GuildUser { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Messages/UserVoiceStateNotification.cs b/Zhongli.Services/Core/Messages/UserVoiceStateNotification.cs index d8f8523..e7e0634 100644 --- a/Zhongli.Services/Core/Messages/UserVoiceStateNotification.cs +++ b/Zhongli.Services/Core/Messages/UserVoiceStateNotification.cs @@ -1,21 +1,20 @@ using Discord.WebSocket; using MediatR; -namespace Zhongli.Services.Core.Messages +namespace Zhongli.Services.Core.Messages; + +public class UserVoiceStateNotification : INotification { - public class UserVoiceStateNotification : INotification + public UserVoiceStateNotification(SocketUser user, SocketVoiceState old, SocketVoiceState @new) { - public UserVoiceStateNotification(SocketUser user, SocketVoiceState old, SocketVoiceState @new) - { - User = user; - Old = old; - New = @new; - } + User = user; + Old = old; + New = @new; + } - public SocketUser User { get; } + public SocketUser User { get; } - public SocketVoiceState New { get; } + public SocketVoiceState New { get; } - public SocketVoiceState Old { get; } - } + public SocketVoiceState Old { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/Preconditions/RequireAuthorizationAttribute.cs b/Zhongli.Services/Core/Preconditions/RequireAuthorizationAttribute.cs index e2ab405..fafaaec 100644 --- a/Zhongli.Services/Core/Preconditions/RequireAuthorizationAttribute.cs +++ b/Zhongli.Services/Core/Preconditions/RequireAuthorizationAttribute.cs @@ -5,26 +5,25 @@ using Microsoft.Extensions.DependencyInjection; using Zhongli.Data.Models.Authorization; -namespace Zhongli.Services.Core.Preconditions +namespace Zhongli.Services.Core.Preconditions; + +public class RequireAuthorizationAttribute : PreconditionAttribute { - public class RequireAuthorizationAttribute : PreconditionAttribute - { - private readonly AuthorizationScope _scopes = AuthorizationScope.All; + private readonly AuthorizationScope _scopes = AuthorizationScope.All; - public RequireAuthorizationAttribute(AuthorizationScope scopes) { _scopes |= scopes; } + public RequireAuthorizationAttribute(AuthorizationScope scopes) { _scopes |= scopes; } - public override async Task CheckPermissionsAsync(ICommandContext context, - CommandInfo command, IServiceProvider services) - { - if (context.User is not IGuildUser user) - return PreconditionResult.FromError("User could not be cast as SocketGuildUser"); + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) + { + if (context.User is not IGuildUser user) + return PreconditionResult.FromError("User could not be cast as SocketGuildUser"); - var auth = services.GetRequiredService(); - var isAuthorized = await auth.IsAuthorizedAsync(context, _scopes); + var auth = services.GetRequiredService(); + var isAuthorized = await auth.IsAuthorizedAsync(context, _scopes); - return isAuthorized - ? PreconditionResult.FromSuccess() - : PreconditionResult.FromError("You do not have permission to use this command."); - } + return isAuthorized + ? PreconditionResult.FromSuccess() + : PreconditionResult.FromError("You do not have permission to use this command."); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/EnumFlagsTypeReader.cs b/Zhongli.Services/Core/TypeReaders/EnumFlagsTypeReader.cs index 6ca626b..b949201 100644 --- a/Zhongli.Services/Core/TypeReaders/EnumFlagsTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/EnumFlagsTypeReader.cs @@ -4,35 +4,34 @@ using Discord.Commands; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public class EnumFlagsTypeReader : TypeReader where T : struct, Enum { - public class EnumFlagsTypeReader : TypeReader where T : struct, Enum - { - private readonly bool _ignoreCase; - private readonly string _separator; - private readonly StringSplitOptions _splitOptions; + private readonly bool _ignoreCase; + private readonly string _separator; + private readonly StringSplitOptions _splitOptions; - public EnumFlagsTypeReader(bool ignoreCase = true, string separator = ",", - StringSplitOptions splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) - { - _splitOptions = splitOptions; - _ignoreCase = ignoreCase; - _separator = separator; - } + public EnumFlagsTypeReader(bool ignoreCase = true, string separator = ",", + StringSplitOptions splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) + { + _splitOptions = splitOptions; + _ignoreCase = ignoreCase; + _separator = separator; + } - public override Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) - { - var enums = input.Split(_separator, _splitOptions) - .Select(content => (success: Enum.TryParse(content, _ignoreCase, out var result), result)) - .Where(e => e.success) - .ToList(); + public override Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) + { + var enums = input.Split(_separator, _splitOptions) + .Select(content => (success: Enum.TryParse(content, _ignoreCase, out var result), result)) + .Where(e => e.success) + .ToList(); - var generic = new GenericBitwise(); + var generic = new GenericBitwise(); - return enums.Any() - ? Task.FromResult(TypeReaderResult.FromSuccess(generic.Or(enums.Select(e => e.result)))) - : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse input.")); - } + return enums.Any() + ? Task.FromResult(TypeReaderResult.FromSuccess(generic.Or(enums.Select(e => e.result)))) + : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse input.")); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/EnumerableTypeReader.cs b/Zhongli.Services/Core/TypeReaders/EnumerableTypeReader.cs index aec5395..1295a6b 100644 --- a/Zhongli.Services/Core/TypeReaders/EnumerableTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/EnumerableTypeReader.cs @@ -3,33 +3,32 @@ using System.Threading.Tasks; using Discord.Commands; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public class EnumerableTypeReader : TypeReader { - public class EnumerableTypeReader : TypeReader - { - private readonly string _separator; - private readonly StringSplitOptions _splitOptions; - private readonly TypeReader _typeReader; + private readonly string _separator; + private readonly StringSplitOptions _splitOptions; + private readonly TypeReader _typeReader; - public EnumerableTypeReader(TypeReader typeReader, string separator = ",", - StringSplitOptions splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) - { - _typeReader = typeReader; - _separator = separator; - _splitOptions = splitOptions; - } + public EnumerableTypeReader(TypeReader typeReader, string separator = ",", + StringSplitOptions splitOptions = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) + { + _typeReader = typeReader; + _separator = separator; + _splitOptions = splitOptions; + } - public override async Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) - { - var results = await input - .Split(_separator, _splitOptions).ToAsyncEnumerable() - .SelectAwait(async i => await _typeReader.ReadAsync(context, i, services)) - .SelectMany(r => r.Values.ToAsyncEnumerable()) - .Select(v => v.Value).OfType() - .ToListAsync(); + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) + { + var results = await input + .Split(_separator, _splitOptions).ToAsyncEnumerable() + .SelectAwait(async i => await _typeReader.ReadAsync(context, i, services)) + .SelectMany(r => r.Values.ToAsyncEnumerable()) + .Select(v => v.Value).OfType() + .ToListAsync(); - return TypeReaderResult.FromSuccess(results); - } + return TypeReaderResult.FromSuccess(results); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/HexColorTypeReader.cs b/Zhongli.Services/Core/TypeReaders/HexColorTypeReader.cs index 3dc6722..c8586d9 100644 --- a/Zhongli.Services/Core/TypeReaders/HexColorTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/HexColorTypeReader.cs @@ -4,22 +4,21 @@ using Discord.Commands; using Color = Discord.Color; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public class HexColorTypeReader : TypeReader { - public class HexColorTypeReader : TypeReader + public override Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - public override Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) + try + { + var color = ColorTranslator.FromHtml($"#{input.Replace("#", string.Empty)}"); + return Task.FromResult(TypeReaderResult.FromSuccess((Color) color)); + } + catch (Exception e) { - try - { - var color = ColorTranslator.FromHtml($"#{input.Replace("#", string.Empty)}"); - return Task.FromResult(TypeReaderResult.FromSuccess((Color) color)); - } - catch (Exception e) - { - return Task.FromResult(TypeReaderResult.FromError(e)); - } + return Task.FromResult(TypeReaderResult.FromError(e)); } } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/JumpUrlTypeReader.cs b/Zhongli.Services/Core/TypeReaders/JumpUrlTypeReader.cs index 2e0ea05..cbe103a 100644 --- a/Zhongli.Services/Core/TypeReaders/JumpUrlTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/JumpUrlTypeReader.cs @@ -3,18 +3,17 @@ using Discord.Commands; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public class JumpUrlTypeReader : TypeReader { - public class JumpUrlTypeReader : TypeReader + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - public override async Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) - { - var message = await context.GetMessageFromUrlAsync(input); + var message = await context.GetMessageFromUrlAsync(input); - return message is null - ? TypeReaderResult.FromError(CommandError.ParseFailed, "Could not find message.") - : TypeReaderResult.FromSuccess(message); - } + return message is null + ? TypeReaderResult.FromError(CommandError.ParseFailed, "Could not find message.") + : TypeReaderResult.FromSuccess(message); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/MessageTypeReader.cs b/Zhongli.Services/Core/TypeReaders/MessageTypeReader.cs index 94fdd6e..c5f792f 100644 --- a/Zhongli.Services/Core/TypeReaders/MessageTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/MessageTypeReader.cs @@ -4,25 +4,24 @@ using Discord; using Discord.Commands; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +/// +/// A for parsing objects implementing . +/// +/// The type to be checked; must implement . +public class MessageTypeReader : TypeReader where T : class, IMessage { - /// - /// A for parsing objects implementing . - /// - /// The type to be checked; must implement . - public class MessageTypeReader : TypeReader where T : class, IMessage + /// + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - /// - public override async Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) - { - if (!ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out var id)) - return TypeReaderResult.FromError(CommandError.ParseFailed, "Could not parse Message ID."); + if (!ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out var id)) + return TypeReaderResult.FromError(CommandError.ParseFailed, "Could not parse Message ID."); - if (await context.Channel.GetMessageAsync(id).ConfigureAwait(false) is T msg) - return TypeReaderResult.FromSuccess(msg); + if (await context.Channel.GetMessageAsync(id).ConfigureAwait(false) is T msg) + return TypeReaderResult.FromSuccess(msg); - return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Could not find message."); - } + return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Could not find message."); } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/TryParseTypeReader.cs b/Zhongli.Services/Core/TypeReaders/TryParseTypeReader.cs index 1a2ceda..cf0ca46 100644 --- a/Zhongli.Services/Core/TypeReaders/TryParseTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/TryParseTypeReader.cs @@ -3,31 +3,30 @@ using Discord.Commands; using Zhongli.Services.Interactive.TryParse; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public class TryParseTypeReader : TypeReader { - public class TryParseTypeReader : TypeReader - { - private readonly TryParseDelegate _tryParse; + private readonly TryParseDelegate _tryParse; - public TryParseTypeReader(TryParseDelegate tryParse) { _tryParse = tryParse; } + public TryParseTypeReader(TryParseDelegate tryParse) { _tryParse = tryParse; } - public override Task ReadAsync( - ICommandContext context, string input, IServiceProvider services) => - _tryParse(input, out var result) - ? Task.FromResult(TypeReaderResult.FromSuccess(result)) - : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid input")); - } + public override Task ReadAsync( + ICommandContext context, string input, IServiceProvider services) => + _tryParse(input, out var result) + ? Task.FromResult(TypeReaderResult.FromSuccess(result)) + : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid input")); +} - public class EnumTryParseTypeReader : TypeReader where T : struct, Enum - { - private readonly bool _ignoreCase; +public class EnumTryParseTypeReader : TypeReader where T : struct, Enum +{ + private readonly bool _ignoreCase; - public EnumTryParseTypeReader(bool ignoreCase = true) { _ignoreCase = ignoreCase; } + public EnumTryParseTypeReader(bool ignoreCase = true) { _ignoreCase = ignoreCase; } - public override Task ReadAsync( - ICommandContext context, string input, IServiceProvider services) => - Enum.TryParse(input, _ignoreCase, out var result) && Enum.IsDefined(result) - ? Task.FromResult(TypeReaderResult.FromSuccess(result)) - : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {input}.")); - } + public override Task ReadAsync( + ICommandContext context, string input, IServiceProvider services) => + Enum.TryParse(input, _ignoreCase, out var result) && Enum.IsDefined(result) + ? Task.FromResult(TypeReaderResult.FromSuccess(result)) + : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {input}.")); } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/TypeReaderExtensions.cs b/Zhongli.Services/Core/TypeReaders/TypeReaderExtensions.cs index e7db40a..5162500 100644 --- a/Zhongli.Services/Core/TypeReaders/TypeReaderExtensions.cs +++ b/Zhongli.Services/Core/TypeReaders/TypeReaderExtensions.cs @@ -1,30 +1,29 @@ using System.Collections.Generic; using Discord.Commands; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +public static class TypeReaderExtensions { - public static class TypeReaderExtensions - { - public static CommandService AddEnumerableTypeReader( - this CommandService commands) where TReader : TypeReader, new() - => commands.AddEnumerableTypeReader(new TReader()); + public static CommandService AddEnumerableTypeReader( + this CommandService commands) where TReader : TypeReader, new() + => commands.AddEnumerableTypeReader(new TReader()); - public static CommandService AddEnumerableTypeReader( - this CommandService commands, TypeReader typeReader) - { - var reader = new EnumerableTypeReader(typeReader); + public static CommandService AddEnumerableTypeReader( + this CommandService commands, TypeReader typeReader) + { + var reader = new EnumerableTypeReader(typeReader); - commands.AddTypeReader>(reader); + commands.AddTypeReader>(reader); - commands.AddTypeReader>(reader); - commands.AddTypeReader>(reader); - commands.AddTypeReader>(reader); + commands.AddTypeReader>(reader); + commands.AddTypeReader>(reader); + commands.AddTypeReader>(reader); - commands.AddTypeReader(reader); - commands.AddTypeReader>(reader); - commands.AddTypeReader>(reader); + commands.AddTypeReader(reader); + commands.AddTypeReader>(reader); + commands.AddTypeReader>(reader); - return commands; - } + return commands; } } \ No newline at end of file diff --git a/Zhongli.Services/Core/TypeReaders/UserTypeReader.cs b/Zhongli.Services/Core/TypeReaders/UserTypeReader.cs index 18da50f..780906c 100644 --- a/Zhongli.Services/Core/TypeReaders/UserTypeReader.cs +++ b/Zhongli.Services/Core/TypeReaders/UserTypeReader.cs @@ -8,133 +8,127 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Core.TypeReaders +namespace Zhongli.Services.Core.TypeReaders; + +/// +/// A for parsing objects implementing . +/// +/// The type to be checked; must implement . +public class UserTypeReader : TypeReader + where T : class, IUser { - /// - /// A for parsing objects implementing . - /// - /// The type to be checked; must implement . - public class UserTypeReader : TypeReader - where T : class, IUser + private readonly bool _useRest; + private readonly CacheMode _cacheMode; + + public UserTypeReader(CacheMode cacheMode = CacheMode.CacheOnly, bool useRest = false) { - private readonly bool _useRest; - private readonly CacheMode _cacheMode; + _cacheMode = cacheMode; + _useRest = useRest; + } - public UserTypeReader(CacheMode cacheMode = CacheMode.CacheOnly, bool useRest = false) - { - _cacheMode = cacheMode; - _useRest = useRest; - } + /// + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) + { + var results = new Dictionary(); - /// - public override async Task ReadAsync(ICommandContext context, string input, - IServiceProvider services) + // By Mention (1.0) + if (MentionUtils.TryParseUser(input, out var id)) { - var results = new Dictionary(); - - // By Mention (1.0) - if (MentionUtils.TryParseUser(input, out var id)) + if (context.Guild is not null) { - if (context.Guild is not null) - { - var guildUser = await context.Guild.GetUserAsync(id, _cacheMode).ConfigureAwait(false); - var user = await GetUserAsync(context.Client, guildUser, id); + var guildUser = await context.Guild.GetUserAsync(id, _cacheMode).ConfigureAwait(false); + var user = await GetUserAsync(context.Client, guildUser, id); - AddResult(results, user, 1.00f); - } - else - { - var channelUser = await context.Channel.GetUserAsync(id, _cacheMode).ConfigureAwait(false); - var user = await GetUserAsync(context.Client, channelUser, id); + AddResult(results, user, 1.00f); + } + else + { + var channelUser = await context.Channel.GetUserAsync(id, _cacheMode).ConfigureAwait(false); + var user = await GetUserAsync(context.Client, channelUser, id); - AddResult(results, user, 1.00f); - } + AddResult(results, user, 1.00f); } + } - // By Id (0.9) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + // By Id (0.9) + if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + { + if (context.Guild is not null) { - if (context.Guild is not null) - { - var guildUser = await context.Guild.GetUserAsync(id, _cacheMode).ConfigureAwait(false); - var user = await GetUserAsync(context.Client, guildUser, id); + var guildUser = await context.Guild.GetUserAsync(id, _cacheMode).ConfigureAwait(false); + var user = await GetUserAsync(context.Client, guildUser, id); - AddResult(results, user, 0.90f); - } - else - { - var channelUser = await context.Channel.GetUserAsync(id, _cacheMode).ConfigureAwait(false); - var user = await GetUserAsync(context.Client, channelUser, id); + AddResult(results, user, 0.90f); + } + else + { + var channelUser = await context.Channel.GetUserAsync(id, _cacheMode).ConfigureAwait(false); + var user = await GetUserAsync(context.Client, channelUser, id); - AddResult(results, user, 1.00f); - } + AddResult(results, user, 1.00f); } + } - if (context.Guild is not null) + if (context.Guild is not null) + { + // By Username + Discriminator (0.7-0.85) + var index = input.LastIndexOf('#'); + if (index >= 0) { - // By Username + Discriminator (0.7-0.85) - var index = input.LastIndexOf('#'); - if (index >= 0) + var username = input[..index]; + if (ushort.TryParse(input[(index + 1)..], out var discriminator)) { - var username = input[..index]; - if (ushort.TryParse(input[(index + 1)..], out var discriminator)) - { - var users = await context.Guild - .SearchUsersAsync($"{username}#{discriminator}", mode: _cacheMode) - .ConfigureAwait(false); - - foreach (var user in users) - { - AddResult(results, user as T, user.Username == username ? 0.85f : 0.75f); + var users = await context.Guild + .SearchUsersAsync($"{username}#{discriminator}", mode: _cacheMode) + .ConfigureAwait(false); - if (user.DiscriminatorValue == discriminator && string.Equals(username, user.Username, StringComparison.OrdinalIgnoreCase)) - { - AddResult(results, user as T, user.Username == username ? 0.80f : 0.70f); - } + foreach (var user in users) + { + AddResult(results, user as T, user.Username == username ? 0.85f : 0.75f); - } + if (user.DiscriminatorValue == discriminator && string.Equals(username, user.Username, StringComparison.OrdinalIgnoreCase)) AddResult(results, user as T, user.Username == username ? 0.80f : 0.70f); } } + } - var search = await context.Guild - .SearchUsersAsync(input, mode: _cacheMode) - .ConfigureAwait(false); - - // By Username (0.5-0.6) - var usernames = search.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)); - foreach (var user in usernames) - { - AddResult(results, user as T, user.Username == input ? 0.65f : 0.55f); - } - + var search = await context.Guild + .SearchUsersAsync(input, mode: _cacheMode) + .ConfigureAwait(false); - // By Nickname (0.5-0.6) - var nicknames = search.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase)); - foreach (var user in nicknames) - { - AddResult(results, user as T, user.Username == input ? 0.65f : 0.55f); - } + // By Username (0.5-0.6) + var usernames = search.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)); + foreach (var user in usernames) + { + AddResult(results, user as T, user.Username == input ? 0.65f : 0.55f); } - return results.Count > 0 - ? TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()) - : TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); + // By Nickname (0.5-0.6) + var nicknames = search.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase)); + foreach (var user in nicknames) + { + AddResult(results, user as T, user.Username == input ? 0.65f : 0.55f); + } } - private async Task GetUserAsync(IDiscordClient client, IUser? user, ulong id) - { - var result = user as T; - if (user is not null || !_useRest) - return result; + return results.Count > 0 + ? TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()) + : TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); + } - if (client is not DiscordSocketClient socketClient) return null; - return socketClient.GetUser(id) as T ?? await socketClient.Rest.GetUserAsync(id).ConfigureAwait(false) as T; - } + private async Task GetUserAsync(IDiscordClient client, IUser? user, ulong id) + { + var result = user as T; + if (user is not null || !_useRest) + return result; - private static void AddResult(IDictionary results, T? user, float score) - { - if (user is not null && !results.ContainsKey(user.Id)) - results.Add(user.Id, new TypeReaderValue(user, score)); - } + if (client is not DiscordSocketClient socketClient) return null; + return socketClient.GetUser(id) as T ?? await socketClient.Rest.GetUserAsync(id).ConfigureAwait(false) as T; + } + + private static void AddResult(IDictionary results, T? user, float score) + { + if (user is not null && !results.ContainsKey(user.Id)) + results.Add(user.Id, new TypeReaderValue(user, score)); } } \ No newline at end of file diff --git a/Zhongli.Services/Expirable/ExpirableService.cs b/Zhongli.Services/Expirable/ExpirableService.cs index 1074fde..1443ea1 100644 --- a/Zhongli.Services/Expirable/ExpirableService.cs +++ b/Zhongli.Services/Expirable/ExpirableService.cs @@ -7,37 +7,36 @@ using Zhongli.Data.Models.Moderation.Infractions; using Zhongli.Services.Moderation; -namespace Zhongli.Services.Expirable +namespace Zhongli.Services.Expirable; + +public abstract class ExpirableService where T : class, IExpirable { - public abstract class ExpirableService where T : class, IExpirable - { - private readonly ZhongliContext _db; + private readonly ZhongliContext _db; - protected ExpirableService(ZhongliContext db) { _db = db; } + protected ExpirableService(ZhongliContext db) { _db = db; } - // ReSharper disable once MemberCanBePrivate.Global - public async Task ExpireEntityAsync(Guid id, CancellationToken cancellationToken = default) + // ReSharper disable once MemberCanBePrivate.Global + public async Task ExpireEntityAsync(Guid id, CancellationToken cancellationToken = default) + { + var expirable = _db.Set().FirstOrDefault(e => e.Id == id); + if (expirable?.IsActive() is true) { - var expirable = _db.Set().FirstOrDefault(e => e.Id == id); - if (expirable?.IsActive() is true) - { - expirable.EndedAt = DateTimeOffset.UtcNow; - await _db.SaveChangesAsync(cancellationToken); + expirable.EndedAt = DateTimeOffset.UtcNow; + await _db.SaveChangesAsync(cancellationToken); - await OnExpiredEntity(expirable, cancellationToken); - } + await OnExpiredEntity(expirable, cancellationToken); } + } - public void EnqueueExpirableEntity(T expire, CancellationToken cancellationToken = default) + public void EnqueueExpirableEntity(T expire, CancellationToken cancellationToken = default) + { + if (expire.ExpireAt is not null) { - if (expire.ExpireAt is not null) - { - BackgroundJob.Schedule(() - => ExpireEntityAsync(expire.Id, cancellationToken), - expire.ExpireAt.Value); - } + BackgroundJob.Schedule(() + => ExpireEntityAsync(expire.Id, cancellationToken), + expire.ExpireAt.Value); } - - protected abstract Task OnExpiredEntity(T expired, CancellationToken cancellationToken); } + + protected abstract Task OnExpiredEntity(T expired, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Zhongli.Services/Expirable/ExpiredEntityBehavior.cs b/Zhongli.Services/Expirable/ExpiredEntityBehavior.cs index c5a97e2..b9023c7 100644 --- a/Zhongli.Services/Expirable/ExpiredEntityBehavior.cs +++ b/Zhongli.Services/Expirable/ExpiredEntityBehavior.cs @@ -7,29 +7,28 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Moderation; -namespace Zhongli.Services.Expirable +namespace Zhongli.Services.Expirable; + +public class ExpiredEntityBehavior : INotificationHandler + where T : class, IExpirable { - public class ExpiredEntityBehavior : INotificationHandler - where T : class, IExpirable + private readonly ExpirableService _expire; + private readonly ZhongliContext _db; + + public ExpiredEntityBehavior(ZhongliContext db, ExpirableService expire) { - private readonly ExpirableService _expire; - private readonly ZhongliContext _db; + _db = db; + _expire = expire; + } - public ExpiredEntityBehavior(ZhongliContext db, ExpirableService expire) - { - _db = db; - _expire = expire; - } + public async Task Handle(ReadyNotification notification, CancellationToken cancellationToken) + { + var active = _db.Set().AsAsyncEnumerable() + .Where(m => m.IsActive()); - public async Task Handle(ReadyNotification notification, CancellationToken cancellationToken) + await foreach (var entity in active.WithCancellation(cancellationToken)) { - var active = _db.Set().AsAsyncEnumerable() - .Where(m => m.IsActive()); - - await foreach (var entity in active.WithCancellation(cancellationToken)) - { - _expire.EnqueueExpirableEntity(entity, cancellationToken); - } + _expire.EnqueueExpirableEntity(entity, cancellationToken); } } } \ No newline at end of file diff --git a/Zhongli.Services/Expirable/ExpiredEntitySetup.cs b/Zhongli.Services/Expirable/ExpiredEntitySetup.cs index 2479d8d..eb710ab 100644 --- a/Zhongli.Services/Expirable/ExpiredEntitySetup.cs +++ b/Zhongli.Services/Expirable/ExpiredEntitySetup.cs @@ -6,23 +6,22 @@ using Zhongli.Services.Core.Messages; using Zhongli.Services.Moderation; -namespace Zhongli.Services.Expirable +namespace Zhongli.Services.Expirable; + +public static class ExpiredEntitySetup { - public static class ExpiredEntitySetup - { - public static IServiceCollection AddExpirableServices(this IServiceCollection services) - => services - .AddExpirableService() - .AddExpirableService() - .AddExpirableService(); + public static IServiceCollection AddExpirableServices(this IServiceCollection services) + => services + .AddExpirableService() + .AddExpirableService() + .AddExpirableService(); - private static IServiceCollection AddExpirableService( - this IServiceCollection services) - where TExpirable : class, IExpirable - where TService : ExpirableService - => services - .AddScoped() - .AddScoped, TService>() - .AddScoped, ExpiredEntityBehavior>(); - } + private static IServiceCollection AddExpirableService( + this IServiceCollection services) + where TExpirable : class, IExpirable + where TService : ExpirableService + => services + .AddScoped() + .AddScoped, TService>() + .AddScoped, ExpiredEntityBehavior>(); } \ No newline at end of file diff --git a/Zhongli.Services/Expirable/TemporaryRoleMemberService.cs b/Zhongli.Services/Expirable/TemporaryRoleMemberService.cs index cba3c1e..1638350 100644 --- a/Zhongli.Services/Expirable/TemporaryRoleMemberService.cs +++ b/Zhongli.Services/Expirable/TemporaryRoleMemberService.cs @@ -7,42 +7,41 @@ using Zhongli.Data.Models.Discord; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Expirable +namespace Zhongli.Services.Expirable; + +public class TemporaryRoleMemberService : ExpirableService { - public class TemporaryRoleMemberService : ExpirableService + private readonly DiscordSocketClient _client; + private readonly ZhongliContext _db; + + public TemporaryRoleMemberService(ZhongliContext db, DiscordSocketClient client) : base(db) + { + _db = db; + _client = client; + } + + public async Task AddTemporaryRoleMemberAsync(IGuildUser user, IRole role, TimeSpan length, + CancellationToken cancellationToken = default) + { + await user.AddRoleAsync(role); + + var guild = await _db.Guilds.TrackGuildAsync(role.Guild, cancellationToken); + var temporary = new TemporaryRoleMember(user, role, length); + + guild.TemporaryRoleMembers.Add(temporary); + await _db.SaveChangesAsync(cancellationToken); + + EnqueueExpirableEntity(temporary, cancellationToken); + } + + protected override async Task OnExpiredEntity(TemporaryRoleMember temporary, + CancellationToken cancellationToken) { - private readonly DiscordSocketClient _client; - private readonly ZhongliContext _db; - - public TemporaryRoleMemberService(ZhongliContext db, DiscordSocketClient client) : base(db) - { - _db = db; - _client = client; - } - - public async Task AddTemporaryRoleMemberAsync(IGuildUser user, IRole role, TimeSpan length, - CancellationToken cancellationToken = default) - { - await user.AddRoleAsync(role); - - var guild = await _db.Guilds.TrackGuildAsync(role.Guild, cancellationToken); - var temporary = new TemporaryRoleMember(user, role, length); - - guild.TemporaryRoleMembers.Add(temporary); - await _db.SaveChangesAsync(cancellationToken); - - EnqueueExpirableEntity(temporary, cancellationToken); - } - - protected override async Task OnExpiredEntity(TemporaryRoleMember temporary, - CancellationToken cancellationToken) - { - var guild = _client.GetGuild(temporary.GuildId); - var role = guild?.GetRole(temporary.RoleId); - var user = guild?.GetUser(temporary.UserId); - - if (user is not null && role is not null) - await user.RemoveRoleAsync(role); - } + var guild = _client.GetGuild(temporary.GuildId); + var role = guild?.GetRole(temporary.RoleId); + var user = guild?.GetUser(temporary.UserId); + + if (user is not null && role is not null) + await user.RemoveRoleAsync(role); } } \ No newline at end of file diff --git a/Zhongli.Services/Expirable/TemporaryRoleService.cs b/Zhongli.Services/Expirable/TemporaryRoleService.cs index 2eceb84..d7d4dd0 100644 --- a/Zhongli.Services/Expirable/TemporaryRoleService.cs +++ b/Zhongli.Services/Expirable/TemporaryRoleService.cs @@ -7,39 +7,38 @@ using Zhongli.Data.Models.Discord; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Expirable +namespace Zhongli.Services.Expirable; + +public class TemporaryRoleService : ExpirableService { - public class TemporaryRoleService : ExpirableService + private readonly DiscordSocketClient _client; + private readonly ZhongliContext _db; + + public TemporaryRoleService(ZhongliContext db, DiscordSocketClient client) : base(db) + { + _db = db; + _client = client; + } + + public async Task CreateTemporaryRoleAsync(IRole role, TimeSpan length, + CancellationToken cancellationToken = default) { - private readonly DiscordSocketClient _client; - private readonly ZhongliContext _db; - - public TemporaryRoleService(ZhongliContext db, DiscordSocketClient client) : base(db) - { - _db = db; - _client = client; - } - - public async Task CreateTemporaryRoleAsync(IRole role, TimeSpan length, - CancellationToken cancellationToken = default) - { - var guild = await _db.Guilds.TrackGuildAsync(role.Guild, cancellationToken); - var temporary = new TemporaryRole(role, length); - - guild.TemporaryRoles.Add(temporary); - await _db.SaveChangesAsync(cancellationToken); - - EnqueueExpirableEntity(temporary, cancellationToken); - } - - protected override async Task OnExpiredEntity(TemporaryRole temporary, CancellationToken cancellationToken) - { - var role = _client - .GetGuild(temporary.GuildId) - ?.GetRole(temporary.RoleId); - - if (role is not null) - await role.DeleteAsync(); - } + var guild = await _db.Guilds.TrackGuildAsync(role.Guild, cancellationToken); + var temporary = new TemporaryRole(role, length); + + guild.TemporaryRoles.Add(temporary); + await _db.SaveChangesAsync(cancellationToken); + + EnqueueExpirableEntity(temporary, cancellationToken); + } + + protected override async Task OnExpiredEntity(TemporaryRole temporary, CancellationToken cancellationToken) + { + var role = _client + .GetGuild(temporary.GuildId) + ?.GetRole(temporary.RoleId); + + if (role is not null) + await role.DeleteAsync(); } } \ No newline at end of file diff --git a/Zhongli.Services/Image/ImageService.cs b/Zhongli.Services/Image/ImageService.cs index a98816a..bcd5bd7 100644 --- a/Zhongli.Services/Image/ImageService.cs +++ b/Zhongli.Services/Image/ImageService.cs @@ -7,84 +7,83 @@ using Zhongli.Services.Utilities; using Color = Discord.Color; -namespace Zhongli.Services.Image +namespace Zhongli.Services.Image; + +/// +/// Desribes a service that performs actions related to images. +/// +public interface IImageService { /// - /// Desribes a service that performs actions related to images. + /// Identifies a dominant color from the provided image. /// - public interface IImageService - { - /// - /// Identifies a dominant color from the provided image. - /// - /// The bytes that compose the image for which the dominant color is to be retrieved. - /// A dominant color in the provided image. - Color GetDominantColor(byte[] imageBytes); - - /// - /// Gets the dominant color of a user's avatar. - /// - /// The user to retrieve the avatar from. - /// A dominant color of the user. - ValueTask GetAvatarColor(IUser contextUser); - - /// - /// Identifies a dominant color from the image at the supplied location. - /// - /// The location of the image. - /// - /// A that will complete when the operation completes, - /// containing a dominant color in the image. - /// - ValueTask GetDominantColorAsync(Uri location); - } + /// The bytes that compose the image for which the dominant color is to be retrieved. + /// A dominant color in the provided image. + Color GetDominantColor(byte[] imageBytes); - public sealed class ImageService : IImageService - { - private readonly ColorThief _colorThief = new(); - private readonly IHttpClientFactory _httpClientFactory; - private readonly IMemoryCache _cache; + /// + /// Gets the dominant color of a user's avatar. + /// + /// The user to retrieve the avatar from. + /// A dominant color of the user. + ValueTask GetAvatarColor(IUser contextUser); - public ImageService(IHttpClientFactory httpClientFactory, IMemoryCache cache) - { - _httpClientFactory = httpClientFactory; - _cache = cache; - } + /// + /// Identifies a dominant color from the image at the supplied location. + /// + /// The location of the image. + /// + /// A that will complete when the operation completes, + /// containing a dominant color in the image. + /// + ValueTask GetDominantColorAsync(Uri location); +} + +public sealed class ImageService : IImageService +{ + private readonly ColorThief _colorThief = new(); + private readonly IHttpClientFactory _httpClientFactory; + private readonly IMemoryCache _cache; - /// - public Color GetDominantColor(byte[] imageBytes) - { - var quantizedColor = _colorThief.GetColor(imageBytes.ToBitmap(), 8); + public ImageService(IHttpClientFactory httpClientFactory, IMemoryCache cache) + { + _httpClientFactory = httpClientFactory; + _cache = cache; + } - return quantizedColor.ToDiscordColor(); - } + /// + public Color GetDominantColor(byte[] imageBytes) + { + var quantizedColor = _colorThief.GetColor(imageBytes.ToBitmap(), 8); - /// - public ValueTask GetAvatarColor(IUser contextUser) - { - ValueTask colorTask = default; + return quantizedColor.ToDiscordColor(); + } - if ((contextUser.GetAvatarUrl(size: 16) ?? contextUser.GetDefaultAvatarUrl()) is { } avatarUrl) - colorTask = GetDominantColorAsync(new Uri(avatarUrl)); + /// + public ValueTask GetAvatarColor(IUser contextUser) + { + ValueTask colorTask = default; - return colorTask; - } + if ((contextUser.GetAvatarUrl(size: 16) ?? contextUser.GetDefaultAvatarUrl()) is { } avatarUrl) + colorTask = GetDominantColorAsync(new Uri(avatarUrl)); - /// - public async ValueTask GetDominantColorAsync(Uri location) - { - var key = GetKey(location); + return colorTask; + } - if (_cache.TryGetValue(key, out Color color)) return color; + /// + public async ValueTask GetDominantColorAsync(Uri location) + { + var key = GetKey(location); - var imageBytes = await _httpClientFactory.CreateClient().GetByteArrayAsync(location); - color = GetDominantColor(imageBytes); + if (_cache.TryGetValue(key, out Color color)) return color; - _cache.Set(key, color, TimeSpan.FromDays(7)); + var imageBytes = await _httpClientFactory.CreateClient().GetByteArrayAsync(location); + color = GetDominantColor(imageBytes); - return color; - } + _cache.Set(key, color, TimeSpan.FromDays(7)); - private static object GetKey(Uri uri) => new { Target = "DominantColor", uri.AbsoluteUri }; + return color; } + + private static object GetKey(Uri uri) => new { Target = "DominantColor", uri.AbsoluteUri }; } \ No newline at end of file diff --git a/Zhongli.Services/Image/ImageSetup.cs b/Zhongli.Services/Image/ImageSetup.cs index 06f915c..49a054a 100644 --- a/Zhongli.Services/Image/ImageSetup.cs +++ b/Zhongli.Services/Image/ImageSetup.cs @@ -1,10 +1,9 @@ using Microsoft.Extensions.DependencyInjection; -namespace Zhongli.Services.Image +namespace Zhongli.Services.Image; + +public static class ImageSetup { - public static class ImageSetup - { - public static IServiceCollection AddImages(this IServiceCollection services) => - services.AddScoped(); - } + public static IServiceCollection AddImages(this IServiceCollection services) => + services.AddScoped(); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Criteria/CriteriaCriterion.cs b/Zhongli.Services/Interactive/Criteria/CriteriaCriterion.cs index f4e6d3a..3c92b26 100644 --- a/Zhongli.Services/Interactive/Criteria/CriteriaCriterion.cs +++ b/Zhongli.Services/Interactive/Criteria/CriteriaCriterion.cs @@ -4,31 +4,27 @@ using Discord.Addons.Interactive.Criteria; using Discord.Commands; -namespace Zhongli.Services.Interactive.Criteria -{ - public class CriteriaCriterion : ICriterion - { - public CriteriaCriterion(IEnumerable> criteria) { Criteria = criteria; } +namespace Zhongli.Services.Interactive.Criteria; - public CriteriaCriterion(params ICriterion[] criteria) { Criteria = criteria; } +public class CriteriaCriterion : ICriterion +{ + public CriteriaCriterion(IEnumerable> criteria) { Criteria = criteria; } - private CriteriaCriterion(IEnumerable> criteria, params ICriterion[] newCriteria) - { - Criteria = criteria.Concat(newCriteria); - } + public CriteriaCriterion(params ICriterion[] criteria) { Criteria = criteria; } - private IEnumerable> Criteria { get; } + private CriteriaCriterion(IEnumerable> criteria, params ICriterion[] newCriteria) { Criteria = criteria.Concat(newCriteria); } - public async Task JudgeAsync(SocketCommandContext sourceContext, T parameter) - { - var judges = Criteria - .Select(c => c.JudgeAsync(sourceContext, parameter)); + private IEnumerable> Criteria { get; } - var results = await Task.WhenAll(judges); + public async Task JudgeAsync(SocketCommandContext sourceContext, T parameter) + { + var judges = Criteria + .Select(c => c.JudgeAsync(sourceContext, parameter)); - return results.All(r => r); - } + var results = await Task.WhenAll(judges); - public CriteriaCriterion With(ICriterion criterion) => new(Criteria, criterion); + return results.All(r => r); } + + public CriteriaCriterion With(ICriterion criterion) => new(Criteria, criterion); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Criteria/CriteriaExtensions.cs b/Zhongli.Services/Interactive/Criteria/CriteriaExtensions.cs index 1f05b86..5513404 100644 --- a/Zhongli.Services/Interactive/Criteria/CriteriaExtensions.cs +++ b/Zhongli.Services/Interactive/Criteria/CriteriaExtensions.cs @@ -6,35 +6,34 @@ using Zhongli.Services.Interactive.TryParse; using Zhongli.Services.Interactive.TypeReaders; -namespace Zhongli.Services.Interactive.Criteria -{ - public static class CriteriaExtensions - { - public static CriteriaCriterion AsCriterion(this IEnumerable> criteria) => - new(criteria); +namespace Zhongli.Services.Interactive.Criteria; - public static CriteriaCriterion AsCriterion(this ICriterion criteria) => - new(criteria); +public static class CriteriaExtensions +{ + public static CriteriaCriterion AsCriterion(this IEnumerable> criteria) => + new(criteria); - public static ICriterion AsCriterion(this TypeReader reader, IServiceProvider? services = null) - where T : SocketMessage => - reader.AsCriterion(services); + public static CriteriaCriterion AsCriterion(this ICriterion criteria) => + new(criteria); - public static IEnumerable> GetCriteria(this IPromptCriteria promptCriteria) - where T : SocketMessage - { - var criteria = new List>(); + public static ICriterion AsCriterion(this TypeReader reader, IServiceProvider? services = null) + where T : SocketMessage => + reader.AsCriterion(services); - if (promptCriteria.Criteria is not null) - criteria.AddRange(promptCriteria.Criteria); + public static IEnumerable> GetCriteria(this IPromptCriteria promptCriteria) + where T : SocketMessage + { + var criteria = new List>(); - if (promptCriteria.TypeReader is not null) - criteria.Add(promptCriteria.TypeReader.AsCriterion()); + if (promptCriteria.Criteria is not null) + criteria.AddRange(promptCriteria.Criteria); - return criteria; - } + if (promptCriteria.TypeReader is not null) + criteria.Add(promptCriteria.TypeReader.AsCriterion()); - public static TryParseCriterion AsCriterion(this TryParseDelegate tryParse) => - new(tryParse); + return criteria; } + + public static TryParseCriterion AsCriterion(this TryParseDelegate tryParse) => + new(tryParse); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Criteria/IPromptCriteria.cs b/Zhongli.Services/Interactive/Criteria/IPromptCriteria.cs index 76a8028..8a5ac67 100644 --- a/Zhongli.Services/Interactive/Criteria/IPromptCriteria.cs +++ b/Zhongli.Services/Interactive/Criteria/IPromptCriteria.cs @@ -3,12 +3,11 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.Criteria +namespace Zhongli.Services.Interactive.Criteria; + +public interface IPromptCriteria { - public interface IPromptCriteria - { - ICollection>? Criteria { get; } + ICollection>? Criteria { get; } - TypeReader? TypeReader { get; } - } + TypeReader? TypeReader { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Functions/FuncCriterion.cs b/Zhongli.Services/Interactive/Functions/FuncCriterion.cs index 7e123bc..11e9753 100644 --- a/Zhongli.Services/Interactive/Functions/FuncCriterion.cs +++ b/Zhongli.Services/Interactive/Functions/FuncCriterion.cs @@ -4,15 +4,14 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.Functions +namespace Zhongli.Services.Interactive.Functions; + +public class FuncCriterion : ICriterion { - public class FuncCriterion : ICriterion - { - private readonly Func _func; + private readonly Func _func; - public FuncCriterion(Func func) { _func = func; } + public FuncCriterion(Func func) { _func = func; } - public Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) => - Task.FromResult(_func(parameter)); - } + public Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) => + Task.FromResult(_func(parameter)); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Functions/FuncCriterionExtensions.cs b/Zhongli.Services/Interactive/Functions/FuncCriterionExtensions.cs index ecbec5a..660d30c 100644 --- a/Zhongli.Services/Interactive/Functions/FuncCriterionExtensions.cs +++ b/Zhongli.Services/Interactive/Functions/FuncCriterionExtensions.cs @@ -1,10 +1,9 @@ using System; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.Functions +namespace Zhongli.Services.Interactive.Functions; + +public static class FuncCriterionExtensions { - public static class FuncCriterionExtensions - { - public static FuncCriterion AsCriterion(this Func func) => new(func); - } + public static FuncCriterion AsCriterion(this Func func) => new(func); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/InteractiveEntity.cs b/Zhongli.Services/Interactive/InteractiveEntity.cs index 4e845f6..102ea6a 100644 --- a/Zhongli.Services/Interactive/InteractiveEntity.cs +++ b/Zhongli.Services/Interactive/InteractiveEntity.cs @@ -11,107 +11,105 @@ using Zhongli.Services.Utilities; using EmbedBuilderExtensions = Zhongli.Services.Utilities.EmbedBuilderExtensions; -namespace Zhongli.Services.Interactive -{ - public abstract class InteractiveEntity : InteractivePromptBase where T : class - { - protected const string EmptyMatchMessage = "Unable to find any match. Provide at least 2 characters."; - private readonly CommandErrorHandler _error; - private readonly ZhongliContext _db; +namespace Zhongli.Services.Interactive; - protected InteractiveEntity(CommandErrorHandler error, ZhongliContext db) - { - _error = error; - _db = db; - } +public abstract class InteractiveEntity : InteractivePromptBase where T : class +{ + protected const string EmptyMatchMessage = "Unable to find any match. Provide at least 2 characters."; + private readonly CommandErrorHandler _error; + private readonly ZhongliContext _db; - protected virtual string? Title { get; } = null; + protected InteractiveEntity(CommandErrorHandler error, ZhongliContext db) + { + _error = error; + _db = db; + } - protected abstract (string Title, StringBuilder Value) EntityViewer(T entity); + protected virtual string? Title { get; } = null; - protected abstract bool IsMatch(T entity, string id); + protected abstract (string Title, StringBuilder Value) EntityViewer(T entity); - protected async Task AddEntityAsync(T entity) - { - var collection = await GetCollectionAsync(); - collection.Add(entity); + protected abstract bool IsMatch(T entity, string id); - await _db.SaveChangesAsync(); - await Context.Message.AddReactionAsync(new Emoji("✅")); - } + protected async Task AddEntityAsync(T entity) + { + var collection = await GetCollectionAsync(); + collection.Add(entity); - protected async Task PagedViewAsync(IEnumerable collection, string? title = null) - { - var author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild); - await PagedViewAsync(collection, EntityViewer, title, author); - } + await _db.SaveChangesAsync(); + await Context.Message.AddReactionAsync(new Emoji("✅")); + } - protected async Task PagedViewAsync(IEnumerable collection, - EmbedBuilderExtensions.EntityViewerDelegate entityViewer, - string? title = null, EmbedAuthorBuilder? author = null) - { - var pages = collection.ToEmbedFields(entityViewer); + protected async Task PagedViewAsync(IEnumerable collection, string? title = null) + { + var author = new EmbedAuthorBuilder().WithGuildAsAuthor(Context.Guild); + await PagedViewAsync(collection, EntityViewer, title, author); + } - var paginated = new PaginatedMessage - { - Title = title, - Pages = pages, - Author = author, - Options = new PaginatedAppearanceOptions - { - DisplayInformationIcon = false, - Timeout = TimeSpan.FromMinutes(10) - } - }; - - await PagedReplyAsync(paginated); - } + protected async Task PagedViewAsync(IEnumerable collection, EmbedBuilderExtensions.EntityViewerDelegate entityViewer, + string? title = null, EmbedAuthorBuilder? author = null) + { + var pages = collection.ToEmbedFields(entityViewer); - protected virtual async Task RemoveEntityAsync(string id) + var paginated = new PaginatedMessage { - var collection = await GetCollectionAsync(); - var entity = await TryFindEntityAsync(id, collection); - - if (entity is null) + Title = title, + Pages = pages, + Author = author, + Options = new PaginatedAppearanceOptions { - await _error.AssociateError(Context.Message, EmptyMatchMessage); - return; + DisplayInformationIcon = false, + Timeout = TimeSpan.FromMinutes(10) } + }; - await RemoveEntityAsync(entity); - await Context.Message.AddReactionAsync(new Emoji("✅")); - } + await PagedReplyAsync(paginated); + } - protected abstract Task RemoveEntityAsync(T entity); + protected virtual async Task RemoveEntityAsync(string id) + { + var collection = await GetCollectionAsync(); + var entity = await TryFindEntityAsync(id, collection); - protected virtual async Task ViewEntityAsync() + if (entity is null) { - var collection = await GetCollectionAsync(); - await PagedViewAsync(collection); + await _error.AssociateError(Context.Message, EmptyMatchMessage); + return; } - protected abstract Task> GetCollectionAsync(); + await RemoveEntityAsync(entity); + await Context.Message.AddReactionAsync(new Emoji("✅")); + } + + protected abstract Task RemoveEntityAsync(T entity); - protected async Task TryFindEntityAsync(string id, IEnumerable? collection = null) - { - if (id.Length < 2) - return null; + protected virtual async Task ViewEntityAsync() + { + var collection = await GetCollectionAsync(); + await PagedViewAsync(collection); + } - collection ??= await GetCollectionAsync(); - var filtered = collection - .Where(e => IsMatch(e, id)) - .ToList(); + protected abstract Task> GetCollectionAsync(); - if (filtered.Count <= 1) - return filtered.Count == 1 ? filtered.First() : null; + protected async Task TryFindEntityAsync(string id, IEnumerable? collection = null) + { + if (id.Length < 2) + return null; - var containsCriterion = new FuncCriterion(m => - int.TryParse(m.Content, out var selection) - && selection < filtered.Count && selection > -1); + collection ??= await GetCollectionAsync(); + var filtered = collection + .Where(e => IsMatch(e, id)) + .ToList(); - await PagedViewAsync(filtered, "Reply with a number to select."); - var selected = await NextMessageAsync(containsCriterion); - return selected is null ? null : filtered.ElementAtOrDefault(int.Parse(selected.Content)); - } + if (filtered.Count <= 1) + return filtered.Count == 1 ? filtered.First() : null; + + var containsCriterion = new FuncCriterion(m => + int.TryParse(m.Content, out var selection) + && selection < filtered.Count && selection > -1); + + await PagedViewAsync(filtered, "Reply with a number to select."); + var selected = await NextMessageAsync(containsCriterion); + return selected is null ? null : filtered.ElementAtOrDefault(int.Parse(selected.Content)); } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/InteractivePromptBase.cs b/Zhongli.Services/Interactive/InteractivePromptBase.cs index c45dc25..a09d228 100644 --- a/Zhongli.Services/Interactive/InteractivePromptBase.cs +++ b/Zhongli.Services/Interactive/InteractivePromptBase.cs @@ -9,53 +9,52 @@ using Zhongli.Services.Utilities; using Optional = Zhongli.Services.Interactive.TypeReaders.Optional; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public abstract class InteractivePromptBase : InteractiveBase { - public abstract class InteractivePromptBase : InteractiveBase + public IImageService ImageService { get; init; } = null!; + + public PromptCollection CreatePromptCollection(string? errorMessage = null) + where T : notnull => new(this, errorMessage); + + internal async Task<(SocketMessage? response, IUserMessage message)> Prompt(string question, + IUserMessage? message, PromptOptions? promptOptions) { - public IImageService ImageService { get; init; } = null!; - - public PromptCollection CreatePromptCollection(string? errorMessage = null) - where T : notnull => new(this, errorMessage); - - internal async Task<(SocketMessage? response, IUserMessage message)> Prompt(string question, - IUserMessage? message, PromptOptions? promptOptions) - { - message = await ModifyOrSendMessage(question, message, promptOptions); - - SocketMessage? response; - var timeout = TimeSpan.FromSeconds(promptOptions?.SecondsTimeout ?? 30); - if (promptOptions?.Criterion is null) - response = await NextMessageAsync(timeout: timeout); - else - response = await NextMessageAsync(timeout: timeout, criterion: promptOptions.Criterion); - - _ = response?.DeleteAsync(); - - if (!(promptOptions?.IsRequired ?? false) && response.IsSkipped()) - response = null; - - return (response, message); - } - - internal async Task ModifyOrSendMessage(string content, - IUserMessage? message, PromptOptions? promptOptions) - { - var embed = new EmbedBuilder() - .WithUserAsAuthor(Context.User) - .WithDescription(content) - .WithColor(promptOptions?.Color ?? - await ImageService.GetDominantColorAsync(new Uri(Context.User.GetDefiniteAvatarUrl()))) - .WithFields(promptOptions?.Fields ?? Enumerable.Empty()); - - if (!promptOptions?.IsRequired ?? false) - embed.WithFooter($"Reply '{Optional.SkipString}' if you don't need this."); - - if (message is null) - return await ReplyAsync(embed: embed.Build()); - - await message.ModifyAsync(msg => msg.Embed = embed.Build()); - return message; - } + message = await ModifyOrSendMessage(question, message, promptOptions); + + SocketMessage? response; + var timeout = TimeSpan.FromSeconds(promptOptions?.SecondsTimeout ?? 30); + if (promptOptions?.Criterion is null) + response = await NextMessageAsync(timeout: timeout); + else + response = await NextMessageAsync(timeout: timeout, criterion: promptOptions.Criterion); + + _ = response?.DeleteAsync(); + + if (!(promptOptions?.IsRequired ?? false) && response.IsSkipped()) + response = null; + + return (response, message); + } + + internal async Task ModifyOrSendMessage(string content, + IUserMessage? message, PromptOptions? promptOptions) + { + var embed = new EmbedBuilder() + .WithUserAsAuthor(Context.User) + .WithDescription(content) + .WithColor(promptOptions?.Color ?? + await ImageService.GetDominantColorAsync(new Uri(Context.User.GetDefiniteAvatarUrl()))) + .WithFields(promptOptions?.Fields ?? Enumerable.Empty()); + + if (!promptOptions?.IsRequired ?? false) + embed.WithFooter($"Reply '{Optional.SkipString}' if you don't need this."); + + if (message is null) + return await ReplyAsync(embed: embed.Build()); + + await message.ModifyAsync(msg => msg.Embed = embed.Build()); + return message; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/InteractiveTrigger.cs b/Zhongli.Services/Interactive/InteractiveTrigger.cs index 90e569b..169e382 100644 --- a/Zhongli.Services/Interactive/InteractiveTrigger.cs +++ b/Zhongli.Services/Interactive/InteractiveTrigger.cs @@ -6,75 +6,74 @@ using Zhongli.Services.Core.Listeners; using Zhongli.Services.Moderation; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public abstract class InteractiveTrigger : InteractiveEntity where T : Trigger { - public abstract class InteractiveTrigger : InteractiveEntity where T : Trigger + private readonly CommandErrorHandler _error; + private readonly ModerationService _moderation; + private readonly ZhongliContext _db; + + protected InteractiveTrigger(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) + : base(error, db) { - private readonly CommandErrorHandler _error; - private readonly ModerationService _moderation; - private readonly ZhongliContext _db; + _error = error; + _db = db; + _moderation = moderation; + } - protected InteractiveTrigger(CommandErrorHandler error, ZhongliContext db, ModerationService moderation) - : base(error, db) - { - _error = error; - _db = db; - _moderation = moderation; - } + [Command("delete")] + [Summary("Deletes a trigger by ID. Associated reprimands will be deleted.")] + protected async Task DeleteTriggerAsync(string id, + [Summary("Silently delete the reprimands in case there are too many.")] + bool silent = false) + { + var collection = await GetCollectionAsync(); + var trigger = await TryFindEntityAsync(id, collection); - [Command("delete")] - [Summary("Deletes a trigger by ID. Associated reprimands will be deleted.")] - protected async Task DeleteTriggerAsync(string id, - [Summary("Silently delete the reprimands in case there are too many.")] - bool silent = false) + if (trigger is null) { - var collection = await GetCollectionAsync(); - var trigger = await TryFindEntityAsync(id, collection); - - if (trigger is null) - { - await _error.AssociateError(Context.Message, EmptyMatchMessage); - return; - } - - await _moderation.DeleteTriggerAsync(trigger, (IGuildUser) Context.User, silent); - await Context.Message.AddReactionAsync(new Emoji("✅")); + await _error.AssociateError(Context.Message, EmptyMatchMessage); + return; } - [Command("enable")] - [Summary("Enables a trigger by ID.")] - protected Task EnableEntityAsync(string id) => ToggleEntityAsync(id, true); + await _moderation.DeleteTriggerAsync(trigger, (IGuildUser) Context.User, silent); + await Context.Message.AddReactionAsync(new Emoji("✅")); + } - [Command("disable")] - [Summary("Disables a trigger by ID. Associated reprimands will be kept.")] - protected override Task RemoveEntityAsync(string id) => ToggleEntityAsync(id, false); + [Command("enable")] + [Summary("Enables a trigger by ID.")] + protected Task EnableEntityAsync(string id) => ToggleEntityAsync(id, true); - [Command("toggle")] - [Summary("Toggles a trigger by ID. Associated reprimands will be kept.")] - protected async Task ToggleEntityAsync( - [Summary("The ID of the trigger.")] string id, - [Summary("Leave empty to toggle the state.")] - bool? state = null) - { - var collection = await GetCollectionAsync(); - var entity = await TryFindEntityAsync(id, collection); + [Command("disable")] + [Summary("Disables a trigger by ID. Associated reprimands will be kept.")] + protected override Task RemoveEntityAsync(string id) => ToggleEntityAsync(id, false); - if (entity is null) - await _error.AssociateError(Context.Message, EmptyMatchMessage); - else - await ToggleTriggerAsync(entity, state); - } + [Command("toggle")] + [Summary("Toggles a trigger by ID. Associated reprimands will be kept.")] + protected async Task ToggleEntityAsync( + [Summary("The ID of the trigger.")] string id, + [Summary("Leave empty to toggle the state.")] + bool? state = null) + { + var collection = await GetCollectionAsync(); + var entity = await TryFindEntityAsync(id, collection); + + if (entity is null) + await _error.AssociateError(Context.Message, EmptyMatchMessage); + else + await ToggleTriggerAsync(entity, state); + } - protected override Task RemoveEntityAsync(T entity) => Task.CompletedTask; + protected override Task RemoveEntityAsync(T entity) => Task.CompletedTask; - private async Task ToggleTriggerAsync(T entity, bool? state) - { - await _moderation.ToggleTriggerAsync(entity, (IGuildUser) Context.User, state); + private async Task ToggleTriggerAsync(T entity, bool? state) + { + await _moderation.ToggleTriggerAsync(entity, (IGuildUser) Context.User, state); - var (title, value) = EntityViewer(entity); - var embed = new EmbedBuilder().AddField(title, value); + var (title, value) = EntityViewer(entity); + var embed = new EmbedBuilder().AddField(title, value); - await ReplyAsync(embed: embed.Build()); - } + await ReplyAsync(embed: embed.Build()); } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Paginator/InteractiveExtensions.cs b/Zhongli.Services/Interactive/Paginator/InteractiveExtensions.cs index 6809a30..4239d8e 100644 --- a/Zhongli.Services/Interactive/Paginator/InteractiveExtensions.cs +++ b/Zhongli.Services/Interactive/Paginator/InteractiveExtensions.cs @@ -7,63 +7,62 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.Paginator +namespace Zhongli.Services.Interactive.Paginator; + +public static class InteractiveExtensions { - public static class InteractiveExtensions + public static EmbedBuilder ToEmbed(this PaginatedMessage paginated) { - public static EmbedBuilder ToEmbed(this PaginatedMessage paginated) - { - var embed = new EmbedBuilder() - .WithColor(paginated.Color); + var embed = new EmbedBuilder() + .WithColor(paginated.Color); - if (paginated.Author is not null) - embed.WithAuthor(paginated.Author); + if (paginated.Author is not null) + embed.WithAuthor(paginated.Author); - if (!string.IsNullOrEmpty(paginated.Title)) - embed.WithAuthor(paginated.Title); + if (!string.IsNullOrEmpty(paginated.Title)) + embed.WithAuthor(paginated.Title); - if (!string.IsNullOrEmpty(paginated.AlternateDescription)) - embed.WithDescription(paginated.AlternateDescription); + if (!string.IsNullOrEmpty(paginated.AlternateDescription)) + embed.WithDescription(paginated.AlternateDescription); - embed.Fields = paginated.Pages.Cast().ToList(); + embed.Fields = paginated.Pages.Cast().ToList(); - return embed; - } + return embed; + } - public static EmbedBuilder ToEmbed(this PaginatedMessage paginated, out string content) - { - content = paginated.Content; + public static EmbedBuilder ToEmbed(this PaginatedMessage paginated, out string content) + { + content = paginated.Content; - return paginated.ToEmbed(); - } + return paginated.ToEmbed(); + } - public static Task PagedDMAsync( - this InteractiveBase interactive, PaginatedMessage pager, - bool fromSourceUser = true) where T : SocketCommandContext - { - var criterion = new Criteria(); + public static Task PagedDMAsync( + this InteractiveBase interactive, PaginatedMessage pager, + bool fromSourceUser = true) where T : SocketCommandContext + { + var criterion = new Criteria(); - if (fromSourceUser) - criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); + if (fromSourceUser) + criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); - return SendPaginatedDMAsync(interactive.Interactive, interactive.Context, pager, criterion); - } + return SendPaginatedDMAsync(interactive.Interactive, interactive.Context, pager, criterion); + } - private static async Task SendPaginatedDMAsync( - this InteractiveService interactive, - SocketCommandContext context, PaginatedMessage pager, - ICriterion? criterion = null) - { - var callback = new PaginatedDMCallback(interactive, context, pager); - await callback.DisplayAsync().ConfigureAwait(false); + private static async Task SendPaginatedDMAsync( + this InteractiveService interactive, + SocketCommandContext context, PaginatedMessage pager, + ICriterion? criterion = null) + { + var callback = new PaginatedDMCallback(interactive, context, pager); + await callback.DisplayAsync().ConfigureAwait(false); - return callback.Message; - } + return callback.Message; + } - private class EnsureReactionFromSourceUserCriterion : ICriterion - { - public Task JudgeAsync(SocketCommandContext sourceContext, SocketReaction parameter) - => Task.FromResult(parameter.UserId == sourceContext.User.Id); - } + private class EnsureReactionFromSourceUserCriterion : ICriterion + { + public Task JudgeAsync(SocketCommandContext sourceContext, SocketReaction parameter) + => Task.FromResult(parameter.UserId == sourceContext.User.Id); } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Paginator/PaginatedDMCallback.cs b/Zhongli.Services/Interactive/Paginator/PaginatedDMCallback.cs index 445f8a6..ad6e0b0 100644 --- a/Zhongli.Services/Interactive/Paginator/PaginatedDMCallback.cs +++ b/Zhongli.Services/Interactive/Paginator/PaginatedDMCallback.cs @@ -4,57 +4,56 @@ using Discord.Addons.Interactive.Paginator; using Discord.Commands; -namespace Zhongli.Services.Interactive.Paginator +namespace Zhongli.Services.Interactive.Paginator; + +public class PaginatedDMCallback : PaginatedMessageCallback { - public class PaginatedDMCallback : PaginatedMessageCallback + private readonly InteractiveService _interactive; + private readonly PaginatedAppearanceOptions _options; + private readonly PaginatedMessage _pager; + + public PaginatedDMCallback( + InteractiveService interactive, SocketCommandContext sourceContext, PaginatedMessage pager) + : base(interactive, sourceContext, pager) { - private readonly InteractiveService _interactive; - private readonly PaginatedAppearanceOptions _options; - private readonly PaginatedMessage _pager; + _interactive = interactive; + _pager = pager; + _options = _pager.Options; + } - public PaginatedDMCallback( - InteractiveService interactive, SocketCommandContext sourceContext, PaginatedMessage pager) - : base(interactive, sourceContext, pager) - { - _interactive = interactive; - _pager = pager; - _options = _pager.Options; - } + public new IUserMessage Message { get; private set; } = null!; - public new IUserMessage Message { get; private set; } = null!; + public new async Task DisplayAsync() + { + var embed = BuildEmbed(); + var dm = await Context.User.GetOrCreateDMChannelAsync(); + Message = await dm.SendMessageAsync(_pager.Content, embed: embed).ConfigureAwait(false); - public new async Task DisplayAsync() + _interactive.AddReactionCallback(Message, this); + // Reactions take a while to add, don't wait for them + _ = Task.Run(async () => { - var embed = BuildEmbed(); - var dm = await Context.User.GetOrCreateDMChannelAsync(); - Message = await dm.SendMessageAsync(_pager.Content, embed: embed).ConfigureAwait(false); + await Message.AddReactionAsync(_options.First); + await Message.AddReactionAsync(_options.Back); + await Message.AddReactionAsync(_options.Next); + await Message.AddReactionAsync(_options.Last); - _interactive.AddReactionCallback(Message, this); - // Reactions take a while to add, don't wait for them - _ = Task.Run(async () => - { - await Message.AddReactionAsync(_options.First); - await Message.AddReactionAsync(_options.Back); - await Message.AddReactionAsync(_options.Next); - await Message.AddReactionAsync(_options.Last); - - if (_options.JumpDisplayOptions == JumpDisplayOptions.Always) - await Message.AddReactionAsync(_options.Jump); + if (_options.JumpDisplayOptions == JumpDisplayOptions.Always) + await Message.AddReactionAsync(_options.Jump); - await Message.AddReactionAsync(_options.Stop); + await Message.AddReactionAsync(_options.Stop); - if (_options.DisplayInformationIcon) - await Message.AddReactionAsync(_options.Info); - }); + if (_options.DisplayInformationIcon) + await Message.AddReactionAsync(_options.Info); + }); - if (Timeout is not null) + if (Timeout is not null) + { + _ = Task.Delay(Timeout.Value).ContinueWith(_ => { - _ = Task.Delay(Timeout.Value).ContinueWith(_ => - { - _interactive.RemoveReactionCallback(Message); - Message.DeleteAsync(); - }); - } + _interactive.RemoveReactionCallback(Message); + Message.DeleteAsync(); + }); } } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/Prompt.cs b/Zhongli.Services/Interactive/Prompt.cs index ebc2335..ada59a2 100644 --- a/Zhongli.Services/Interactive/Prompt.cs +++ b/Zhongli.Services/Interactive/Prompt.cs @@ -5,35 +5,34 @@ using Discord.WebSocket; using Zhongli.Services.Interactive.Criteria; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public class Prompt : IPromptCriteria where T : notnull { - public class Prompt : IPromptCriteria where T : notnull + public Prompt( + T key, string question, IEnumerable? fields, bool isRequired, int? timeout, + TypeReader? typeReader = null) { - public Prompt( - T key, string question, IEnumerable? fields, bool isRequired, int? timeout, - TypeReader? typeReader = null) - { - Key = key; - Question = question; - Fields = fields; - IsRequired = isRequired; - Timeout = timeout; - TypeReader = typeReader; - Criteria = new List>(); - } + Key = key; + Question = question; + Fields = fields; + IsRequired = isRequired; + Timeout = timeout; + TypeReader = typeReader; + Criteria = new List>(); + } - public bool IsRequired { get; } + public bool IsRequired { get; } - public IEnumerable? Fields { get; } + public IEnumerable? Fields { get; } - public int? Timeout { get; } + public int? Timeout { get; } - public string Question { get; } + public string Question { get; } - public T Key { get; } + public T Key { get; } - public ICollection>? Criteria { get; } + public ICollection>? Criteria { get; } - public TypeReader? TypeReader { get; set; } - } + public TypeReader? TypeReader { get; set; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/PromptBuilder.cs b/Zhongli.Services/Interactive/PromptBuilder.cs index 17d7700..91fc91f 100644 --- a/Zhongli.Services/Interactive/PromptBuilder.cs +++ b/Zhongli.Services/Interactive/PromptBuilder.cs @@ -10,130 +10,129 @@ using Zhongli.Services.Interactive.TryParse; using Zhongli.Services.Interactive.TypeReaders; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public partial class PromptCollection where T : notnull { - public partial class PromptCollection where T : notnull - { - public PromptCollection WithCriterion(ICriterion criterion) => - this.Modify(c => c.Criteria?.Add(criterion)); + public PromptCollection WithCriterion(ICriterion criterion) => + this.Modify(c => c.Criteria?.Add(criterion)); - public PromptCollection WithError(string error) => this.Modify(c => c.ErrorMessage = error); + public PromptCollection WithError(string error) => this.Modify(c => c.ErrorMessage = error); - public PromptCollection WithTimeout(int timeout) => this.Modify(c => c.Timeout = timeout); + public PromptCollection WithTimeout(int timeout) => this.Modify(c => c.Timeout = timeout); - public PromptCollection WithTypeReader(TypeReader reader) => this.Modify(p => p.TypeReader = reader); + public PromptCollection WithTypeReader(TypeReader reader) => this.Modify(p => p.TypeReader = reader); - public PromptOrCollection WithPrompt( - T key, string question, - IEnumerable? fields = null, - bool required = true, int? timeOut = null) - { - var prompt = new Prompt(key, question, fields, required, timeOut); - this.Modify(c => c.Prompts.Add(prompt)); - return new PromptOrCollection(prompt, this); - } + public PromptOrCollection WithPrompt( + T key, string question, + IEnumerable? fields = null, + bool required = true, int? timeOut = null) + { + var prompt = new Prompt(key, question, fields, required, timeOut); + this.Modify(c => c.Prompts.Add(prompt)); + return new PromptOrCollection(prompt, this); + } + + public async Task> GetAnswersAsync(bool deleteResponse = true) + { + var ret = new Dictionary(); + IUserMessage? message = null; - public async Task> GetAnswersAsync(bool deleteResponse = true) + foreach (var prompt in Prompts) { - var ret = new Dictionary(); - IUserMessage? message = null; + var criteria = + this.GetCriteria() + .Concat(prompt.GetCriteria()) + .AsCriterion(); - foreach (var prompt in Prompts) + var options = new PromptOptions { - var criteria = - this.GetCriteria() - .Concat(prompt.GetCriteria()) - .AsCriterion(); + Criterion = criteria, + Fields = prompt.Fields, + SecondsTimeout = prompt.Timeout ?? Timeout, + IsRequired = prompt.IsRequired + }; + var result = await Module.Prompt(prompt.Question, deleteResponse ? message : null, options); - var options = new PromptOptions - { - Criterion = criteria, - Fields = prompt.Fields, - SecondsTimeout = prompt.Timeout ?? Timeout, - IsRequired = prompt.IsRequired - }; - var result = await Module.Prompt(prompt.Question, deleteResponse ? message : null, options); + message = result.message; - message = result.message; + if (result.response is null) + { + if (!prompt.IsRequired) + continue; - if (result.response is null) + if (ErrorMessage is not null) { - if (!prompt.IsRequired) - continue; - - if (ErrorMessage is not null) - { - await Module.ModifyOrSendMessage(ErrorMessage ?? "You did not respond in time.", message, - new PromptOptions { Color = Color.Red }); - } - - throw new ArgumentNullException(nameof(result.response), "User did not respond in time"); + await Module.ModifyOrSendMessage(ErrorMessage ?? "You did not respond in time.", message, + new PromptOptions { Color = Color.Red }); } - object response = result.response.Content; - if (prompt.TypeReader is not null) - response = await prompt.TypeReader.ReadAsync(Context, response.ToString(), Services); - else if (TypeReader is not null) - response = await TypeReader.ReadAsync(Context, response.ToString(), Services); + throw new ArgumentNullException(nameof(result.response), "User did not respond in time"); + } - var promptResult = new PromptResult(prompt.Question, response); + object response = result.response.Content; + if (prompt.TypeReader is not null) + response = await prompt.TypeReader.ReadAsync(Context, response.ToString(), Services); + else if (TypeReader is not null) + response = await TypeReader.ReadAsync(Context, response.ToString(), Services); - ret[prompt.Key] = promptResult; - } + var promptResult = new PromptResult(prompt.Question, response); - return new ResultDictionary(ret); + ret[prompt.Key] = promptResult; } + + return new ResultDictionary(ret); } +} - public partial class PromptOrCollection +public partial class PromptOrCollection +{ + public PromptCollection ThatHas(TypeReader reader) { - public PromptCollection ThatHas(TypeReader reader) - { - if (!Prompt.IsRequired) - reader = new OptionalTypeReader(reader); + if (!Prompt.IsRequired) + reader = new OptionalTypeReader(reader); - Prompt.Modify(p => p.TypeReader = reader); - return Collection; - } - - public PromptCollection ThatHas(TryParseDelegate tryParse) => - ThatHas(tryParse.AsTypeReader()); - - public PromptCollection ThatHas(EnumTryParseDelegate tryParse, bool ignoreCase = true) - where T : struct, Enum => - ThatHas(tryParse.AsTypeReader(ignoreCase)); + Prompt.Modify(p => p.TypeReader = reader); + return Collection; + } - public PromptCollection ThatHas(ICriterion criterion) - { - Prompt.Modify(p => p.Criteria?.Add(criterion)); - return Collection; - } + public PromptCollection ThatHas(TryParseDelegate tryParse) => + ThatHas(tryParse.AsTypeReader()); - public PromptOrCollection WithPrompt( - TOptions key, string question, - IEnumerable? fields = null, - bool required = true, int? timeOut = null) - { - var prompt = new Prompt(key, question, fields, required, timeOut); - var collection = Collection.Modify(c => c.Prompts.Add(prompt)); - return new PromptOrCollection(prompt, collection); - } + public PromptCollection ThatHas(EnumTryParseDelegate tryParse, bool ignoreCase = true) + where T : struct, Enum => + ThatHas(tryParse.AsTypeReader(ignoreCase)); - public Task> GetAnswersAsync(bool deleteResponse = true) => - Collection.GetAnswersAsync(deleteResponse); + public PromptCollection ThatHas(ICriterion criterion) + { + Prompt.Modify(p => p.Criteria?.Add(criterion)); + return Collection; } - public static class PromptBuilderExtensions + public PromptOrCollection WithPrompt( + TOptions key, string question, + IEnumerable? fields = null, + bool required = true, int? timeOut = null) { - public static TValue GetOrDefault(this IDictionary dict, TKey key, TValue value) - where TKey : notnull - where TValue : notnull => - dict.TryGetValue(key, out var result) ? result : value; + var prompt = new Prompt(key, question, fields, required, timeOut); + var collection = Collection.Modify(c => c.Prompts.Add(prompt)); + return new PromptOrCollection(prompt, collection); + } - internal static T Modify(this T t, Action action) - { - action(t); - return t; - } + public Task> GetAnswersAsync(bool deleteResponse = true) => + Collection.GetAnswersAsync(deleteResponse); +} + +public static class PromptBuilderExtensions +{ + public static TValue GetOrDefault(this IDictionary dict, TKey key, TValue value) + where TKey : notnull + where TValue : notnull => + dict.TryGetValue(key, out var result) ? result : value; + + internal static T Modify(this T t, Action action) + { + action(t); + return t; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/PromptCollection.cs b/Zhongli.Services/Interactive/PromptCollection.cs index 938bc0f..2fb1e6e 100644 --- a/Zhongli.Services/Interactive/PromptCollection.cs +++ b/Zhongli.Services/Interactive/PromptCollection.cs @@ -5,52 +5,51 @@ using Discord.WebSocket; using Zhongli.Services.Interactive.Criteria; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public partial class PromptCollection : IPromptCriteria where T : notnull { - public partial class PromptCollection : IPromptCriteria where T : notnull + public PromptCollection( + InteractivePromptBase module, + string? errorMessage = null, IServiceProvider? services = null) { - public PromptCollection( - InteractivePromptBase module, - string? errorMessage = null, IServiceProvider? services = null) + ErrorMessage = errorMessage; + Module = module; + Services = services; + Criteria = new ICriterion[] { - ErrorMessage = errorMessage; - Module = module; - Services = services; - Criteria = new ICriterion[] - { - new EnsureSourceChannelCriterion(), - new EnsureSourceUserCriterion() - }; - } + new EnsureSourceChannelCriterion(), + new EnsureSourceUserCriterion() + }; + } - public int Timeout { get; set; } = 30; + public int Timeout { get; set; } = 30; - public InteractivePromptBase Module { get; } + public InteractivePromptBase Module { get; } - public IServiceProvider? Services { get; } + public IServiceProvider? Services { get; } - public List> Prompts { get; } = new(); + public List> Prompts { get; } = new(); - public SocketCommandContext Context => Module.Context; + public SocketCommandContext Context => Module.Context; - public string? ErrorMessage { get; set; } + public string? ErrorMessage { get; set; } - public ICollection> Criteria { get; } + public ICollection> Criteria { get; } - public TypeReader? TypeReader { get; set; } - } + public TypeReader? TypeReader { get; set; } +} - public partial class PromptOrCollection - where TOptions : notnull +public partial class PromptOrCollection + where TOptions : notnull +{ + public PromptOrCollection(Prompt prompt, PromptCollection collection) { - public PromptOrCollection(Prompt prompt, PromptCollection collection) - { - Prompt = prompt; - Collection = collection; - } + Prompt = prompt; + Collection = collection; + } - public Prompt Prompt { get; } + public Prompt Prompt { get; } - public PromptCollection Collection { get; } - } + public PromptCollection Collection { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/PromptOptions.cs b/Zhongli.Services/Interactive/PromptOptions.cs index 6512fff..cb32198 100644 --- a/Zhongli.Services/Interactive/PromptOptions.cs +++ b/Zhongli.Services/Interactive/PromptOptions.cs @@ -3,18 +3,17 @@ using Discord.WebSocket; using Zhongli.Services.Interactive.Criteria; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +internal class PromptOptions { - internal class PromptOptions - { - public bool IsRequired { get; init; } + public bool IsRequired { get; init; } - public Color? Color { get; init; } + public Color? Color { get; init; } - public CriteriaCriterion? Criterion { get; init; } + public CriteriaCriterion? Criterion { get; init; } - public IEnumerable? Fields { get; init; } + public IEnumerable? Fields { get; init; } - public int SecondsTimeout { get; init; } - } + public int SecondsTimeout { get; init; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/PromptResult.cs b/Zhongli.Services/Interactive/PromptResult.cs index 117ed79..a759083 100644 --- a/Zhongli.Services/Interactive/PromptResult.cs +++ b/Zhongli.Services/Interactive/PromptResult.cs @@ -2,48 +2,47 @@ using System.Collections.Generic; using Discord.Commands; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public class PromptResult { - public class PromptResult + public PromptResult(string question, object? userResponse) { - public PromptResult(string question, object? userResponse) - { - Question = question; - UserResponse = userResponse; - } + Question = question; + UserResponse = userResponse; + } - public object? UserResponse { get; } + public object? UserResponse { get; } - public string Question { get; } + public string Question { get; } - public T As() - { - if (UserResponse is TypeReaderResult result) - return (T) result.BestMatch; + public T As() + { + if (UserResponse is TypeReaderResult result) + return (T) result.BestMatch; - return (T) UserResponse!; - } + return (T) UserResponse!; + } - public T As(Func selector) => selector((TypeReaderResult) UserResponse!); + public T As(Func selector) => selector((TypeReaderResult) UserResponse!); - public static implicit operator string?(PromptResult? result) => result?.UserResponse?.ToString(); - } + public static implicit operator string?(PromptResult? result) => result?.UserResponse?.ToString(); +} - public static class PromptResultExtensions +public static class PromptResultExtensions +{ + public static TValue Get( + this IReadOnlyDictionary results, TKey key, Func selector) + where TKey : notnull => + results[key].As(selector); + + public static TValue GetOrDefault( + this IReadOnlyDictionary results, TKey key, TValue @default = default) + where TKey : notnull { - public static TValue Get( - this IReadOnlyDictionary results, TKey key, Func selector) - where TKey : notnull => - results[key].As(selector); - - public static TValue GetOrDefault( - this IReadOnlyDictionary results, TKey key, TValue @default = default) - where TKey : notnull - { - if (results.TryGetValue(key, out var result) && result.UserResponse is TValue) - return result.As(); - - return @default; - } + if (results.TryGetValue(key, out var result) && result.UserResponse is TValue) + return result.As(); + + return @default; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/ResultDictionary.cs b/Zhongli.Services/Interactive/ResultDictionary.cs index 78341bf..998826a 100644 --- a/Zhongli.Services/Interactive/ResultDictionary.cs +++ b/Zhongli.Services/Interactive/ResultDictionary.cs @@ -3,22 +3,21 @@ using System.Collections.ObjectModel; using Discord.Commands; -namespace Zhongli.Services.Interactive +namespace Zhongli.Services.Interactive; + +public class ResultDictionary : ReadOnlyDictionary where TOptions : notnull { - public class ResultDictionary : ReadOnlyDictionary where TOptions : notnull - { - public ResultDictionary(IDictionary dictionary) : base(dictionary) { } + public ResultDictionary(IDictionary dictionary) : base(dictionary) { } - public TValue Get(TOptions key) => this[key].As(); + public TValue Get(TOptions key) => this[key].As(); - public TValue Get(TOptions key, Func selector) => this[key].As(selector); + public TValue Get(TOptions key, Func selector) => this[key].As(selector); - public TValue? GetOrDefault(TOptions key, TValue? @default = default) - { - if (TryGetValue(key, out var result) && result!.UserResponse is TValue) - return result.As(); + public TValue? GetOrDefault(TOptions key, TValue? @default = default) + { + if (TryGetValue(key, out var result) && result!.UserResponse is TValue) + return result.As(); - return @default; - } + return @default; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TryParse/TryParseCriterion.cs b/Zhongli.Services/Interactive/TryParse/TryParseCriterion.cs index fd3abec..8436c3e 100644 --- a/Zhongli.Services/Interactive/TryParse/TryParseCriterion.cs +++ b/Zhongli.Services/Interactive/TryParse/TryParseCriterion.cs @@ -3,15 +3,14 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.TryParse +namespace Zhongli.Services.Interactive.TryParse; + +public class TryParseCriterion : ICriterion { - public class TryParseCriterion : ICriterion - { - private readonly TryParseDelegate _tryParse; + private readonly TryParseDelegate _tryParse; - public TryParseCriterion(TryParseDelegate tryParse) { _tryParse = tryParse; } + public TryParseCriterion(TryParseDelegate tryParse) { _tryParse = tryParse; } - public Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) => - Task.FromResult(_tryParse(parameter.Content, out _)); - } + public Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) => + Task.FromResult(_tryParse(parameter.Content, out _)); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TryParse/TryParseDelegate.cs b/Zhongli.Services/Interactive/TryParse/TryParseDelegate.cs index e40cca8..ed98cf3 100644 --- a/Zhongli.Services/Interactive/TryParse/TryParseDelegate.cs +++ b/Zhongli.Services/Interactive/TryParse/TryParseDelegate.cs @@ -1,6 +1,5 @@ -namespace Zhongli.Services.Interactive.TryParse -{ - public delegate bool TryParseDelegate(string input, out T result); +namespace Zhongli.Services.Interactive.TryParse; - public delegate bool EnumTryParseDelegate(string input, bool ignoreCase, out T result); -} \ No newline at end of file +public delegate bool TryParseDelegate(string input, out T result); + +public delegate bool EnumTryParseDelegate(string input, bool ignoreCase, out T result); \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TypeReaders/Optional.cs b/Zhongli.Services/Interactive/TypeReaders/Optional.cs index 7405db4..dc91df9 100644 --- a/Zhongli.Services/Interactive/TypeReaders/Optional.cs +++ b/Zhongli.Services/Interactive/TypeReaders/Optional.cs @@ -3,33 +3,32 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.TypeReaders +namespace Zhongli.Services.Interactive.TypeReaders; + +public static class Optional { - public static class Optional - { - public const string SkipString = "skip"; + public const string SkipString = "skip"; - public static bool IsSkipped(this string message) => - message.Equals(SkipString, StringComparison.OrdinalIgnoreCase); + public static bool IsSkipped(this string message) => + message.Equals(SkipString, StringComparison.OrdinalIgnoreCase); - public static bool IsSkipped(this SocketMessage? message) => - message?.Content.Equals(SkipString, StringComparison.OrdinalIgnoreCase) ?? true; - } + public static bool IsSkipped(this SocketMessage? message) => + message?.Content.Equals(SkipString, StringComparison.OrdinalIgnoreCase) ?? true; +} - public class OptionalTypeReader : TypeReader - { - private readonly TypeReader _reader; +public class OptionalTypeReader : TypeReader +{ + private readonly TypeReader _reader; - public OptionalTypeReader(TypeReader reader) { _reader = reader; } + public OptionalTypeReader(TypeReader reader) { _reader = reader; } - public override async Task ReadAsync( - ICommandContext context, string input, IServiceProvider services) - { - var result = await _reader.ReadAsync(context, input, services); + public override async Task ReadAsync( + ICommandContext context, string input, IServiceProvider services) + { + var result = await _reader.ReadAsync(context, input, services); - return result.IsSuccess || input.IsSkipped() - ? TypeReaderResult.FromSuccess(result) - : TypeReaderResult.FromError(result); - } + return result.IsSuccess || input.IsSkipped() + ? TypeReaderResult.FromSuccess(result) + : TypeReaderResult.FromError(result); } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TypeReaders/Reader.cs b/Zhongli.Services/Interactive/TypeReaders/Reader.cs index d9a86dc..a908b4d 100644 --- a/Zhongli.Services/Interactive/TypeReaders/Reader.cs +++ b/Zhongli.Services/Interactive/TypeReaders/Reader.cs @@ -1,12 +1,11 @@ using Discord; using Discord.Commands; -namespace Zhongli.Services.Interactive.TypeReaders +namespace Zhongli.Services.Interactive.TypeReaders; + +public static class Reader { - public static class Reader - { - public static TypeReader Channel() where T : class, IChannel => new ChannelTypeReader(); + public static TypeReader Channel() where T : class, IChannel => new ChannelTypeReader(); - public static TypeReader Message() where T : class, IMessage => new Core.TypeReaders.MessageTypeReader(); - } + public static TypeReader Message() where T : class, IMessage => new Core.TypeReaders.MessageTypeReader(); } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TypeReaders/TypeReaderCriterion.cs b/Zhongli.Services/Interactive/TypeReaders/TypeReaderCriterion.cs index cbad618..1a34aa0 100644 --- a/Zhongli.Services/Interactive/TypeReaders/TypeReaderCriterion.cs +++ b/Zhongli.Services/Interactive/TypeReaders/TypeReaderCriterion.cs @@ -4,24 +4,23 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Interactive.TypeReaders +namespace Zhongli.Services.Interactive.TypeReaders; + +public class TypeReaderCriterion : ICriterion { - public class TypeReaderCriterion : ICriterion - { - private readonly IServiceProvider? _services; - private readonly TypeReader _reader; + private readonly IServiceProvider? _services; + private readonly TypeReader _reader; - public TypeReaderCriterion(TypeReader reader, IServiceProvider? services = null) - { - _reader = reader; - _services = services; - } + public TypeReaderCriterion(TypeReader reader, IServiceProvider? services = null) + { + _reader = reader; + _services = services; + } - public async Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) - { - var result = await _reader.ReadAsync(sourceContext, parameter.Content, _services); + public async Task JudgeAsync(SocketCommandContext sourceContext, SocketMessage parameter) + { + var result = await _reader.ReadAsync(sourceContext, parameter.Content, _services); - return result.IsSuccess; - } + return result.IsSuccess; } } \ No newline at end of file diff --git a/Zhongli.Services/Interactive/TypeReaders/TypeReaderExtensions.cs b/Zhongli.Services/Interactive/TypeReaders/TypeReaderExtensions.cs index ae69754..3ddc40c 100644 --- a/Zhongli.Services/Interactive/TypeReaders/TypeReaderExtensions.cs +++ b/Zhongli.Services/Interactive/TypeReaders/TypeReaderExtensions.cs @@ -3,18 +3,17 @@ using Zhongli.Services.Core.TypeReaders; using Zhongli.Services.Interactive.TryParse; -namespace Zhongli.Services.Interactive.TypeReaders +namespace Zhongli.Services.Interactive.TypeReaders; + +public static class TypeReaderExtensions { - public static class TypeReaderExtensions - { - public static EnumTryParseTypeReader AsTypeReader(this EnumTryParseDelegate tryParse, - bool ignoreCase = true) where T : struct, Enum => - new(ignoreCase); + public static EnumTryParseTypeReader AsTypeReader(this EnumTryParseDelegate tryParse, + bool ignoreCase = true) where T : struct, Enum => + new(ignoreCase); - public static TryParseTypeReader AsTypeReader(this TryParseDelegate tryParse) => - new(tryParse); + public static TryParseTypeReader AsTypeReader(this TryParseDelegate tryParse) => + new(tryParse); - public static TypeReaderCriterion AsCriterion(this TypeReader reader, IServiceProvider? services = null) => - new(reader, services); - } + public static TypeReaderCriterion AsCriterion(this TypeReader reader, IServiceProvider? services = null) => + new(reader, services); } \ No newline at end of file diff --git a/Zhongli.Services/Logging/LoggingExtensions.cs b/Zhongli.Services/Logging/LoggingExtensions.cs index af2c307..1c355b4 100644 --- a/Zhongli.Services/Logging/LoggingExtensions.cs +++ b/Zhongli.Services/Logging/LoggingExtensions.cs @@ -10,126 +10,125 @@ using Zhongli.Services.Utilities; using Attachment = Zhongli.Data.Models.Discord.Message.Attachment; -namespace Zhongli.Services.Logging +namespace Zhongli.Services.Logging; + +public static class LoggingExtensions { - public static class LoggingExtensions + public static EmbedBuilder AddImages(this EmbedBuilder embed, MessageLog log) { - public static EmbedBuilder AddImages(this EmbedBuilder embed, MessageLog log) - { - var images = log.GetImages().ToList(); + var images = log.GetImages().ToList(); - var image = images.FirstOrDefault(); - if (image is null) return embed; + var image = images.FirstOrDefault(); + if (image is null) return embed; - return embed - .WithImageUrl(image.ProxyUrl) - .AddOtherImages(images.Skip(1).ToList()); - } + return embed + .WithImageUrl(image.ProxyUrl) + .AddOtherImages(images.Skip(1).ToList()); + } - public static string ChannelMentionMarkdown(this IChannelEntity channel) - => $"{channel.MentionChannel()} ({channel.ChannelId})"; + public static string ChannelMentionMarkdown(this IChannelEntity channel) + => $"{channel.MentionChannel()} ({channel.ChannelId})"; - public static string GetTitle(this ILog log) + public static string GetTitle(this ILog log) + { + var title = log switch { - var title = log switch - { - MessageDeleteLog => "Message", - MessageLog => "Message", - MessagesDeleteLog => "Bulk Message", - ReactionDeleteLog => "Reaction", - ReactionLog => "Reaction", - _ => throw new ArgumentOutOfRangeException( - nameof(log), log, "Invalid log type.") - }; - - return $"{title.Replace("Log", string.Empty)}"; - } + MessageDeleteLog => "Message", + MessageLog => "Message", + MessagesDeleteLog => "Bulk Message", + ReactionDeleteLog => "Reaction", + ReactionLog => "Reaction", + _ => throw new ArgumentOutOfRangeException( + nameof(log), log, "Invalid log type.") + }; + + return $"{title.Replace("Log", string.Empty)}"; + } + + public static string JumpUrlMarkdown(this IMessageEntity message) + => $"[Jump]({message.JumpUrl()}) ({message.MessageId}) from {message.MentionChannel()}"; - public static string JumpUrlMarkdown(this IMessageEntity message) - => $"[Jump]({message.JumpUrl()}) ({message.MessageId}) from {message.MentionChannel()}"; + public static StringBuilder GetDetails(this IEnumerable logs) + { + var builder = new StringBuilder(); - public static StringBuilder GetDetails(this IEnumerable logs) + foreach (var (message, index) in logs.AsIndexable()) { - var builder = new StringBuilder(); + builder + .AppendLine($"## Message {index}") + .Append(message.GetDetails()); + } - foreach (var (message, index) in logs.AsIndexable()) - { - builder - .AppendLine($"## Message {index}") - .Append(message.GetDetails()); - } + return builder; + } - return builder; - } + private static EmbedBuilder AddOtherImages(this EmbedBuilder embed, IReadOnlyCollection images) + { + if (!images.Any()) return embed; - private static EmbedBuilder AddOtherImages(this EmbedBuilder embed, IReadOnlyCollection images) - { - if (!images.Any()) return embed; + var builder = new StringBuilder().AppendImageUrls(images); + return embed.AddField("Other Images", builder.ToString()); + } - var builder = new StringBuilder().AppendImageUrls(images); - return embed.AddField("Other Images", builder.ToString()); - } + private static IEnumerable GetImages(this MessageLog log) + { + var attachments = log.Attachments.Cast(); + var thumbnails = log.Embeds + .Select(e => e.Thumbnail) + .OfType(); - private static IEnumerable GetImages(this MessageLog log) - { - var attachments = log.Attachments.Cast(); - var thumbnails = log.Embeds - .Select(e => e.Thumbnail) - .OfType(); + return attachments.Concat(thumbnails); + } - return attachments.Concat(thumbnails); - } + private static string Attachment(Attachment a) => $"{Image(a)} {a.Size.Bytes().Humanize()}"; - private static string Attachment(Attachment a) => $"{Image(a)} {a.Size.Bytes().Humanize()}"; + private static string Image(IImage a) => $"[{a.Width}x{a.Height}px]({a.Url}) [Proxy]({a.ProxyUrl})"; - private static string Image(IImage a) => $"[{a.Width}x{a.Height}px]({a.Url}) [Proxy]({a.ProxyUrl})"; + private static StringBuilder AppendImageUrls(this StringBuilder builder, IReadOnlyCollection images) + { + if (!images.Any()) return builder; - private static StringBuilder AppendImageUrls(this StringBuilder builder, IReadOnlyCollection images) + foreach (var image in images) { - if (!images.Any()) return builder; - - foreach (var image in images) + builder.AppendLine(image switch { - builder.AppendLine(image switch - { - Attachment a => Attachment(a), - _ => Image(image) - }); - } - - return builder; + Attachment a => Attachment(a), + _ => Image(image) + }); } - private static StringBuilder GetDetails(this MessageLog log) - { - var builder = new StringBuilder() - .AppendLine("### Details") - .AppendLine($"- User: {log.User.Username} {log.MentionUser()}") - .AppendLine($"- ID: [{log.Id}]({log.JumpUrl()})") - .AppendLine($"- Channel: [{log.MentionChannel()}]") - .AppendLine($"- Date: {log.LogDate}"); + return builder; + } - if (log.EditedTimestamp is not null) - builder.AppendLine($"- Edited: {log.EditedTimestamp}"); + private static StringBuilder GetDetails(this MessageLog log) + { + var builder = new StringBuilder() + .AppendLine("### Details") + .AppendLine($"- User: {log.User.Username} {log.MentionUser()}") + .AppendLine($"- ID: [{log.Id}]({log.JumpUrl()})") + .AppendLine($"- Channel: [{log.MentionChannel()}]") + .AppendLine($"- Date: {log.LogDate}"); - if (!string.IsNullOrWhiteSpace(log.Content)) - { - builder.AppendLine("### Content"); - foreach (var line in log.Content.Split(Environment.NewLine)) - { - builder.AppendLine($"> {line}"); - } - } + if (log.EditedTimestamp is not null) + builder.AppendLine($"- Edited: {log.EditedTimestamp}"); - var images = log.GetImages().ToList(); - if (images.Any()) + if (!string.IsNullOrWhiteSpace(log.Content)) + { + builder.AppendLine("### Content"); + foreach (var line in log.Content.Split(Environment.NewLine)) { - builder - .AppendLine("### Images") - .AppendImageUrls(images); + builder.AppendLine($"> {line}"); } + } - return builder; + var images = log.GetImages().ToList(); + if (images.Any()) + { + builder + .AppendLine("### Images") + .AppendImageUrls(images); } + + return builder; } } \ No newline at end of file diff --git a/Zhongli.Services/Logging/LoggingService.cs b/Zhongli.Services/Logging/LoggingService.cs index c4c711e..f5c0e0d 100644 --- a/Zhongli.Services/Logging/LoggingService.cs +++ b/Zhongli.Services/Logging/LoggingService.cs @@ -23,406 +23,405 @@ using Zhongli.Services.Utilities; using Embed = Zhongli.Data.Models.Discord.Message.Embed; -namespace Zhongli.Services.Logging +namespace Zhongli.Services.Logging; + +public class LoggingService { - public class LoggingService + private readonly DiscordSocketClient _client; + private readonly ZhongliContext _db; + + public LoggingService(DiscordSocketClient client, ZhongliContext db) { - private readonly DiscordSocketClient _client; - private readonly ZhongliContext _db; + _client = client; + _db = db; + } - public LoggingService(DiscordSocketClient client, ZhongliContext db) - { - _client = client; - _db = db; - } + public async Task LogAsync(MessageReceivedNotification notification, CancellationToken cancellationToken) + { + if (notification.Message is not IUserMessage { Channel: INestedChannel channel } message) return; + if (message.Author is not IGuildUser user || user.IsBot) return; + if (await IsExcludedAsync(channel, user, cancellationToken)) return; - public async Task LogAsync(MessageReceivedNotification notification, CancellationToken cancellationToken) - { - if (notification.Message is not IUserMessage { Channel: INestedChannel channel } message) return; - if (message.Author is not IGuildUser user || user.IsBot) return; - if (await IsExcludedAsync(channel, user, cancellationToken)) return; + var userEntity = await _db.Users.TrackUserAsync(user, cancellationToken); + await LogMessageAsync(userEntity, message, cancellationToken); + } - var userEntity = await _db.Users.TrackUserAsync(user, cancellationToken); - await LogMessageAsync(userEntity, message, cancellationToken); - } + public async Task LogAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) + { + if (notification.Channel is not INestedChannel channel) return; - public async Task LogAsync(ReactionAddedNotification notification, CancellationToken cancellationToken) - { - if (notification.Channel is not INestedChannel channel) return; + var reaction = notification.Reaction; - var reaction = notification.Reaction; + var user = reaction.User.GetValueOrDefault(); + if (user is not IGuildUser guildUser || guildUser.IsBot) return; + if (await IsExcludedAsync(channel, guildUser, cancellationToken)) return; - var user = reaction.User.GetValueOrDefault(); - if (user is not IGuildUser guildUser || guildUser.IsBot) return; - if (await IsExcludedAsync(channel, guildUser, cancellationToken)) return; + var message = await notification.Message.GetOrDownloadAsync(); + var metadata = message.Reactions[reaction.Emote]; + if (metadata.ReactionCount > 1) return; - var message = await notification.Message.GetOrDownloadAsync(); - var metadata = message.Reactions[reaction.Emote]; - if (metadata.ReactionCount > 1) return; + var userEntity = await _db.Users.TrackUserAsync(guildUser, cancellationToken); + var log = await LogReactionAsync(userEntity, reaction, cancellationToken); + await PublishLogAsync(log, LogType.ReactionAdded, channel.Guild, cancellationToken); + } - var userEntity = await _db.Users.TrackUserAsync(guildUser, cancellationToken); - var log = await LogReactionAsync(userEntity, reaction, cancellationToken); - await PublishLogAsync(log, LogType.ReactionAdded, channel.Guild, cancellationToken); - } + public async Task LogAsync(ReactionRemovedNotification notification, CancellationToken cancellationToken) + { + if (notification.Channel is not IGuildChannel channel) return; - public async Task LogAsync(ReactionRemovedNotification notification, CancellationToken cancellationToken) - { - if (notification.Channel is not IGuildChannel channel) return; + var log = await LogDeletionAsync(notification.Reaction, null, cancellationToken); + await PublishLogAsync(log, LogType.ReactionRemoved, channel.Guild, cancellationToken); + } - var log = await LogDeletionAsync(notification.Reaction, null, cancellationToken); - await PublishLogAsync(log, LogType.ReactionRemoved, channel.Guild, cancellationToken); - } + public async Task LogAsync(MessageDeletedNotification notification, CancellationToken cancellationToken) + { + if (notification.Channel is not IGuildChannel channel) return; - public async Task LogAsync(MessageDeletedNotification notification, CancellationToken cancellationToken) - { - if (notification.Channel is not IGuildChannel channel) return; + var message = notification.Message.Value; + var details = await TryGetAuditLogDetails(message, channel.Guild); + + var latest = await GetLatestMessage(notification.Message.Id, cancellationToken); + if (latest is null) return; - var message = notification.Message.Value; - var details = await TryGetAuditLogDetails(message, channel.Guild); + var log = await LogDeletionAsync(latest, details, cancellationToken); + await PublishLogAsync(log, LogType.MessageDeleted, channel.Guild, cancellationToken); + } - var latest = await GetLatestMessage(notification.Message.Id, cancellationToken); - if (latest is null) return; + public async Task LogAsync(MessageUpdatedNotification notification, CancellationToken cancellationToken) + { + if (notification.NewMessage is not IUserMessage { Channel: INestedChannel channel } message) return; + if (message.Author is not IGuildUser user || user.IsBot) return; + if (await IsExcludedAsync(channel, user, cancellationToken)) return; - var log = await LogDeletionAsync(latest, details, cancellationToken); - await PublishLogAsync(log, LogType.MessageDeleted, channel.Guild, cancellationToken); - } + var latest = await GetLatestMessage(message.Id, cancellationToken); + if (latest is null) return; - public async Task LogAsync(MessageUpdatedNotification notification, CancellationToken cancellationToken) + if (latest.Embeds.Count != message.Embeds.Count) { - if (notification.NewMessage is not IUserMessage { Channel: INestedChannel channel } message) return; - if (message.Author is not IGuildUser user || user.IsBot) return; - if (await IsExcludedAsync(channel, user, cancellationToken)) return; - - var latest = await GetLatestMessage(message.Id, cancellationToken); - if (latest is null) return; - - if (latest.Embeds.Count != message.Embeds.Count) - { - await UpdateLogEmbedsAsync(latest, message, cancellationToken); - return; - } - - var userEntity = await _db.Users.TrackUserAsync(user, cancellationToken); - var log = await LogMessageAsync(userEntity, message, latest, cancellationToken); - await PublishLogAsync(log, LogType.MessageUpdated, channel.Guild, cancellationToken); + await UpdateLogEmbedsAsync(latest, message, cancellationToken); + return; } - public async Task LogAsync(MessagesBulkDeletedNotification notification, CancellationToken cancellationToken) - { - if (notification.Channel is not IGuildChannel channel) return; + var userEntity = await _db.Users.TrackUserAsync(user, cancellationToken); + var log = await LogMessageAsync(userEntity, message, latest, cancellationToken); + await PublishLogAsync(log, LogType.MessageUpdated, channel.Guild, cancellationToken); + } - var details = await TryGetAuditLogDetails(notification.Messages.Count, channel); - var logs = await notification.Messages.ToAsyncEnumerable() - .SelectAwait(async m => await GetLatestMessage(m.Id, cancellationToken)) - .ToListAsync(cancellationToken); + public async Task LogAsync(MessagesBulkDeletedNotification notification, CancellationToken cancellationToken) + { + if (notification.Channel is not IGuildChannel channel) return; - var log = await LogDeletionAsync(logs.OfType(), channel, details, cancellationToken); - await PublishLogAsync(log, LogType.MessagesBulkDeleted, channel.Guild, cancellationToken); - } + var details = await TryGetAuditLogDetails(notification.Messages.Count, channel); + var logs = await notification.Messages.ToAsyncEnumerable() + .SelectAwait(async m => await GetLatestMessage(m.Id, cancellationToken)) + .ToListAsync(cancellationToken); - private EmbedLog AddDetails(EmbedBuilder embed, MessageLog log) - { - var user = _client.GetUser(log.UserId); + var log = await LogDeletionAsync(logs.OfType(), channel, details, cancellationToken); + await PublishLogAsync(log, LogType.MessagesBulkDeleted, channel.Guild, cancellationToken); + } - embed - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .AddContent(log.Content); - - var updated = log.UpdatedLog; - if (updated is not null) - { - var date = updated.EditedTimestamp ?? updated.LogDate; - embed - .AddField("After", updated.Content) - .AddField("Edited", date.ToUniversalTimestamp(), true); - } + private EmbedLog AddDetails(EmbedBuilder embed, MessageLog log) + { + var user = _client.GetUser(log.UserId); - embed - .AddField("Created", log.Timestamp.ToUniversalTimestamp(), true) - .AddField("Message", log.JumpUrlMarkdown(), true); + embed + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .AddContent(log.Content); - var urls = log.Embeds.Select(e => e.Url); - return new EmbedLog(embed.AddImages(log), string.Join(Environment.NewLine, urls)); + var updated = log.UpdatedLog; + if (updated is not null) + { + var date = updated.EditedTimestamp ?? updated.LogDate; + embed + .AddField("After", updated.Content) + .AddField("Edited", date.ToUniversalTimestamp(), true); } - private EmbedLog AddDetails(EmbedBuilder embed, T log) where T : ILog, IReactionEntity - { - var user = _client.GetUser(log.UserId); - var reaction = log.Emote; + embed + .AddField("Created", log.Timestamp.ToUniversalTimestamp(), true) + .AddField("Message", log.JumpUrlMarkdown(), true); - embed - .WithUserAsAuthor(user, AuthorOptions.IncludeId) - .AddContent($"{reaction} {Format.Code($"{reaction}")}") - .AddField("Message", log.JumpUrlMarkdown(), true) - .AddField("Channel", log.ChannelMentionMarkdown(), true) - .AddField("Date", log.LogDate.ToUniversalTimestamp(), true); + var urls = log.Embeds.Select(e => e.Url); + return new EmbedLog(embed.AddImages(log), string.Join(Environment.NewLine, urls)); + } - if (reaction is EmoteEntity emote) - embed.WithThumbnailUrl(CDN.GetEmojiUrl(emote.EmoteId, emote.IsAnimated)); + private EmbedLog AddDetails(EmbedBuilder embed, T log) where T : ILog, IReactionEntity + { + var user = _client.GetUser(log.UserId); + var reaction = log.Emote; - return new EmbedLog(embed); - } + embed + .WithUserAsAuthor(user, AuthorOptions.IncludeId) + .AddContent($"{reaction} {Format.Code($"{reaction}")}") + .AddField("Message", log.JumpUrlMarkdown(), true) + .AddField("Channel", log.ChannelMentionMarkdown(), true) + .AddField("Date", log.LogDate.ToUniversalTimestamp(), true); - private EmbedLog BuildLog(ILog log) - { - var embed = new EmbedBuilder() - .WithTitle(log.GetTitle()) - .WithColor(Color.Blue) - .WithCurrentTimestamp(); - - return log switch - { - MessageLog message => AddDetails(embed, message), - ReactionLog reaction => AddDetails(embed, reaction), - _ => throw new ArgumentOutOfRangeException( - nameof(log), log, "Invalid log type.") - }; - } + if (reaction is EmoteEntity emote) + embed.WithThumbnailUrl(CDN.GetEmojiUrl(emote.EmoteId, emote.IsAnimated)); + + return new EmbedLog(embed); + } - private static MemoryStream GenerateStreamFromString(string value) - => new(Encoding.Unicode.GetBytes(value)); + private EmbedLog BuildLog(ILog log) + { + var embed = new EmbedBuilder() + .WithTitle(log.GetTitle()) + .WithColor(Color.Blue) + .WithCurrentTimestamp(); - private async Task PublishLogAsync(DeleteLog? log, LogType type, IGuild guild, - CancellationToken cancellationToken) + return log switch { - if (log is null) return; + MessageLog message => AddDetails(embed, message), + ReactionLog reaction => AddDetails(embed, reaction), + _ => throw new ArgumentOutOfRangeException( + nameof(log), log, "Invalid log type.") + }; + } - var embed = await BuildLogAsync(log, cancellationToken); - var channel = await GetLoggingChannelAsync(type, guild, cancellationToken); + private static MemoryStream GenerateStreamFromString(string value) + => new(Encoding.Unicode.GetBytes(value)); - await PublishLogAsync(embed, channel); - } + private async Task PublishLogAsync(DeleteLog? log, LogType type, IGuild guild, + CancellationToken cancellationToken) + { + if (log is null) return; - private async Task PublishLogAsync(ILog? log, LogType type, IGuild guild, CancellationToken cancellationToken) - { - if (log is null) return; + var embed = await BuildLogAsync(log, cancellationToken); + var channel = await GetLoggingChannelAsync(type, guild, cancellationToken); - var embed = BuildLog(log); - var channel = await GetLoggingChannelAsync(type, guild, cancellationToken); + await PublishLogAsync(embed, channel); + } - await PublishLogAsync(embed, channel); - } + private async Task PublishLogAsync(ILog? log, LogType type, IGuild guild, CancellationToken cancellationToken) + { + if (log is null) return; + + var embed = BuildLog(log); + var channel = await GetLoggingChannelAsync(type, guild, cancellationToken); + + await PublishLogAsync(embed, channel); + } + + private static async Task PublishLogAsync(EmbedLog? log, IMessageChannel? channel) + { + if (log is null || channel is null) return; + var (embed, content, attachment) = log; - private static async Task PublishLogAsync(EmbedLog? log, IMessageChannel? channel) + if (attachment is null) + await channel.SendMessageAsync(content, embed: embed.Build()); + else { - if (log is null || channel is null) return; - var (embed, content, attachment) = log; - - if (attachment is null) - await channel.SendMessageAsync(content, embed: embed.Build()); - else - { - await channel.SendFileAsync(GenerateStreamFromString(attachment), "Messages.md", - content, embed: embed.Build()); - } + await channel.SendFileAsync(GenerateStreamFromString(attachment), "Messages.md", + content, embed: embed.Build()); } + } - private async Task UpdateLogEmbedsAsync(MessageLog oldLog, IMessage message, - CancellationToken cancellationToken) + private async Task UpdateLogEmbedsAsync(MessageLog oldLog, IMessage message, + CancellationToken cancellationToken) + { + foreach (var embed in message.Embeds) { - foreach (var embed in message.Embeds) - { - oldLog.Embeds.Add(new Embed(embed)); - } - - await _db.SaveChangesAsync(cancellationToken); + oldLog.Embeds.Add(new Embed(embed)); } - private static async Task TryGetAuditLogDetails(IMessage? message, IGuild guild) - { - var entry = await TryGetAuditLogEntry(message, guild); - if (entry is null) return null; + await _db.SaveChangesAsync(cancellationToken); + } - var user = await guild.GetUserAsync(entry.User.Id); - return new ActionDetails(user, entry.Reason); - } + private static async Task TryGetAuditLogDetails(IMessage? message, IGuild guild) + { + var entry = await TryGetAuditLogEntry(message, guild); + if (entry is null) return null; - private static async Task TryGetAuditLogDetails( - int messageCount, IGuildChannel channel) - { - var entry = await TryGetAuditLogEntry(messageCount, channel); - if (entry is null) return null; + var user = await guild.GetUserAsync(entry.User.Id); + return new ActionDetails(user, entry.Reason); + } - var user = await channel.Guild.GetUserAsync(entry.User.Id); - return new ActionDetails(user, entry.Reason); - } + private static async Task TryGetAuditLogDetails( + int messageCount, IGuildChannel channel) + { + var entry = await TryGetAuditLogEntry(messageCount, channel); + if (entry is null) return null; - private async Task IsExcludedAsync(INestedChannel channel, IGuildUser user, - CancellationToken cancellationToken) - { - var guild = channel.Guild; + var user = await channel.Guild.GetUserAsync(entry.User.Id); + return new ActionDetails(user, entry.Reason); + } - var guildEntity = await _db.Guilds.FindByIdAsync(guild.Id, cancellationToken); - if (guildEntity is null || cancellationToken.IsCancellationRequested) return false; + private async Task IsExcludedAsync(INestedChannel channel, IGuildUser user, + CancellationToken cancellationToken) + { + var guild = channel.Guild; - return guildEntity.LoggingRules.LoggingExclusions.Any(e => e.Judge(channel, user)); - } + var guildEntity = await _db.Guilds.FindByIdAsync(guild.Id, cancellationToken); + if (guildEntity is null || cancellationToken.IsCancellationRequested) return false; - private async Task LogDeletionAsync(IMessageEntity message, ActionDetails? details, - CancellationToken cancellationToken) - { - var deleted = new MessageDeleteLog(message, details); + return guildEntity.LoggingRules.LoggingExclusions.Any(e => e.Judge(channel, user)); + } - _db.Add(deleted); - await _db.SaveChangesAsync(cancellationToken); + private async Task LogDeletionAsync(IMessageEntity message, ActionDetails? details, + CancellationToken cancellationToken) + { + var deleted = new MessageDeleteLog(message, details); - return deleted; - } + _db.Add(deleted); + await _db.SaveChangesAsync(cancellationToken); - private async Task LogDeletionAsync(IEnumerable messages, - IGuildChannel channel, ActionDetails? details, - CancellationToken cancellationToken) - { - var deleted = new MessagesDeleteLog(messages, channel, details); + return deleted; + } - _db.Add(deleted); - await _db.SaveChangesAsync(cancellationToken); + private async Task LogDeletionAsync(IEnumerable messages, + IGuildChannel channel, ActionDetails? details, + CancellationToken cancellationToken) + { + var deleted = new MessagesDeleteLog(messages, channel, details); - return deleted; - } + _db.Add(deleted); + await _db.SaveChangesAsync(cancellationToken); - private async Task LogDeletionAsync(SocketReaction reaction, ActionDetails? details, - CancellationToken cancellationToken) - { - var emote = await _db.TrackEmoteAsync(reaction.Emote, cancellationToken); - var deleted = new ReactionDeleteLog(emote, reaction, details); + return deleted; + } - _db.Add(deleted); - await _db.SaveChangesAsync(cancellationToken); + private async Task LogDeletionAsync(SocketReaction reaction, ActionDetails? details, + CancellationToken cancellationToken) + { + var emote = await _db.TrackEmoteAsync(reaction.Emote, cancellationToken); + var deleted = new ReactionDeleteLog(emote, reaction, details); - return deleted; - } + _db.Add(deleted); + await _db.SaveChangesAsync(cancellationToken); - private async Task AddDetailsAsync(EmbedBuilder embed, IMessageEntity log, - CancellationToken cancellationToken) - { - var deleted = await GetLatestMessage(log.MessageId, cancellationToken); - return deleted is null ? null : AddDetails(embed, deleted); - } + return deleted; + } - private async Task BuildLogAsync(DeleteLog log, CancellationToken cancellationToken) + private async Task AddDetailsAsync(EmbedBuilder embed, IMessageEntity log, + CancellationToken cancellationToken) + { + var deleted = await GetLatestMessage(log.MessageId, cancellationToken); + return deleted is null ? null : AddDetails(embed, deleted); + } + + private async Task BuildLogAsync(DeleteLog log, CancellationToken cancellationToken) + { + var embed = new EmbedBuilder() + .WithTitle($"{log.GetTitle()} Deleted") + .WithColor(Color.Red) + .WithCurrentTimestamp(); + + if (log.Action is not null) { - var embed = new EmbedBuilder() - .WithTitle($"{log.GetTitle()} Deleted") - .WithColor(Color.Red) - .WithCurrentTimestamp(); - - if (log.Action is not null) - { - embed - .AddField("Deleted by", log.GetModerator(), true) - .AddField("Deleted on", log.GetDate(), true) - .AddField("Reason", log.GetReason(), true); - } - - return log switch - { - MessagesDeleteLog messages => await AddDetailsAsync(embed, messages, cancellationToken), - MessageDeleteLog message => await AddDetailsAsync(embed, message, cancellationToken), - ReactionDeleteLog reaction => AddDetails(embed, reaction), - _ => throw new ArgumentOutOfRangeException( - nameof(log), log, "Invalid log type.") - }; + embed + .AddField("Deleted by", log.GetModerator(), true) + .AddField("Deleted on", log.GetDate(), true) + .AddField("Reason", log.GetReason(), true); } - private async Task AddDetailsAsync(EmbedBuilder embed, MessagesDeleteLog log, - CancellationToken cancellationToken) + return log switch { - var logs = await log.Messages - .ToAsyncEnumerable() - .SelectAwait(async m => await GetLatestMessage(m.MessageId, cancellationToken)) - .ToListAsync(cancellationToken); + MessagesDeleteLog messages => await AddDetailsAsync(embed, messages, cancellationToken), + MessageDeleteLog message => await AddDetailsAsync(embed, message, cancellationToken), + ReactionDeleteLog reaction => AddDetails(embed, reaction), + _ => throw new ArgumentOutOfRangeException( + nameof(log), log, "Invalid log type.") + }; + } - embed - .AddField("Created", log.LogDate.ToUniversalTimestamp(), true) - .AddField("Message Count", log.Messages.Count, true); + private async Task AddDetailsAsync(EmbedBuilder embed, MessagesDeleteLog log, + CancellationToken cancellationToken) + { + var logs = await log.Messages + .ToAsyncEnumerable() + .SelectAwait(async m => await GetLatestMessage(m.MessageId, cancellationToken)) + .ToListAsync(cancellationToken); - var messages = logs.OfType().GetDetails(); - return new EmbedLog(embed, Attachment: messages.ToString()); - } + embed + .AddField("Created", log.LogDate.ToUniversalTimestamp(), true) + .AddField("Message Count", log.Messages.Count, true); - private static async Task TryGetAuditLogEntry(IMessage? message, IGuild guild) - { - if (message is null) return null; + var messages = logs.OfType().GetDetails(); + return new EmbedLog(embed, Attachment: messages.ToString()); + } - var audits = await guild - .GetAuditLogsAsync(actionType: ActionType.MessageDeleted); + private static async Task TryGetAuditLogEntry(IMessage? message, IGuild guild) + { + if (message is null) return null; - return audits - .Where(e => DateTimeOffset.UtcNow - e.CreatedAt < TimeSpan.FromMinutes(1)) - .FirstOrDefault(e - => e.Data is MessageDeleteAuditLogData d - && d.Target.Id == message.Author.Id - && d.ChannelId == message.Channel.Id); - } + var audits = await guild + .GetAuditLogsAsync(actionType: ActionType.MessageDeleted); - private static async Task TryGetAuditLogEntry( - int messageCount, IGuildChannel channel) - { - var audits = await channel.Guild - .GetAuditLogsAsync(actionType: ActionType.MessageBulkDeleted); - - return audits - .Where(e => DateTimeOffset.UtcNow - e.CreatedAt < TimeSpan.FromMinutes(1)) - .FirstOrDefault(e - => e.Data is MessageBulkDeleteAuditLogData d - && d.MessageCount >= messageCount - && d.ChannelId == channel.Id); - } + return audits + .Where(e => DateTimeOffset.UtcNow - e.CreatedAt < TimeSpan.FromMinutes(1)) + .FirstOrDefault(e + => e.Data is MessageDeleteAuditLogData d + && d.Target.Id == message.Author.Id + && d.ChannelId == message.Channel.Id); + } - private async Task GetLoggingChannelAsync(LogType type, IGuild guild, - CancellationToken cancellationToken) - { - var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); + private static async Task TryGetAuditLogEntry( + int messageCount, IGuildChannel channel) + { + var audits = await channel.Guild + .GetAuditLogsAsync(actionType: ActionType.MessageBulkDeleted); + + return audits + .Where(e => DateTimeOffset.UtcNow - e.CreatedAt < TimeSpan.FromMinutes(1)) + .FirstOrDefault(e + => e.Data is MessageBulkDeleteAuditLogData d + && d.MessageCount >= messageCount + && d.ChannelId == channel.Id); + } - var channel = guildEntity.LoggingRules.LoggingChannels - .FirstOrDefault(r => r.Type == type); + private async Task GetLoggingChannelAsync(LogType type, IGuild guild, + CancellationToken cancellationToken) + { + var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); - if (channel is null) return null; - return await guild.GetTextChannelAsync(channel.ChannelId); - } + var channel = guildEntity.LoggingRules.LoggingChannels + .FirstOrDefault(r => r.Type == type); - private async Task LogMessageAsync(GuildUserEntity user, - IUserMessage message, MessageLog oldLog, - CancellationToken cancellationToken) - { - oldLog.UpdatedLog = await LogMessageAsync(user, message, cancellationToken); - await _db.SaveChangesAsync(cancellationToken); + if (channel is null) return null; + return await guild.GetTextChannelAsync(channel.ChannelId); + } - return oldLog; - } + private async Task LogMessageAsync(GuildUserEntity user, + IUserMessage message, MessageLog oldLog, + CancellationToken cancellationToken) + { + oldLog.UpdatedLog = await LogMessageAsync(user, message, cancellationToken); + await _db.SaveChangesAsync(cancellationToken); - private async Task LogMessageAsync(GuildUserEntity user, IUserMessage message, - CancellationToken cancellationToken) - { - var log = _db.Add(new MessageLog(user, message)).Entity; - await _db.SaveChangesAsync(cancellationToken); + return oldLog; + } - return log; - } + private async Task LogMessageAsync(GuildUserEntity user, IUserMessage message, + CancellationToken cancellationToken) + { + var log = _db.Add(new MessageLog(user, message)).Entity; + await _db.SaveChangesAsync(cancellationToken); - private async Task LogReactionAsync(GuildUserEntity user, SocketReaction reaction, - CancellationToken cancellationToken) - { - var emote = await _db.TrackEmoteAsync(reaction.Emote, cancellationToken); + return log; + } - var log = _db.Add(new ReactionLog(user, reaction, emote)).Entity; - await _db.SaveChangesAsync(cancellationToken); + private async Task LogReactionAsync(GuildUserEntity user, SocketReaction reaction, + CancellationToken cancellationToken) + { + var emote = await _db.TrackEmoteAsync(reaction.Emote, cancellationToken); - return log; - } + var log = _db.Add(new ReactionLog(user, reaction, emote)).Entity; + await _db.SaveChangesAsync(cancellationToken); - private ValueTask GetLatestMessage(ulong messageId, CancellationToken cancellationToken) - => GetLatestLogAsync(m => m.MessageId == messageId, cancellationToken); + return log; + } - private async ValueTask GetLatestLogAsync(Expression> filter, - CancellationToken cancellationToken) where T : class, ILog - { - return await _db.Set().AsQueryable() - .OrderByDescending(l => l.LogDate) - .FirstOrDefaultAsync(filter, cancellationToken); - } + private ValueTask GetLatestMessage(ulong messageId, CancellationToken cancellationToken) + => GetLatestLogAsync(m => m.MessageId == messageId, cancellationToken); - private record EmbedLog(EmbedBuilder Embed, string? Content = null, string? Attachment = null); + private async ValueTask GetLatestLogAsync(Expression> filter, + CancellationToken cancellationToken) where T : class, ILog + { + return await _db.Set().AsQueryable() + .OrderByDescending(l => l.LogDate) + .FirstOrDefaultAsync(filter, cancellationToken); } + + private record EmbedLog(EmbedBuilder Embed, string? Content = null, string? Attachment = null); } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/CensorExtensions.cs b/Zhongli.Services/Moderation/CensorExtensions.cs index 98297aa..a679b2b 100644 --- a/Zhongli.Services/Moderation/CensorExtensions.cs +++ b/Zhongli.Services/Moderation/CensorExtensions.cs @@ -4,15 +4,14 @@ using Zhongli.Data.Models.Moderation.Infractions.Censors; using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public static class CensorExtensions { - public static class CensorExtensions - { - public static Regex Regex(this ICensor censor) - => new(censor.Pattern, censor.Options, TimeSpan.FromSeconds(1)); + public static Regex Regex(this ICensor censor) + => new(censor.Pattern, censor.Options, TimeSpan.FromSeconds(1)); - public static string? CensoredMessage(this Censored censored) - => (censored.Trigger as Censor)?.Regex() - .Replace(censored.Content, m => Format.Bold(m.Value)); - } + public static string? CensoredMessage(this Censored censored) + => (censored.Trigger as Censor)?.Regex() + .Replace(censored.Content, m => Format.Bold(m.Value)); } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/InfractionType.cs b/Zhongli.Services/Moderation/InfractionType.cs index 4fc375b..0e5b713 100644 --- a/Zhongli.Services/Moderation/InfractionType.cs +++ b/Zhongli.Services/Moderation/InfractionType.cs @@ -1,14 +1,13 @@ -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public enum InfractionType { - public enum InfractionType - { - All, - Ban, - Censored, - Kick, - Mute, - Note, - Notice, - Warning - } + All, + Ban, + Censored, + Kick, + Mute, + Note, + Notice, + Warning } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/ModerationActionExtensions.cs b/Zhongli.Services/Moderation/ModerationActionExtensions.cs index e7ea12e..bab182d 100644 --- a/Zhongli.Services/Moderation/ModerationActionExtensions.cs +++ b/Zhongli.Services/Moderation/ModerationActionExtensions.cs @@ -3,26 +3,25 @@ using Zhongli.Data.Models.Moderation.Infractions; using static Zhongli.Services.Utilities.DateTimeExtensions; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public static class ModerationActionExtensions { - public static class ModerationActionExtensions - { - public static string GetDate(this ModerationAction action) - => action.Date.ToUniversalTimestamp(); + public static string GetDate(this ModerationAction action) + => action.Date.ToUniversalTimestamp(); - public static string GetDate(this IModerationAction action) - => action.Action?.GetDate() ?? "Unknown"; + public static string GetDate(this IModerationAction action) + => action.Action?.GetDate() ?? "Unknown"; - public static string GetModerator(this ModerationAction action) - => $"{Format.Bold(action.MentionUser())} ({action.UserId})"; + public static string GetModerator(this ModerationAction action) + => $"{Format.Bold(action.MentionUser())} ({action.UserId})"; - public static string GetModerator(this IModerationAction action) - => action.Action?.GetModerator() ?? "Unknown"; + public static string GetModerator(this IModerationAction action) + => action.Action?.GetModerator() ?? "Unknown"; - public static string GetReason(this ModerationAction action) - => Format.Bold(action.Reason ?? "No reason."); + public static string GetReason(this ModerationAction action) + => Format.Bold(action.Reason ?? "No reason."); - public static string GetReason(this IModerationAction action) - => action.Action?.GetReason() ?? "Unknown"; - } + public static string GetReason(this IModerationAction action) + => action.Action?.GetReason() ?? "Unknown"; } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/ModerationLoggingService.cs b/Zhongli.Services/Moderation/ModerationLoggingService.cs index 64543a9..35fd2f0 100644 --- a/Zhongli.Services/Moderation/ModerationLoggingService.cs +++ b/Zhongli.Services/Moderation/ModerationLoggingService.cs @@ -11,201 +11,200 @@ using Zhongli.Data.Models.Moderation.Infractions.Triggers; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public class ModerationLoggingService { - public class ModerationLoggingService + private readonly ZhongliContext _db; + + public ModerationLoggingService(ZhongliContext db) { _db = db; } + + public async Task PublishReprimandAsync(Reprimand reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default) { - private readonly ZhongliContext _db; + var embed = await UpdatedEmbedAsync(reprimand, details, cancellationToken); + await LogReprimandAsync(embed, reprimand, details, cancellationToken); + } - public ModerationLoggingService(ZhongliContext db) { _db = db; } + public async Task CreateEmbedAsync(ReprimandResult result, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var embed = CreateReprimandEmbed(details.User); - public async Task PublishReprimandAsync(Reprimand reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default) + await AddPrimaryAsync(embed, result.Primary, cancellationToken); + foreach (var secondary in result.Secondary) { - var embed = await UpdatedEmbedAsync(reprimand, details, cancellationToken); - await LogReprimandAsync(embed, reprimand, details, cancellationToken); + await AddSecondaryAsync(embed, secondary, cancellationToken); } - public async Task CreateEmbedAsync(ReprimandResult result, ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var embed = CreateReprimandEmbed(details.User); + return embed; + } - await AddPrimaryAsync(embed, result.Primary, cancellationToken); - foreach (var secondary in result.Secondary) - { - await AddSecondaryAsync(embed, secondary, cancellationToken); - } + public async Task UpdatedEmbedAsync(Reprimand reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var embed = CreateReprimandEmbed(details.User) + .WithTitle($"{reprimand.Status.Humanize()} {reprimand.GetTitle()}") + .WithColor(Color.Purple); - return embed; - } + AddReason(embed, reprimand.ModifiedAction); + await AddReprimandDetailsAsync(embed, reprimand, cancellationToken); - public async Task UpdatedEmbedAsync(Reprimand reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var embed = CreateReprimandEmbed(details.User) - .WithTitle($"{reprimand.Status.Humanize()} {reprimand.GetTitle()}") - .WithColor(Color.Purple); + return embed; + } - AddReason(embed, reprimand.ModifiedAction); - await AddReprimandDetailsAsync(embed, reprimand, cancellationToken); + public async Task PublishReprimandAsync(ReprimandResult reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var embed = await CreateEmbedAsync(reprimand, details, cancellationToken); + await LogReprimandAsync(embed, reprimand, details, cancellationToken); - return embed; - } + return reprimand; + } + + private static EmbedBuilder CreateReprimandEmbed(IUser user) => new EmbedBuilder() + .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) + .WithCurrentTimestamp(); - public async Task PublishReprimandAsync(ReprimandResult reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default) + private async Task AddPrimaryAsync(EmbedBuilder embed, Reprimand? reprimand, + CancellationToken cancellationToken) + { + if (reprimand is not null) { - var embed = await CreateEmbedAsync(reprimand, details, cancellationToken); - await LogReprimandAsync(embed, reprimand, details, cancellationToken); + embed + .WithTitle(reprimand.GetTitle()) + .WithColor(reprimand.GetColor()) + .WithDescription(reprimand.GetMessage()); - return reprimand; + AddReason(embed, reprimand.Action); + await AddReprimandDetailsAsync(embed, reprimand, cancellationToken); } + } - private static EmbedBuilder CreateReprimandEmbed(IUser user) => new EmbedBuilder() - .WithUserAsAuthor(user, AuthorOptions.IncludeId | AuthorOptions.UseThumbnail) - .WithCurrentTimestamp(); + private async Task AddReprimandDetailsAsync(EmbedBuilder embed, Reprimand reprimand, + CancellationToken cancellationToken) + { + embed + .AddField("Active", await GetTotalAsync(reprimand, false, cancellationToken), true) + .AddField("Total", await GetTotalAsync(reprimand, true, cancellationToken), true); - private async Task AddPrimaryAsync(EmbedBuilder embed, Reprimand? reprimand, - CancellationToken cancellationToken) - { - if (reprimand is not null) - { - embed - .WithTitle(reprimand.GetTitle()) - .WithColor(reprimand.GetColor()) - .WithDescription(reprimand.GetMessage()); + var trigger = await reprimand.GetTriggerAsync(_db, cancellationToken); + if (trigger is null) return; - AddReason(embed, reprimand.Action); - await AddReprimandDetailsAsync(embed, reprimand, cancellationToken); - } - } + embed + .AddField("Trigger", trigger.GetTriggerDetails(), true) + .AddField("Trigger ID", trigger.Id, true); + } - private async Task AddReprimandDetailsAsync(EmbedBuilder embed, Reprimand reprimand, - CancellationToken cancellationToken) + private async Task AddSecondaryAsync(EmbedBuilder embed, Reprimand? reprimand, + CancellationToken cancellationToken) + { + if (reprimand is not null) { - embed - .AddField("Active", await GetTotalAsync(reprimand, false, cancellationToken), true) - .AddField("Total", await GetTotalAsync(reprimand, true, cancellationToken), true); - - var trigger = await reprimand.GetTriggerAsync(_db, cancellationToken); - if (trigger is null) return; + var active = await GetTotalAsync(reprimand, false, cancellationToken); + var total = await GetTotalAsync(reprimand, true, cancellationToken); embed - .AddField("Trigger", trigger.GetTriggerDetails(), true) - .AddField("Trigger ID", trigger.Id, true); + .WithColor(reprimand.GetColor()) + .AddField($"{reprimand.GetTitle()} [{active}/{total}]", $"{reprimand.GetMessage()}"); } + } - private async Task AddSecondaryAsync(EmbedBuilder embed, Reprimand? reprimand, - CancellationToken cancellationToken) + private async Task LogReprimandAsync(EmbedBuilder embed, + ReprimandResult result, ReprimandDetails details, + CancellationToken cancellationToken) + { + var (user, moderator, _, _) = details; + var reprimand = result.Primary; + var guild = await reprimand.GetGuildAsync(_db, cancellationToken); + var options = guild.ModerationRules.Options; + + if (!options.HasFlag(ReprimandOptions.Verbose) + && reprimand.Status is ReprimandStatus.Added + && reprimand.TriggerId is not null) { - if (reprimand is not null) - { - var active = await GetTotalAsync(reprimand, false, cancellationToken); - var total = await GetTotalAsync(reprimand, true, cancellationToken); - - embed - .WithColor(reprimand.GetColor()) - .AddField($"{reprimand.GetTitle()} [{active}/{total}]", $"{reprimand.GetMessage()}"); - } + var trigger = await reprimand.GetTriggerAsync(_db, cancellationToken); + if (trigger is ReprimandTrigger { Source: TriggerSource.Warning or TriggerSource.Notice }) + return; } - private async Task LogReprimandAsync(EmbedBuilder embed, - ReprimandResult result, ReprimandDetails details, - CancellationToken cancellationToken) - { - var (user, moderator, _, _) = details; - var reprimand = result.Primary; - var guild = await reprimand.GetGuildAsync(_db, cancellationToken); - var options = guild.ModerationRules.Options; - - if (!options.HasFlag(ReprimandOptions.Verbose) - && reprimand.Status is ReprimandStatus.Added - && reprimand.TriggerId is not null) - { - var trigger = await reprimand.GetTriggerAsync(_db, cancellationToken); - if (trigger is ReprimandTrigger { Source: TriggerSource.Warning or TriggerSource.Notice }) - return; - } - - var type = reprimand.GetReprimandType(); - var channel = await GetLoggingChannelAsync(type, moderator.Guild, cancellationToken); - if (channel is null) return; + var type = reprimand.GetReprimandType(); + var channel = await GetLoggingChannelAsync(type, moderator.Guild, cancellationToken); + if (channel is null) return; - AddReprimandAuthor(moderator, false, embed); - _ = channel.SendMessageAsync(embed: embed.Build()); + AddReprimandAuthor(moderator, false, embed); + _ = channel.SendMessageAsync(embed: embed.Build()); - if (options.HasFlag(ReprimandOptions.NotifyUser) - && reprimand is not Note - && reprimand.IsIncluded(guild.LoggingRules.NotifyReprimands) - && reprimand.Status - is ReprimandStatus.Added - or ReprimandStatus.Expired - or ReprimandStatus.Updated) + if (options.HasFlag(ReprimandOptions.NotifyUser) + && reprimand is not Note + && reprimand.IsIncluded(guild.LoggingRules.NotifyReprimands) + && reprimand.Status + is ReprimandStatus.Added + or ReprimandStatus.Expired + or ReprimandStatus.Updated) + { + AddReprimandAuthor(moderator, options.HasFlag(ReprimandOptions.Anonymous), embed); + if (reprimand.IsIncluded(guild.LoggingRules.ShowAppealOnReprimands)) { - AddReprimandAuthor(moderator, options.HasFlag(ReprimandOptions.Anonymous), embed); - if (reprimand.IsIncluded(guild.LoggingRules.ShowAppealOnReprimands)) - { - var appealMessage = guild.LoggingRules.ReprimandAppealMessage; - if (!string.IsNullOrWhiteSpace(appealMessage)) - embed.AddField("Appeal", appealMessage); - } - - var dm = await user.GetOrCreateDMChannelAsync(); - _ = dm?.SendMessageAsync(embed: embed.Build()); + var appealMessage = guild.LoggingRules.ReprimandAppealMessage; + if (!string.IsNullOrWhiteSpace(appealMessage)) + embed.AddField("Appeal", appealMessage); } - } - private async Task GetLoggingChannelAsync(ReprimandType type, IGuild guild, - CancellationToken cancellationToken) - { - var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); + var dm = await user.GetOrCreateDMChannelAsync(); + _ = dm?.SendMessageAsync(embed: embed.Build()); + } + } - var channel = guildEntity.ModerationRules.LoggingChannels - .FirstOrDefault(r => r.Type == type); + private async Task GetLoggingChannelAsync(ReprimandType type, IGuild guild, + CancellationToken cancellationToken) + { + var guildEntity = await _db.Guilds.TrackGuildAsync(guild, cancellationToken); - if (channel is null) return null; - return await guild.GetTextChannelAsync(channel.ChannelId); - } + var channel = guildEntity.ModerationRules.LoggingChannels + .FirstOrDefault(r => r.Type == type); - private async ValueTask GetTotalAsync(Reprimand reprimand, - bool countHidden = true, - CancellationToken cancellationToken = default) - { - var user = await reprimand.GetUserAsync(_db, cancellationToken); + if (channel is null) return null; + return await guild.GetTextChannelAsync(channel.ChannelId); + } - return reprimand switch - { - Ban => user.HistoryCount(countHidden), - Censored => user.HistoryCount(countHidden), - Kick => user.HistoryCount(countHidden), - Mute => user.HistoryCount(countHidden), - Note => user.HistoryCount(countHidden), - Notice => user.HistoryCount(countHidden), - Warning => user.WarningCount(countHidden), - - _ => throw new ArgumentOutOfRangeException( - nameof(reprimand), reprimand, "An unknown reprimand was given.") - }; - } + private async ValueTask GetTotalAsync(Reprimand reprimand, + bool countHidden = true, + CancellationToken cancellationToken = default) + { + var user = await reprimand.GetUserAsync(_db, cancellationToken); - private static void AddReason(EmbedBuilder embed, ModerationAction? action) + return reprimand switch { - if (string.IsNullOrWhiteSpace(action?.Reason)) return; + Ban => user.HistoryCount(countHidden), + Censored => user.HistoryCount(countHidden), + Kick => user.HistoryCount(countHidden), + Mute => user.HistoryCount(countHidden), + Note => user.HistoryCount(countHidden), + Notice => user.HistoryCount(countHidden), + Warning => user.WarningCount(countHidden), + + _ => throw new ArgumentOutOfRangeException( + nameof(reprimand), reprimand, "An unknown reprimand was given.") + }; + } - var reason = action.Reason.Length > 1024 - ? $"{action.Reason[..1021]}..." - : action.Reason; + private static void AddReason(EmbedBuilder embed, ModerationAction? action) + { + if (string.IsNullOrWhiteSpace(action?.Reason)) return; - embed.AddField("Reason", reason); - } + var reason = action.Reason.Length > 1024 + ? $"{action.Reason[..1021]}..." + : action.Reason; - private static void AddReprimandAuthor(IGuildUser moderator, bool isAnonymous, EmbedBuilder embed) - { - if (isAnonymous) - embed.WithGuildAsAuthor(moderator.Guild, AuthorOptions.UseFooter | AuthorOptions.Requested); - else - embed.WithUserAsAuthor(moderator, AuthorOptions.UseFooter | AuthorOptions.Requested); - } + embed.AddField("Reason", reason); + } + + private static void AddReprimandAuthor(IGuildUser moderator, bool isAnonymous, EmbedBuilder embed) + { + if (isAnonymous) + embed.WithGuildAsAuthor(moderator.Guild, AuthorOptions.UseFooter | AuthorOptions.Requested); + else + embed.WithUserAsAuthor(moderator, AuthorOptions.UseFooter | AuthorOptions.Requested); } } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/ModerationService.cs b/Zhongli.Services/Moderation/ModerationService.cs index 2d6b493..e3db3a4 100644 --- a/Zhongli.Services/Moderation/ModerationService.cs +++ b/Zhongli.Services/Moderation/ModerationService.cs @@ -18,406 +18,405 @@ using Zhongli.Services.Utilities; using IBan = Zhongli.Data.Models.Moderation.Infractions.IBan; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public class ModerationService : ExpirableService { - public class ModerationService : ExpirableService + private readonly DiscordSocketClient _client; + private readonly ModerationLoggingService _logging; + private readonly ZhongliContext _db; + + public ModerationService(ZhongliContext db, DiscordSocketClient client, ModerationLoggingService logging) + : base(db) { - private readonly DiscordSocketClient _client; - private readonly ModerationLoggingService _logging; - private readonly ZhongliContext _db; + _client = client; + _logging = logging; + _db = db; + } - public ModerationService(ZhongliContext db, DiscordSocketClient client, ModerationLoggingService logging) - : base(db) - { - _client = client; - _logging = logging; - _db = db; - } + public async Task CensorAsync(SocketMessage message, TimeSpan? length, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var censored = new Censored(message.Content, length, details); - public async Task CensorAsync(SocketMessage message, TimeSpan? length, ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var censored = new Censored(message.Content, length, details); + _db.Add(censored); + await _db.SaveChangesAsync(cancellationToken); - _db.Add(censored); - await _db.SaveChangesAsync(cancellationToken); + if (details.Trigger is Censor censor) + { + if (!censor.Silent) await message.DeleteAsync(); - if (details.Trigger is Censor censor) + var triggerCount = await censored.CountAsync(censor, _db, false, cancellationToken); + if (censor.IsTriggered(triggerCount)) { - if (!censor.Silent) await message.DeleteAsync(); + var triggerDetails = new ReprimandDetails( + details.User, details.Moderator, $"[Reprimand Triggered] at {triggerCount}", censor); - var triggerCount = await censored.CountAsync(censor, _db, false, cancellationToken); - if (censor.IsTriggered(triggerCount)) - { - var triggerDetails = new ReprimandDetails( - details.User, details.Moderator, $"[Reprimand Triggered] at {triggerCount}", censor); - - await ReprimandAsync(censor.Reprimand, triggerDetails, cancellationToken); - } + await ReprimandAsync(censor.Reprimand, triggerDetails, cancellationToken); } - - await PublishReprimandAsync(censored, details, cancellationToken); } - public async Task ConfigureMuteRoleAsync(IGuild guild, IRole? role) - { - var guildEntity = await _db.Guilds.TrackGuildAsync(guild); - var rules = guildEntity.ModerationRules; - var roleId = rules.MuteRoleId; + await PublishReprimandAsync(censored, details, cancellationToken); + } - role ??= guild.Roles.FirstOrDefault(r => r.Id == roleId); - role ??= guild.Roles.FirstOrDefault(r => r.Name == "Muted"); - role ??= await guild.CreateRoleAsync("Muted", isMentionable: false); + public async Task ConfigureMuteRoleAsync(IGuild guild, IRole? role) + { + var guildEntity = await _db.Guilds.TrackGuildAsync(guild); + var rules = guildEntity.ModerationRules; + var roleId = rules.MuteRoleId; - rules.MuteRoleId = role.Id; - await _db.SaveChangesAsync(); + role ??= guild.Roles.FirstOrDefault(r => r.Id == roleId); + role ??= guild.Roles.FirstOrDefault(r => r.Name == "Muted"); + role ??= await guild.CreateRoleAsync("Muted", isMentionable: false); - var permissions = new OverwritePermissions( - addReactions: PermValue.Deny, - sendMessages: PermValue.Deny, - speak: PermValue.Deny, - stream: PermValue.Deny); + rules.MuteRoleId = role.Id; + await _db.SaveChangesAsync(); - foreach (var channel in await guild.GetChannelsAsync()) - { - await channel.AddPermissionOverwriteAsync(role, permissions); - } - } + var permissions = new OverwritePermissions( + addReactions: PermValue.Deny, + sendMessages: PermValue.Deny, + speak: PermValue.Deny, + stream: PermValue.Deny); - public async Task DeleteReprimandAsync(Reprimand reprimand, ReprimandDetails? details, - CancellationToken cancellationToken = default) + foreach (var channel in await guild.GetChannelsAsync()) { - if (details is not null) - await UpdateReprimandAsync(reprimand, details, ReprimandStatus.Deleted, cancellationToken); - - if (reprimand.Action is not null) - _db.Remove(reprimand.Action); - - if (reprimand.ModifiedAction is not null) - _db.Remove(reprimand.ModifiedAction); - - _db.Remove(reprimand); - await _db.SaveChangesAsync(cancellationToken); + await channel.AddPermissionOverwriteAsync(role, permissions); } + } - public async Task DeleteTriggerAsync(Trigger trigger, IGuildUser moderator, bool silent) - { - var reprimands = await _db.Set().ToAsyncEnumerable() - .Where(r => r.TriggerId == trigger.Id) - .ToListAsync(); + public async Task DeleteReprimandAsync(Reprimand reprimand, ReprimandDetails? details, + CancellationToken cancellationToken = default) + { + if (details is not null) + await UpdateReprimandAsync(reprimand, details, ReprimandStatus.Deleted, cancellationToken); - foreach (var reprimand in reprimands) - { - await DeleteReprimandAsync(reprimand, silent ? null : GetModified(reprimand)); + if (reprimand.Action is not null) + _db.Remove(reprimand.Action); - ReprimandDetails GetModified(IUserEntity r) - { - var user = _client.GetUser(r.UserId); - return new ReprimandDetails(user, moderator, "[Deleted Trigger]"); - } - } + if (reprimand.ModifiedAction is not null) + _db.Remove(reprimand.ModifiedAction); - _db.Remove(trigger); - await _db.SaveChangesAsync(); - } + _db.Remove(reprimand); + await _db.SaveChangesAsync(cancellationToken); + } - public Task HideReprimandAsync(Reprimand reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default) - => UpdateReprimandAsync(reprimand, details, ReprimandStatus.Hidden, cancellationToken); + public async Task DeleteTriggerAsync(Trigger trigger, IGuildUser moderator, bool silent) + { + var reprimands = await _db.Set().ToAsyncEnumerable() + .Where(r => r.TriggerId == trigger.Id) + .ToListAsync(); - public async Task ToggleTriggerAsync(Trigger trigger, IGuildUser moderator, bool? state) + foreach (var reprimand in reprimands) { - trigger.IsActive = state ?? !trigger.IsActive; - trigger.Action = new ModerationAction(moderator); + await DeleteReprimandAsync(reprimand, silent ? null : GetModified(reprimand)); - await _db.SaveChangesAsync(); + ReprimandDetails GetModified(IUserEntity r) + { + var user = _client.GetUser(r.UserId); + return new ReprimandDetails(user, moderator, "[Deleted Trigger]"); + } } - public Task UpdateReprimandAsync(Reprimand reprimand, ReprimandDetails details, - CancellationToken cancellationToken = default) - => UpdateReprimandAsync(reprimand, details, ReprimandStatus.Updated, cancellationToken); + _db.Remove(trigger); + await _db.SaveChangesAsync(); + } - public async Task TryUnbanAsync(ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var activeBan = await _db.BanHistory - .FirstOrDefaultAsync(m => m.IsActive() - && m.UserId == details.User.Id - && m.GuildId == details.Guild.Id, - cancellationToken); - - if (activeBan is null) - await details.Guild.RemoveBanAsync(details.User); - else - await ExpireBanAsync(activeBan, cancellationToken); - - return activeBan; - } + public Task HideReprimandAsync(Reprimand reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default) + => UpdateReprimandAsync(reprimand, details, ReprimandStatus.Hidden, cancellationToken); - public async Task TryUnmuteAsync(ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var activeMute = await _db.MuteHistory - .FirstOrDefaultAsync(m => m.IsActive() - && m.UserId == details.User.Id - && m.GuildId == details.Guild.Id, - cancellationToken); + public async Task ToggleTriggerAsync(Trigger trigger, IGuildUser moderator, bool? state) + { + trigger.IsActive = state ?? !trigger.IsActive; + trigger.Action = new ModerationAction(moderator); - if (activeMute is not null) await ExpireReprimandAsync(activeMute, cancellationToken); - await EndMuteAsync(await details.GetUserAsync()); + await _db.SaveChangesAsync(); + } - return activeMute; - } + public Task UpdateReprimandAsync(Reprimand reprimand, ReprimandDetails details, + CancellationToken cancellationToken = default) + => UpdateReprimandAsync(reprimand, details, ReprimandStatus.Updated, cancellationToken); - public async Task TryBanAsync(uint? deleteDays, TimeSpan? length, ReprimandDetails details, - CancellationToken cancellationToken = default) - { - try - { - var user = details.User; - var days = deleteDays ?? 1; + public async Task TryUnbanAsync(ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var activeBan = await _db.BanHistory + .FirstOrDefaultAsync(m => m.IsActive() + && m.UserId == details.User.Id + && m.GuildId == details.Guild.Id, + cancellationToken); + + if (activeBan is null) + await details.Guild.RemoveBanAsync(details.User); + else + await ExpireBanAsync(activeBan, cancellationToken); + + return activeBan; + } - var ban = _db.Add(new Ban(days, length, details)).Entity; - await _db.SaveChangesAsync(cancellationToken); + public async Task TryUnmuteAsync(ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var activeMute = await _db.MuteHistory + .FirstOrDefaultAsync(m => m.IsActive() + && m.UserId == details.User.Id + && m.GuildId == details.Guild.Id, + cancellationToken); - var result = await PublishReprimandAsync(ban, details, cancellationToken); - await details.Guild.AddBanAsync(user, (int) days, details.Reason); + if (activeMute is not null) await ExpireReprimandAsync(activeMute, cancellationToken); + await EndMuteAsync(await details.GetUserAsync()); - return result; - } - catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) - { - return null; - } - } + return activeMute; + } - public async Task TryKickAsync(ReprimandDetails details, - CancellationToken cancellationToken = default) + public async Task TryBanAsync(uint? deleteDays, TimeSpan? length, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + try { - try - { - var user = await details.GetUserAsync(); - if (user is null) return null; + var user = details.User; + var days = deleteDays ?? 1; - await user.KickAsync(details.Reason); + var ban = _db.Add(new Ban(days, length, details)).Entity; + await _db.SaveChangesAsync(cancellationToken); - var kick = _db.Add(new Kick(details)).Entity; - await _db.SaveChangesAsync(cancellationToken); + var result = await PublishReprimandAsync(ban, details, cancellationToken); + await details.Guild.AddBanAsync(user, (int) days, details.Reason); - return await PublishReprimandAsync(kick, details, cancellationToken); - } - catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) - { - return null; - } + return result; } - - public async Task TryMuteAsync(TimeSpan? length, ReprimandDetails details, - CancellationToken cancellationToken = default) + catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) { - var activeMute = await _db.MuteHistory - .FirstOrDefaultAsync(m => m.IsActive() - && m.UserId == details.User.Id - && m.GuildId == details.Guild.Id, - cancellationToken); + return null; + } + } + public async Task TryKickAsync(ReprimandDetails details, + CancellationToken cancellationToken = default) + { + try + { var user = await details.GetUserAsync(); if (user is null) return null; - var guildEntity = await _db.Guilds.TrackGuildAsync(user.Guild, cancellationToken); + await user.KickAsync(details.Reason); - var muteRole = guildEntity.ModerationRules.MuteRoleId; - if (muteRole is null) return null; + var kick = _db.Add(new Kick(details)).Entity; + await _db.SaveChangesAsync(cancellationToken); - try - { - await user.AddRoleAsync(muteRole.Value); - if (user.VoiceChannel is not null) - await user.ModifyAsync(u => u.Mute = true); + return await PublishReprimandAsync(kick, details, cancellationToken); + } + catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) + { + return null; + } + } - if (activeMute is not null) - { - if (!guildEntity.ModerationRules.ReplaceMutes) - return null; + public async Task TryMuteAsync(TimeSpan? length, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var activeMute = await _db.MuteHistory + .FirstOrDefaultAsync(m => m.IsActive() + && m.UserId == details.User.Id + && m.GuildId == details.Guild.Id, + cancellationToken); - await ExpireReprimandAsync(activeMute, cancellationToken); - } + var user = await details.GetUserAsync(); + if (user is null) return null; - var mute = _db.Add(new Mute(length, details)).Entity; - await _db.SaveChangesAsync(cancellationToken); + var guildEntity = await _db.Guilds.TrackGuildAsync(user.Guild, cancellationToken); - return await PublishReprimandAsync(mute, details, cancellationToken); - } - catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) + var muteRole = guildEntity.ModerationRules.MuteRoleId; + if (muteRole is null) return null; + + try + { + await user.AddRoleAsync(muteRole.Value); + if (user.VoiceChannel is not null) + await user.ModifyAsync(u => u.Mute = true); + + if (activeMute is not null) { - return null; + if (!guildEntity.ModerationRules.ReplaceMutes) + return null; + + await ExpireReprimandAsync(activeMute, cancellationToken); } - } - public async Task NoteAsync(ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var note = _db.Add(new Note(details)).Entity; + var mute = _db.Add(new Mute(length, details)).Entity; await _db.SaveChangesAsync(cancellationToken); - return await PublishReprimandAsync(note, details, cancellationToken); + return await PublishReprimandAsync(mute, details, cancellationToken); } - - public async Task NoticeAsync(ReprimandDetails details, - CancellationToken cancellationToken = default) + catch (HttpException e) when (e.HttpCode == HttpStatusCode.Forbidden) { - var guild = await details.GetGuildAsync(_db, cancellationToken); - var notice = new Notice(guild.ModerationRules.NoticeExpiryLength, details); + return null; + } + } - _db.Add(notice); - await _db.SaveChangesAsync(cancellationToken); + public async Task NoteAsync(ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var note = _db.Add(new Note(details)).Entity; + await _db.SaveChangesAsync(cancellationToken); - return await PublishReprimandAsync(notice, details, cancellationToken); - } + return await PublishReprimandAsync(note, details, cancellationToken); + } - public async Task WarnAsync(uint amount, ReprimandDetails details, - CancellationToken cancellationToken = default) - { - var guild = await details.GetGuildAsync(_db, cancellationToken); - var warning = new Warning(amount, guild.ModerationRules.WarningExpiryLength, details); + public async Task NoticeAsync(ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var guild = await details.GetGuildAsync(_db, cancellationToken); + var notice = new Notice(guild.ModerationRules.NoticeExpiryLength, details); - _db.Add(warning); - await _db.SaveChangesAsync(cancellationToken); + _db.Add(notice); + await _db.SaveChangesAsync(cancellationToken); - return await PublishReprimandAsync(warning, details, cancellationToken); - } + return await PublishReprimandAsync(notice, details, cancellationToken); + } - protected override async Task OnExpiredEntity(ExpirableReprimand reprimand, - CancellationToken cancellationToken) - { - await (reprimand switch - { - Ban ban => ExpireBanAsync(ban, cancellationToken), - Mute mute => ExpireMuteAsync(mute, cancellationToken), - _ => ExpireReprimandAsync(reprimand, cancellationToken) - }); - } + public async Task WarnAsync(uint amount, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var guild = await details.GetGuildAsync(_db, cancellationToken); + var warning = new Warning(amount, guild.ModerationRules.WarningExpiryLength, details); - private static bool TryGetTriggerSource(Reprimand reprimand, out TriggerSource? source) - { - source = reprimand switch - { - Censored => TriggerSource.Censored, - Notice => TriggerSource.Notice, - Warning => TriggerSource.Warning, - _ => null - }; + _db.Add(warning); + await _db.SaveChangesAsync(cancellationToken); - return source is not null; - } + return await PublishReprimandAsync(warning, details, cancellationToken); + } - private async Task EndMuteAsync(IGuildUser? user) + protected override async Task OnExpiredEntity(ExpirableReprimand reprimand, + CancellationToken cancellationToken) + { + await (reprimand switch { - if (user is null) return; - var guildEntity = await _db.Guilds.TrackGuildAsync(user.Guild); + Ban ban => ExpireBanAsync(ban, cancellationToken), + Mute mute => ExpireMuteAsync(mute, cancellationToken), + _ => ExpireReprimandAsync(reprimand, cancellationToken) + }); + } - if (guildEntity.ModerationRules.MuteRoleId is not null) - await user.RemoveRoleAsync(guildEntity.ModerationRules.MuteRoleId.Value); + private static bool TryGetTriggerSource(Reprimand reprimand, out TriggerSource? source) + { + source = reprimand switch + { + Censored => TriggerSource.Censored, + Notice => TriggerSource.Notice, + Warning => TriggerSource.Warning, + _ => null + }; - if (user.VoiceChannel is not null) - await user.ModifyAsync(u => u.Mute = false); - } + return source is not null; + } - private async Task ExpireBanAsync(ExpirableReprimand ban, CancellationToken cancellationToken) - { - await ExpireReprimandAsync(ban, cancellationToken); + private async Task EndMuteAsync(IGuildUser? user) + { + if (user is null) return; + var guildEntity = await _db.Guilds.TrackGuildAsync(user.Guild); - var guild = _client.GetGuild(ban.GuildId); - _ = guild.RemoveBanAsync(ban.UserId); - } + if (guildEntity.ModerationRules.MuteRoleId is not null) + await user.RemoveRoleAsync(guildEntity.ModerationRules.MuteRoleId.Value); - private async Task ExpireMuteAsync(ExpirableReprimand mute, CancellationToken cancellationToken) - { - await ExpireReprimandAsync(mute, cancellationToken); + if (user.VoiceChannel is not null) + await user.ModifyAsync(u => u.Mute = false); + } - var guild = _client.GetGuild(mute.GuildId); - var user = guild.GetUser(mute.UserId); + private async Task ExpireBanAsync(ExpirableReprimand ban, CancellationToken cancellationToken) + { + await ExpireReprimandAsync(ban, cancellationToken); - _ = EndMuteAsync(user); - } + var guild = _client.GetGuild(ban.GuildId); + _ = guild.RemoveBanAsync(ban.UserId); + } - private async Task ExpireReprimandAsync(ExpirableReprimand reprimand, CancellationToken cancellationToken) - { - var guild = _client.GetGuild(reprimand.GuildId); - var user = await _client.Rest.GetUserAsync(reprimand.UserId); - var moderator = guild.CurrentUser; - var details = new ReprimandDetails(user, moderator, "[Reprimand Expired]"); - - reprimand.EndedAt = DateTimeOffset.UtcNow; - reprimand.ExpireAt = DateTimeOffset.UtcNow; - await UpdateReprimandAsync(reprimand, details, ReprimandStatus.Expired, cancellationToken); - } + private async Task ExpireMuteAsync(ExpirableReprimand mute, CancellationToken cancellationToken) + { + await ExpireReprimandAsync(mute, cancellationToken); - private async Task UpdateReprimandAsync(Reprimand reprimand, ReprimandDetails details, - ReprimandStatus status, CancellationToken cancellationToken) - { - reprimand.Status = status; - reprimand.ModifiedAction = details; + var guild = _client.GetGuild(mute.GuildId); + var user = guild.GetUser(mute.UserId); - await _db.SaveChangesAsync(cancellationToken); - await _logging.PublishReprimandAsync(reprimand, details, cancellationToken); - } + _ = EndMuteAsync(user); + } - private async Task ReprimandAsync(ReprimandAction? reprimand, ReprimandDetails details, - CancellationToken cancellationToken) => reprimand switch - { - IBan b => await TryBanAsync(b.DeleteDays, b.Length, details, cancellationToken), - IKick => await TryKickAsync(details, cancellationToken), - IMute m => await TryMuteAsync(m.Length, details, cancellationToken), - IWarning w => await WarnAsync(w.Count, details, cancellationToken), - INotice => await NoticeAsync(details, cancellationToken), - INote => await NoteAsync(details, cancellationToken), - _ => null - }; + private async Task ExpireReprimandAsync(ExpirableReprimand reprimand, CancellationToken cancellationToken) + { + var guild = _client.GetGuild(reprimand.GuildId); + var user = await _client.Rest.GetUserAsync(reprimand.UserId); + var moderator = guild.CurrentUser; + var details = new ReprimandDetails(user, moderator, "[Reprimand Expired]"); + + reprimand.EndedAt = DateTimeOffset.UtcNow; + reprimand.ExpireAt = DateTimeOffset.UtcNow; + await UpdateReprimandAsync(reprimand, details, ReprimandStatus.Expired, cancellationToken); + } - private async Task PublishReprimandAsync(T reprimand, ReprimandDetails details, - CancellationToken cancellationToken) where T : Reprimand - { - if (reprimand is ExpirableReprimand expirable) - EnqueueExpirableEntity(expirable, cancellationToken); + private async Task UpdateReprimandAsync(Reprimand reprimand, ReprimandDetails details, + ReprimandStatus status, CancellationToken cancellationToken) + { + reprimand.Status = status; + reprimand.ModifiedAction = details; - var result = await TriggerReprimand(details, reprimand, cancellationToken); - return await _logging.PublishReprimandAsync(result, details, cancellationToken); - } + await _db.SaveChangesAsync(cancellationToken); + await _logging.PublishReprimandAsync(reprimand, details, cancellationToken); + } - private async Task TriggerReprimand(ReprimandDetails details, T reprimand, - CancellationToken cancellationToken) where T : Reprimand - { - if (!TryGetTriggerSource(reprimand, out var source)) - return reprimand; + private async Task ReprimandAsync(ReprimandAction? reprimand, ReprimandDetails details, + CancellationToken cancellationToken) => reprimand switch + { + IBan b => await TryBanAsync(b.DeleteDays, b.Length, details, cancellationToken), + IKick => await TryKickAsync(details, cancellationToken), + IMute m => await TryMuteAsync(m.Length, details, cancellationToken), + IWarning w => await WarnAsync(w.Count, details, cancellationToken), + INotice => await NoticeAsync(details, cancellationToken), + INote => await NoteAsync(details, cancellationToken), + _ => null + }; + + private async Task PublishReprimandAsync(T reprimand, ReprimandDetails details, + CancellationToken cancellationToken) where T : Reprimand + { + if (reprimand is ExpirableReprimand expirable) + EnqueueExpirableEntity(expirable, cancellationToken); - var count = await reprimand.CountAsync(_db, false, cancellationToken); - var trigger = await GetCountTriggerAsync(reprimand, count, source!.Value, cancellationToken); - if (trigger is null) return new ReprimandResult(reprimand); + var result = await TriggerReprimand(details, reprimand, cancellationToken); + return await _logging.PublishReprimandAsync(result, details, cancellationToken); + } - var (_, moderator, _, _) = details; - var currentUser = await moderator.Guild.GetCurrentUserAsync(); + private async Task TriggerReprimand(ReprimandDetails details, T reprimand, + CancellationToken cancellationToken) where T : Reprimand + { + if (!TryGetTriggerSource(reprimand, out var source)) + return reprimand; - var countDetails = new ReprimandDetails( - details.User, currentUser, $"[Reprimand Count Triggered] at {count}", trigger); - var secondary = await ReprimandAsync(trigger.Reprimand, countDetails, cancellationToken); + var count = await reprimand.CountAsync(_db, false, cancellationToken); + var trigger = await GetCountTriggerAsync(reprimand, count, source!.Value, cancellationToken); + if (trigger is null) return new ReprimandResult(reprimand); - return new ReprimandResult(reprimand, secondary); - } + var (_, moderator, _, _) = details; + var currentUser = await moderator.Guild.GetCurrentUserAsync(); - private async Task GetCountTriggerAsync(Reprimand reprimand, uint count, - TriggerSource source, CancellationToken cancellationToken) - { - var user = await reprimand.GetUserAsync(_db, cancellationToken); - var rules = user.Guild.ModerationRules; - - return rules.Triggers - .OfType() - .Where(t => t.IsActive) - .Where(t => t.Source == source) - .Where(t => t.IsTriggered(count)) - .OrderByDescending(t => t.Amount) - .FirstOrDefault(); - } + var countDetails = new ReprimandDetails( + details.User, currentUser, $"[Reprimand Count Triggered] at {count}", trigger); + var secondary = await ReprimandAsync(trigger.Reprimand, countDetails, cancellationToken); + + return new ReprimandResult(reprimand, secondary); + } + + private async Task GetCountTriggerAsync(Reprimand reprimand, uint count, + TriggerSource source, CancellationToken cancellationToken) + { + var user = await reprimand.GetUserAsync(_db, cancellationToken); + var rules = user.Guild.ModerationRules; + + return rules.Triggers + .OfType() + .Where(t => t.IsActive) + .Where(t => t.Source == source) + .Where(t => t.IsTriggered(count)) + .OrderByDescending(t => t.Amount) + .FirstOrDefault(); } } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/ReprimandExtensions.cs b/Zhongli.Services/Moderation/ReprimandExtensions.cs index 7662e0f..50b9b37 100644 --- a/Zhongli.Services/Moderation/ReprimandExtensions.cs +++ b/Zhongli.Services/Moderation/ReprimandExtensions.cs @@ -16,273 +16,265 @@ using Zhongli.Data.Models.Moderation.Infractions.Triggers; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public static class ReprimandExtensions { - public static class ReprimandExtensions + public static bool IsActive(this IExpirable expirable) + => expirable.EndedAt is null || expirable.ExpireAt >= DateTimeOffset.UtcNow; + + public static bool IsIncluded(this Reprimand reprimand, ReprimandNoticeType type) { - public static bool IsActive(this IExpirable expirable) - => expirable.EndedAt is null || expirable.ExpireAt >= DateTimeOffset.UtcNow; + if (type == ReprimandNoticeType.All) + return true; - public static bool IsIncluded(this Reprimand reprimand, ReprimandNoticeType type) + return reprimand switch { - if (type == ReprimandNoticeType.All) - return true; + Ban => type.HasFlag(ReprimandNoticeType.Ban), + Censored => type.HasFlag(ReprimandNoticeType.Censor), + Kick => type.HasFlag(ReprimandNoticeType.Kick), + Mute => type.HasFlag(ReprimandNoticeType.Mute), + Notice => type.HasFlag(ReprimandNoticeType.Notice), + Warning => type.HasFlag(ReprimandNoticeType.Warning), + _ => false + }; + } - return reprimand switch - { - Ban => type.HasFlag(ReprimandNoticeType.Ban), - Censored => type.HasFlag(ReprimandNoticeType.Censor), - Kick => type.HasFlag(ReprimandNoticeType.Kick), - Mute => type.HasFlag(ReprimandNoticeType.Mute), - Notice => type.HasFlag(ReprimandNoticeType.Notice), - Warning => type.HasFlag(ReprimandNoticeType.Warning), - _ => false - }; - } + public static Color GetColor(this Reprimand reprimand) + { + return reprimand switch + { + Ban => Color.Red, + Censored => Color.Blue, + Kick => Color.Red, + Mute => Color.Orange, + Note => Color.Blue, + Notice => Color.Gold, + Warning => Color.Gold, + + _ => throw new ArgumentOutOfRangeException( + nameof(reprimand), reprimand, "An unknown reprimand was given.") + }; + } + + public static EmbedBuilder AddReprimands(this EmbedBuilder embed, GuildUserEntity user) => embed + .AddField("Warnings", $"{user.WarningCount(false)}/{user.WarningCount()}", true) + .AddReprimand(user) + .AddReprimand(user) + .AddReprimand(user) + .AddReprimand(user); - public static string GetExpirationTime(this IExpirable expirable) + public static IEnumerable OfType(this IEnumerable reprimands, InfractionType type) + { + return type switch { - if (expirable.ExpireAt is not null && expirable.Length is not null) - { - if (expirable.ExpireAt > DateTimeOffset.UtcNow) - { - TimeSpan? dif = expirable.ExpireAt - DateTimeOffset.UtcNow; - return $"{(int)dif.Value.TotalDays} days {dif.Value.Hours} hours {dif.Value.Minutes} minutes {dif.Value.Seconds} seconds"; - - } - else - { - return "Now"; - } - } - else - { - return "Indefinitely"; - } - } + InfractionType.Ban => reprimands.OfType(), + InfractionType.Censored => reprimands.OfType(), + InfractionType.Kick => reprimands.OfType(), + InfractionType.Mute => reprimands.OfType(), + InfractionType.Note => reprimands.OfType(), + InfractionType.Notice => reprimands.OfType(), + InfractionType.Warning => reprimands.OfType(), + InfractionType.All => reprimands, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, + "Invalid Infraction type.") + }; + } + + public static IEnumerable Reprimands(this GuildUserEntity user, bool countHidden = true) + where T : Reprimand + { + var reprimands = user.Guild.ReprimandHistory + .Where(r => r.UserId == user.Id + && r.Status is not ReprimandStatus.Deleted) + .OfType(); + + return countHidden + ? reprimands + : reprimands.Where(IsCounted); + } - public static string GetReprimandExpiration(this Reprimand reprimand) + public static ReprimandType GetReprimandType(this Reprimand reprimand) + { + return reprimand switch { - var mention = $"<@{reprimand.UserId}>"; - var line = reprimand switch - { - Ban b => $"Expired in: {b.GetExpirationTime()}.", - Mute m => $"Expired in: {m.GetExpirationTime()}.", - _ => throw new ArgumentOutOfRangeException( - nameof(reprimand), reprimand, "This reprimand is not expirable.") - }; - return new StringBuilder() - .AppendLine($"User: {mention}") - .AppendLine(line).ToString(); - } + Censored => ReprimandType.Censored, + Ban => ReprimandType.Ban, + Mute => ReprimandType.Mute, + Notice => ReprimandType.Notice, + Warning => ReprimandType.Warning, + Kick => ReprimandType.Kick, + Note => ReprimandType.Note, + _ => throw new ArgumentOutOfRangeException(nameof(reprimand)) + }; + } - public static Color GetColor(this Reprimand reprimand) + public static string GetExpirationTime(this IExpirable expirable) + { + if (expirable.ExpireAt is not null && expirable.Length is not null) { - return reprimand switch + if (expirable.ExpireAt > DateTimeOffset.UtcNow) { - Ban => Color.Red, - Censored => Color.Blue, - Kick => Color.Red, - Mute => Color.Orange, - Note => Color.Blue, - Notice => Color.Gold, - Warning => Color.Gold, - - _ => throw new ArgumentOutOfRangeException( - nameof(reprimand), reprimand, "An unknown reprimand was given.") - }; + var dif = expirable.ExpireAt - DateTimeOffset.UtcNow; + return $"{(int) dif.Value.TotalDays} days {dif.Value.Hours} hours {dif.Value.Minutes} minutes {dif.Value.Seconds} seconds"; + } + return "Now"; } + return "Indefinitely"; + } - public static EmbedBuilder AddReprimands(this EmbedBuilder embed, GuildUserEntity user) => embed - .AddField("Warnings", $"{user.WarningCount(false)}/{user.WarningCount()}", true) - .AddReprimand(user) - .AddReprimand(user) - .AddReprimand(user) - .AddReprimand(user); + public static string GetLength(this ILength mute) + => mute.Length?.Humanize(5, + minUnit: TimeUnit.Second, + maxUnit: TimeUnit.Year) ?? "indefinitely"; - public static IEnumerable OfType(this IEnumerable reprimands, InfractionType type) + public static string GetMessage(this Reprimand action) + { + var mention = $"<@{action.UserId}>"; + return action switch { - return type switch - { - InfractionType.Ban => reprimands.OfType(), - InfractionType.Censored => reprimands.OfType(), - InfractionType.Kick => reprimands.OfType(), - InfractionType.Mute => reprimands.OfType(), - InfractionType.Note => reprimands.OfType(), - InfractionType.Notice => reprimands.OfType(), - InfractionType.Warning => reprimands.OfType(), - InfractionType.All => reprimands, - _ => throw new ArgumentOutOfRangeException(nameof(type), type, - "Invalid Infraction type.") - }; - } + Ban b => $"{mention} was banned for {b.GetLength()}.", + Censored c => $"{mention} was censored. Message: {c.CensoredMessage()}", + Kick => $"{mention} was kicked.", + Mute m => $"{mention} was muted for {m.GetLength()}.", + Note => $"{mention} was given a note.", + Notice => $"{mention} was given a notice.", + Warning w => $"{mention} was warned {w.Count} times.", + + _ => throw new ArgumentOutOfRangeException( + nameof(action), action, "An unknown reprimand was given.") + }; + } - public static IEnumerable Reprimands(this GuildUserEntity user, bool countHidden = true) - where T : Reprimand + public static string GetReprimandExpiration(this Reprimand reprimand) + { + var mention = $"<@{reprimand.UserId}>"; + var line = reprimand switch { - var reprimands = user.Guild.ReprimandHistory - .Where(r => r.UserId == user.Id - && r.Status is not ReprimandStatus.Deleted) - .OfType(); - - return countHidden - ? reprimands - : reprimands.Where(IsCounted); - } + Ban b => $"Expired in: {b.GetExpirationTime()}.", + Mute m => $"Expired in: {m.GetExpirationTime()}.", + _ => throw new ArgumentOutOfRangeException( + nameof(reprimand), reprimand, "This reprimand is not expirable.") + }; + return new StringBuilder() + .AppendLine($"User: {mention}") + .AppendLine(line).ToString(); + } - public static ReprimandType GetReprimandType(this Reprimand reprimand) + public static string GetTitle(this Reprimand action) + { + var title = action switch { - return reprimand switch - { - Censored => ReprimandType.Censored, - Ban => ReprimandType.Ban, - Mute => ReprimandType.Mute, - Notice => ReprimandType.Notice, - Warning => ReprimandType.Warning, - Kick => ReprimandType.Kick, - Note => ReprimandType.Note, - _ => throw new ArgumentOutOfRangeException(nameof(reprimand)) - }; - } - - public static string GetLength(this ILength mute) - => mute.Length?.Humanize(5, - minUnit: TimeUnit.Second, - maxUnit: TimeUnit.Year) ?? "indefinitely"; + Ban => nameof(Ban), + Censored => nameof(Censored), + Kick => nameof(Kick), + Mute => nameof(Mute), + Note => nameof(Note), + Notice => nameof(Notice), + Warning => nameof(Warning), + + _ => throw new ArgumentOutOfRangeException( + nameof(action), action, "An unknown reprimand was given.") + }; + + return $"{title.Humanize()}: {action.Id}"; + } - public static string GetMessage(this Reprimand action) + public static StringBuilder GetReprimandDetails(this Reprimand r) + { + var content = new StringBuilder() + .AppendLine($"▌{GetMessage(r)}") + .AppendLine($"▌Reason: {r.GetReason()}") + .AppendLine($"▌Moderator: {r.GetModerator()}") + .AppendLine($"▌Date: {r.GetDate()}") + .AppendLine($"▌Status: {Format.Bold(r.Status.Humanize())}"); + + if (r.ModifiedAction is not null) { - var mention = $"<@{action.UserId}>"; - return action switch - { - Ban b => $"{mention} was banned for {b.GetLength()}.", - Censored c => $"{mention} was censored. Message: {c.CensoredMessage()}", - Kick => $"{mention} was kicked.", - Mute m => $"{mention} was muted for {m.GetLength()}.", - Note => $"{mention} was given a note.", - Notice => $"{mention} was given a notice.", - Warning w => $"{mention} was warned {w.Count} times.", - - _ => throw new ArgumentOutOfRangeException( - nameof(action), action, "An unknown reprimand was given.") - }; + content + .AppendLine("▌") + .AppendLine($"▌▌{r.Status.Humanize()} by {r.ModifiedAction.GetModerator()}") + .AppendLine($"▌▌{r.ModifiedAction.GetDate()}") + .AppendLine($"▌▌{r.ModifiedAction.GetReason()}"); } - public static string GetTitle(this Reprimand action) + var t = r.Trigger; + if (t is not null) { - var title = action switch - { - Ban => nameof(Ban), - Censored => nameof(Censored), - Kick => nameof(Kick), - Mute => nameof(Mute), - Note => nameof(Note), - Notice => nameof(Notice), - Warning => nameof(Warning), - - _ => throw new ArgumentOutOfRangeException( - nameof(action), action, "An unknown reprimand was given.") - }; - - return $"{title.Humanize()}: {action.Id}"; + content + .AppendLine("▌") + .AppendLine($"▌▌{t.GetTitle()}") + .AppendLine($"▌▌Trigger: {t.GetTriggerDetails()}"); } - public static StringBuilder GetReprimandDetails(this Reprimand r) - { - var content = new StringBuilder() - .AppendLine($"▌{GetMessage(r)}") - .AppendLine($"▌Reason: {r.GetReason()}") - .AppendLine($"▌Moderator: {r.GetModerator()}") - .AppendLine($"▌Date: {r.GetDate()}") - .AppendLine($"▌Status: {Format.Bold(r.Status.Humanize())}"); - - if (r.ModifiedAction is not null) - { - content - .AppendLine("▌") - .AppendLine($"▌▌{r.Status.Humanize()} by {r.ModifiedAction.GetModerator()}") - .AppendLine($"▌▌{r.ModifiedAction.GetDate()}") - .AppendLine($"▌▌{r.ModifiedAction.GetReason()}"); - } - - var t = r.Trigger; - if (t is not null) - { - content - .AppendLine("▌") - .AppendLine($"▌▌{t.GetTitle()}") - .AppendLine($"▌▌Trigger: {t.GetTriggerDetails()}"); - } + return content; + } - return content; - } + public static Task GetGuildAsync(this ReprimandDetails details, ZhongliContext db, + CancellationToken cancellationToken) + => db.Guilds.TrackGuildAsync(details.Guild, cancellationToken); - public static Task GetGuildAsync(this ReprimandDetails details, ZhongliContext db, - CancellationToken cancellationToken) - => db.Guilds.TrackGuildAsync(details.Guild, cancellationToken); + public static async Task CountAsync( + this T reprimand, + DbContext db, bool countHidden = true, + CancellationToken cancellationToken = default) where T : Reprimand + { + var user = await reprimand.GetUserAsync(db, cancellationToken); + if (reprimand is Warning) + return user.WarningCount(countHidden); - public static async Task CountAsync( - this T reprimand, - DbContext db, bool countHidden = true, - CancellationToken cancellationToken = default) where T : Reprimand - { - var user = await reprimand.GetUserAsync(db, cancellationToken); - if (reprimand is Warning) - return user.WarningCount(countHidden); + return (uint) user.Reprimands(countHidden).LongCount(); + } - return (uint) user.Reprimands(countHidden).LongCount(); - } + public static async Task CountAsync( + this T reprimand, Trigger trigger, + DbContext db, bool countHidden = true, + CancellationToken cancellationToken = default) where T : Reprimand + { + var user = await reprimand.GetUserAsync(db, cancellationToken); + return (uint) user.Reprimands(countHidden) + .LongCount(r => r.TriggerId == trigger.Id); + } - public static async Task CountAsync( - this T reprimand, Trigger trigger, - DbContext db, bool countHidden = true, - CancellationToken cancellationToken = default) where T : Reprimand - { - var user = await reprimand.GetUserAsync(db, cancellationToken); - return (uint) user.Reprimands(countHidden) - .LongCount(r => r.TriggerId == trigger.Id); - } + public static uint HistoryCount(this GuildUserEntity user, bool countHidden = true) + where T : Reprimand + => (uint) user.Reprimands(countHidden).LongCount(); - public static uint HistoryCount(this GuildUserEntity user, bool countHidden = true) - where T : Reprimand - => (uint) user.Reprimands(countHidden).LongCount(); + public static uint WarningCount(this GuildUserEntity user, bool countHidden = true) + => (uint) user.Reprimands(countHidden).Sum(w => w.Count); - public static uint WarningCount(this GuildUserEntity user, bool countHidden = true) - => (uint) user.Reprimands(countHidden).Sum(w => w.Count); + public static async ValueTask GetGuildAsync(this Reprimand reprimand, DbContext db, + CancellationToken cancellationToken = default) + { + return reprimand.Guild ?? + await db.FindAsync(new object[] { reprimand.GuildId }, cancellationToken); + } - public static async ValueTask GetGuildAsync(this Reprimand reprimand, DbContext db, - CancellationToken cancellationToken = default) - { - return reprimand.Guild ?? - await db.FindAsync(new object[] { reprimand.GuildId }, cancellationToken); - } + public static async ValueTask GetUserAsync(this Reprimand reprimand, DbContext db, + CancellationToken cancellationToken = default) + { + return reprimand.User ?? + await db.FindAsync(new object[] { reprimand.UserId, reprimand.GuildId }, + cancellationToken); + } - public static async ValueTask GetUserAsync(this Reprimand reprimand, DbContext db, - CancellationToken cancellationToken = default) + public static async ValueTask GetTriggerAsync(this Reprimand reprimand, DbContext db, + CancellationToken cancellationToken = default) + { + var trigger = reprimand.Trigger; + if (reprimand.TriggerId is not null) { - return reprimand.User ?? - await db.FindAsync(new object[] { reprimand.UserId, reprimand.GuildId }, - cancellationToken); + trigger ??= await db.FindAsync(new object[] { reprimand.TriggerId }, + cancellationToken); } - public static async ValueTask GetTriggerAsync(this Reprimand reprimand, DbContext db, - CancellationToken cancellationToken = default) - { - var trigger = reprimand.Trigger; - if (reprimand.TriggerId is not null) - { - trigger ??= await db.FindAsync(new object[] { reprimand.TriggerId }, - cancellationToken); - } - - return trigger; - } + return trigger; + } - private static bool IsCounted(Reprimand reprimand) - => reprimand.Status is ReprimandStatus.Added or ReprimandStatus.Updated; + private static bool IsCounted(Reprimand reprimand) + => reprimand.Status is ReprimandStatus.Added or ReprimandStatus.Updated; - private static EmbedBuilder AddReprimand(this EmbedBuilder embed, GuildUserEntity user) - where T : Reprimand => embed.AddField(typeof(T).Name.Pluralize(), - $"{user.HistoryCount(false)}/{user.HistoryCount()}", true); - } + private static EmbedBuilder AddReprimand(this EmbedBuilder embed, GuildUserEntity user) + where T : Reprimand => embed.AddField(typeof(T).Name.Pluralize(), + $"{user.HistoryCount(false)}/{user.HistoryCount()}", true); } \ No newline at end of file diff --git a/Zhongli.Services/Moderation/ReprimandRequest.cs b/Zhongli.Services/Moderation/ReprimandRequest.cs index be7df06..b87beed 100644 --- a/Zhongli.Services/Moderation/ReprimandRequest.cs +++ b/Zhongli.Services/Moderation/ReprimandRequest.cs @@ -1,9 +1,8 @@ using MediatR; using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Services.Moderation -{ - public record ReprimandRequest(ReprimandDetails Details, TAction Reprimand) - : IRequest - where TAction : Reprimand; -} \ No newline at end of file +namespace Zhongli.Services.Moderation; + +public record ReprimandRequest(ReprimandDetails Details, TAction Reprimand) + : IRequest + where TAction : Reprimand; \ No newline at end of file diff --git a/Zhongli.Services/Moderation/TriggerExtensions.cs b/Zhongli.Services/Moderation/TriggerExtensions.cs index b811298..37430d4 100644 --- a/Zhongli.Services/Moderation/TriggerExtensions.cs +++ b/Zhongli.Services/Moderation/TriggerExtensions.cs @@ -6,68 +6,67 @@ using Zhongli.Data.Models.Moderation.Infractions.Reprimands; using Zhongli.Data.Models.Moderation.Infractions.Triggers; -namespace Zhongli.Services.Moderation +namespace Zhongli.Services.Moderation; + +public static class TriggerExtensions { - public static class TriggerExtensions + public static bool IsTriggered(this ITrigger trigger, uint amount) { - public static bool IsTriggered(this ITrigger trigger, uint amount) + return trigger.Mode switch { - return trigger.Mode switch - { - TriggerMode.Exact => amount == trigger.Amount, - TriggerMode.Retroactive => amount >= trigger.Amount, - TriggerMode.Multiple => amount % trigger.Amount is 0 && amount is not 0, - _ => throw new ArgumentOutOfRangeException(nameof(trigger), trigger.Mode, - "Invalid trigger mode.") - }; - } + TriggerMode.Exact => amount == trigger.Amount, + TriggerMode.Retroactive => amount >= trigger.Amount, + TriggerMode.Multiple => amount % trigger.Amount is 0 && amount is not 0, + _ => throw new ArgumentOutOfRangeException(nameof(trigger), trigger.Mode, + "Invalid trigger mode.") + }; + } - public static string GetTitle(this ReprimandAction action) + public static string GetTitle(this ReprimandAction action) + { + return action switch { - return action switch - { - BanAction => nameof(Ban), - KickAction => nameof(Kick), - MuteAction => nameof(Mute), - NoteAction => nameof(Note), - NoticeAction => nameof(Notice), - WarningAction => nameof(Warning), - _ => throw new ArgumentOutOfRangeException(nameof(action), action, - "Invalid ReprimandAction.") - }; - } + BanAction => nameof(Ban), + KickAction => nameof(Kick), + MuteAction => nameof(Mute), + NoteAction => nameof(Note), + NoticeAction => nameof(Notice), + WarningAction => nameof(Warning), + _ => throw new ArgumentOutOfRangeException(nameof(action), action, + "Invalid ReprimandAction.") + }; + } - public static string GetTitle(this Trigger trigger) + public static string GetTitle(this Trigger trigger) + { + var title = trigger switch { - var title = trigger switch - { - Censor => nameof(Censor), - ReprimandTrigger => nameof(ReprimandTrigger), - _ => throw new ArgumentOutOfRangeException(nameof(trigger), trigger, - "Invalid trigger type.") - }; + Censor => nameof(Censor), + ReprimandTrigger => nameof(ReprimandTrigger), + _ => throw new ArgumentOutOfRangeException(nameof(trigger), trigger, + "Invalid trigger type.") + }; - title = title - .Replace(nameof(Trigger), string.Empty) - .Humanize(LetterCasing.Title); + title = title + .Replace(nameof(Trigger), string.Empty) + .Humanize(LetterCasing.Title); - return $"{title}: {trigger.Id}"; - } + return $"{title}: {trigger.Id}"; + } - public static string GetTriggerDetails(this Trigger trigger) + public static string GetTriggerDetails(this Trigger trigger) + { + var action = trigger switch { - var action = trigger switch - { - Censor c => $"{Format.Code(c.Pattern)} ({c.Options.Humanize()})", - ReprimandTrigger r => $"{r.Source.Humanize().Pluralize()}", - _ => throw new ArgumentOutOfRangeException( - nameof(trigger), trigger, "Invalid trigger type.") - }; + Censor c => $"{Format.Code(c.Pattern)} ({c.Options.Humanize()})", + ReprimandTrigger r => $"{r.Source.Humanize().Pluralize()}", + _ => throw new ArgumentOutOfRangeException( + nameof(trigger), trigger, "Invalid trigger type.") + }; - return $"{trigger.GetTriggerMode()} {action}"; - } - - private static string GetTriggerMode(this ITrigger trigger) - => $"{trigger.Mode} {Format.Code($"{trigger.Amount}")}"; + return $"{trigger.GetTriggerMode()} {action}"; } + + private static string GetTriggerMode(this ITrigger trigger) + => $"{trigger.Mode} {Format.Code($"{trigger.Amount}")}"; } \ No newline at end of file diff --git a/Zhongli.Services/Quote/EmbedBuilderExtensions.cs b/Zhongli.Services/Quote/EmbedBuilderExtensions.cs index a6f3ef7..d069efa 100644 --- a/Zhongli.Services/Quote/EmbedBuilderExtensions.cs +++ b/Zhongli.Services/Quote/EmbedBuilderExtensions.cs @@ -3,100 +3,99 @@ using Humanizer.Bytes; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Quote +namespace Zhongli.Services.Quote; + +public static class EmbedBuilderExtensions { - public static class EmbedBuilderExtensions + public static bool TryAddImageAttachment(this EmbedBuilder embed, IMessage message) { - public static bool TryAddImageAttachment(this EmbedBuilder embed, IMessage message) - { - var firstAttachment = message.Attachments.FirstOrDefault(); - if (firstAttachment?.Height is null) - return false; + var firstAttachment = message.Attachments.FirstOrDefault(); + if (firstAttachment?.Height is null) + return false; - embed.WithImageUrl(firstAttachment.Url); + embed.WithImageUrl(firstAttachment.Url); - return true; - } + return true; + } - public static bool TryAddImageEmbed(this EmbedBuilder embed, IMessage message) - { - var imageEmbed = message.Embeds.Select(x => x.Image).FirstOrDefault(x => x is { }); - if (imageEmbed is null) - return false; + public static bool TryAddImageEmbed(this EmbedBuilder embed, IMessage message) + { + var imageEmbed = message.Embeds.Select(x => x.Image).FirstOrDefault(x => x is { }); + if (imageEmbed is null) + return false; - embed.WithImageUrl(imageEmbed.Value.Url); + embed.WithImageUrl(imageEmbed.Value.Url); - return true; - } + return true; + } - public static bool TryAddOtherAttachment(this EmbedBuilder embed, IMessage message) - { - var firstAttachment = message.Attachments.FirstOrDefault(); - if (firstAttachment is null) return false; + public static bool TryAddOtherAttachment(this EmbedBuilder embed, IMessage message) + { + var firstAttachment = message.Attachments.FirstOrDefault(); + if (firstAttachment is null) return false; - embed.AddField($"Attachment (Size: {new ByteSize(firstAttachment.Size)})", firstAttachment.Url); + embed.AddField($"Attachment (Size: {new ByteSize(firstAttachment.Size)})", firstAttachment.Url); - return true; - } + return true; + } - public static bool TryAddThumbnailEmbed(this EmbedBuilder embed, IMessage message) - { - var thumbnailEmbed = message.Embeds.Select(x => x.Thumbnail).FirstOrDefault(x => x is { }); - if (thumbnailEmbed is null) - return false; + public static bool TryAddThumbnailEmbed(this EmbedBuilder embed, IMessage message) + { + var thumbnailEmbed = message.Embeds.Select(x => x.Thumbnail).FirstOrDefault(x => x is { }); + if (thumbnailEmbed is null) + return false; - embed.WithImageUrl(thumbnailEmbed.Value.Url); + embed.WithImageUrl(thumbnailEmbed.Value.Url); - return true; - } + return true; + } - public static EmbedBuilder AddActivity(this EmbedBuilder embed, IMessage message) - { - if (message.Activity is null) return embed; + public static EmbedBuilder AddActivity(this EmbedBuilder embed, IMessage message) + { + if (message.Activity is null) return embed; - return embed - .AddField("Invite Type", message.Activity.Type) - .AddField("Party Id", message.Activity.PartyId); - } + return embed + .AddField("Invite Type", message.Activity.Type) + .AddField("Party Id", message.Activity.PartyId); + } - public static EmbedBuilder AddContent(this EmbedBuilder embed, IMessage message) - => embed.AddContent(message.Content); + public static EmbedBuilder AddContent(this EmbedBuilder embed, IMessage message) + => embed.AddContent(message.Content); - public static EmbedBuilder AddContent(this EmbedBuilder embed, string? content) - => string.IsNullOrWhiteSpace(content) ? embed : embed.WithDescription(content); + public static EmbedBuilder AddContent(this EmbedBuilder embed, string? content) + => string.IsNullOrWhiteSpace(content) ? embed : embed.WithDescription(content); - public static EmbedBuilder AddJumpLink(this EmbedBuilder embed, IMessage message, IMentionable quotingUser) - => embed - .AddField("Quoted by", quotingUser.Mention, true) - .AddField("Author", $"{message.Author.Mention} from {Format.Bold(message.GetJumpUrlForEmbed())}", true); + public static EmbedBuilder AddJumpLink(this EmbedBuilder embed, IMessage message, IMentionable quotingUser) + => embed + .AddField("Quoted by", quotingUser.Mention, true) + .AddField("Author", $"{message.Author.Mention} from {Format.Bold(message.GetJumpUrlForEmbed())}", true); - public static EmbedBuilder AddJumpLink(this EmbedBuilder embed, IMessage message, bool useTitle = false) - => useTitle - ? embed.WithTitle($"From #{message.Channel.Name}").WithUrl(message.GetJumpUrl()) - : embed.AddField("Context", - $"By {message.Author.Mention} from {Format.Bold(message.GetJumpUrlForEmbed())}", - true); + public static EmbedBuilder AddJumpLink(this EmbedBuilder embed, IMessage message, bool useTitle = false) + => useTitle + ? embed.WithTitle($"From #{message.Channel.Name}").WithUrl(message.GetJumpUrl()) + : embed.AddField("Context", + $"By {message.Author.Mention} from {Format.Bold(message.GetJumpUrlForEmbed())}", + true); - public static EmbedBuilder AddMeta(this EmbedBuilder embed, IMessage message, - AuthorOptions options = AuthorOptions.None) => embed - .WithUserAsAuthor(message.Author, options) - .WithTimestamp(message.Timestamp); + public static EmbedBuilder AddMeta(this EmbedBuilder embed, IMessage message, + AuthorOptions options = AuthorOptions.None) => embed + .WithUserAsAuthor(message.Author, options) + .WithTimestamp(message.Timestamp); - public static EmbedBuilder AddOtherEmbed(this EmbedBuilder embed, IMessage message) => message.Embeds.Count == 0 - ? embed - : embed.AddField("Embed Type", message.Embeds.First().Type); + public static EmbedBuilder AddOtherEmbed(this EmbedBuilder embed, IMessage message) => message.Embeds.Count == 0 + ? embed + : embed.AddField("Embed Type", message.Embeds.First().Type); - public static EmbedBuilder? GetRichEmbed(this IMessage message) - { - var firstEmbed = message.Embeds.FirstOrDefault(); - if (firstEmbed?.Type != EmbedType.Rich) return null; + public static EmbedBuilder? GetRichEmbed(this IMessage message) + { + var firstEmbed = message.Embeds.FirstOrDefault(); + if (firstEmbed?.Type != EmbedType.Rich) return null; - var embed = message.Embeds.First() - .ToEmbedBuilder(); + var embed = message.Embeds.First() + .ToEmbedBuilder(); - if (firstEmbed.Color is null) embed.Color = Color.DarkGrey; + if (firstEmbed.Color is null) embed.Color = Color.DarkGrey; - return embed; - } + return embed; } } \ No newline at end of file diff --git a/Zhongli.Services/Quote/MessageLinkBehavior.cs b/Zhongli.Services/Quote/MessageLinkBehavior.cs index 89d2e2b..91cad33 100644 --- a/Zhongli.Services/Quote/MessageLinkBehavior.cs +++ b/Zhongli.Services/Quote/MessageLinkBehavior.cs @@ -14,108 +14,104 @@ using Zhongli.Services.Utilities; using MessageExtensions = Zhongli.Services.Utilities.MessageExtensions; -namespace Zhongli.Services.Quote +namespace Zhongli.Services.Quote; + +public class MessageLinkBehavior : + INotificationHandler, + INotificationHandler { - public class MessageLinkBehavior : - INotificationHandler, - INotificationHandler + private readonly AuthorizationService _auth; + private readonly DiscordSocketClient _discordClient; + private readonly ILogger _log; + private readonly IQuoteService _quoteService; + + public MessageLinkBehavior( + AuthorizationService auth, DiscordSocketClient discordClient, + IQuoteService quoteService, ILogger log) { - private readonly AuthorizationService _auth; - private readonly DiscordSocketClient _discordClient; - private readonly ILogger _log; - private readonly IQuoteService _quoteService; - - public MessageLinkBehavior( - AuthorizationService auth, DiscordSocketClient discordClient, - IQuoteService quoteService, ILogger log) - { - _auth = auth; - _discordClient = discordClient; - _quoteService = quoteService; - _log = log; - } + _auth = auth; + _discordClient = discordClient; + _quoteService = quoteService; + _log = log; + } - public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) - { - await OnMessageReceivedAsync(notification.Message, cancellationToken); - } + public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) { await OnMessageReceivedAsync(notification.Message, cancellationToken); } - public async Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) - { - var cachedMessage = await notification.OldMessage.GetOrDownloadAsync(); + public async Task Handle(MessageUpdatedNotification notification, CancellationToken cancellationToken) + { + var cachedMessage = await notification.OldMessage.GetOrDownloadAsync(); - if (cachedMessage is null) - return; + if (cachedMessage is null) + return; - if (RegexUtilities.JumpUrl.IsMatch(cachedMessage.Content)) - return; + if (RegexUtilities.JumpUrl.IsMatch(cachedMessage.Content)) + return; - await OnMessageReceivedAsync(notification.NewMessage, cancellationToken); - } + await OnMessageReceivedAsync(notification.NewMessage, cancellationToken); + } + + private async Task OnMessageReceivedAsync(SocketMessage message, CancellationToken cancellationToken) + { + if (message.Content?.StartsWith(ZhongliConfig.Configuration.Prefix) ?? true) return; + if (message is not SocketUserMessage userMessage || message.Author.IsBot) + return; + + var context = new SocketCommandContext(_discordClient, userMessage); + if (!await _auth.IsAuthorizedAsync(context, AuthorizationScope.Quote, cancellationToken)) + return; - private async Task OnMessageReceivedAsync(SocketMessage message, CancellationToken cancellationToken) + foreach (Match match in RegexUtilities.JumpUrl.Matches(message.Content)) { - if (message.Content?.StartsWith(ZhongliConfig.Configuration.Prefix) ?? true) return; - if (message is not SocketUserMessage userMessage || message.Author.IsBot) - return; + // check if the link is surrounded with < and >. This was too annoying to do in regex + if (match.Groups["OpenBrace"].Success && match.Groups["CloseBrace"].Success) + continue; - var context = new SocketCommandContext(_discordClient, userMessage); - if (!await _auth.IsAuthorizedAsync(context, AuthorizationScope.Quote, cancellationToken)) - return; + if (!MessageExtensions.TryGetJumpUrl(match, out _, out var channelId, out var messageId)) + continue; - foreach (Match match in RegexUtilities.JumpUrl.Matches(message.Content)) + try { - // check if the link is surrounded with < and >. This was too annoying to do in regex - if (match.Groups["OpenBrace"].Success && match.Groups["CloseBrace"].Success) - continue; - - if (!MessageExtensions.TryGetJumpUrl(match, out _, out var channelId, out var messageId)) - continue; - - try - { - var channel = _discordClient.GetChannel(channelId); - if (channel is not ITextChannel textChannel || textChannel.IsNsfw) return; - - var user = await textChannel.Guild.GetUserAsync(message.Author.Id); - var channelPermissions = user.GetPermissions(textChannel); - if (!channelPermissions.ViewChannel) return; - - var cacheMode = channelPermissions.ReadMessageHistory - ? CacheMode.AllowDownload - : CacheMode.CacheOnly; - - var quote = await textChannel.GetMessageAsync(messageId, cacheMode); - if (quote is null) return; - - var success = await SendQuoteEmbedAsync(message, quote); - if (success - && string.IsNullOrEmpty(match.Groups["Prelink"].Value) - && string.IsNullOrEmpty(match.Groups["Postlink"].Value)) - await userMessage.DeleteAsync(); - } - catch (Exception ex) - { - _log.LogError(ex, "An error occurred while attempting to create a quote embed"); - } + var channel = _discordClient.GetChannel(channelId); + if (channel is not ITextChannel textChannel || textChannel.IsNsfw) return; + + var user = await textChannel.Guild.GetUserAsync(message.Author.Id); + var channelPermissions = user.GetPermissions(textChannel); + if (!channelPermissions.ViewChannel) return; + + var cacheMode = channelPermissions.ReadMessageHistory + ? CacheMode.AllowDownload + : CacheMode.CacheOnly; + + var quote = await textChannel.GetMessageAsync(messageId, cacheMode); + if (quote is null) return; + + var success = await SendQuoteEmbedAsync(message, quote); + if (success + && string.IsNullOrEmpty(match.Groups["Prelink"].Value) + && string.IsNullOrEmpty(match.Groups["Postlink"].Value)) + await userMessage.DeleteAsync(); + } + catch (Exception ex) + { + _log.LogError(ex, "An error occurred while attempting to create a quote embed"); } } + } - private async Task SendQuoteEmbedAsync(SocketMessage source, IMessage quote) - { - var success = false; - await _quoteService.BuildRemovableEmbed(quote, source.Author, - async embed => //If embed building is unsuccessful, this won't execute - { - success = true; - - return await source.Channel.SendMessageAsync( - embed: embed.Build(), - messageReference: source.Reference, - allowedMentions: AllowedMentions.None); - }); - - return success; - } + private async Task SendQuoteEmbedAsync(SocketMessage source, IMessage quote) + { + var success = false; + await _quoteService.BuildRemovableEmbed(quote, source.Author, + async embed => //If embed building is unsuccessful, this won't execute + { + success = true; + + return await source.Channel.SendMessageAsync( + embed: embed.Build(), + messageReference: source.Reference, + allowedMentions: AllowedMentions.None); + }); + + return success; } } \ No newline at end of file diff --git a/Zhongli.Services/Quote/QuoteService.cs b/Zhongli.Services/Quote/QuoteService.cs index 32638f4..a9188e8 100644 --- a/Zhongli.Services/Quote/QuoteService.cs +++ b/Zhongli.Services/Quote/QuoteService.cs @@ -5,73 +5,69 @@ using Zhongli.Services.AutoRemoveMessage; using Zhongli.Services.Utilities; -namespace Zhongli.Services.Quote +namespace Zhongli.Services.Quote; + +public interface IQuoteService { - public interface IQuoteService - { - /// - /// Build an embed quote for the given message. Returns null if the message could not be quoted. - /// - /// The message to quote - /// The user that is doing the quoting - EmbedBuilder? BuildQuoteEmbed(IMessage message, IUser executingUser); + /// + /// Build an embed quote for the given message. Returns null if the message could not be quoted. + /// + /// The message to quote + /// The user that is doing the quoting + EmbedBuilder? BuildQuoteEmbed(IMessage message, IUser executingUser); - Task BuildRemovableEmbed(IMessage message, IUser executingUser, - Func>? callback); - } + Task BuildRemovableEmbed(IMessage message, IUser executingUser, + Func>? callback); +} - public class QuoteService : IQuoteService - { - private readonly IAutoRemoveMessageService _autoRemoveMessageService; +public class QuoteService : IQuoteService +{ + private readonly IAutoRemoveMessageService _autoRemoveMessageService; - public QuoteService(IAutoRemoveMessageService autoRemoveMessageService) - { - _autoRemoveMessageService = autoRemoveMessageService; - } + public QuoteService(IAutoRemoveMessageService autoRemoveMessageService) { _autoRemoveMessageService = autoRemoveMessageService; } - /// - public EmbedBuilder? BuildQuoteEmbed(IMessage message, IUser executingUser) - { - if (IsQuote(message)) return null; + /// + public EmbedBuilder? BuildQuoteEmbed(IMessage message, IUser executingUser) + { + if (IsQuote(message)) return null; - var embed = message.GetRichEmbed() ?? new EmbedBuilder(); + var embed = message.GetRichEmbed() ?? new EmbedBuilder(); - if (!embed.TryAddImageAttachment(message)) + if (!embed.TryAddImageAttachment(message)) + { + if (!embed.TryAddImageEmbed(message)) { - if (!embed.TryAddImageEmbed(message)) - { - if (!embed.TryAddThumbnailEmbed(message)) - embed.TryAddOtherAttachment(message); - } + if (!embed.TryAddThumbnailEmbed(message)) + embed.TryAddOtherAttachment(message); } + } - embed.WithColor(new Color(95, 186, 125)) - .AddContent(message) - .AddOtherEmbed(message) - .AddActivity(message) - .AddMeta(message, AuthorOptions.IncludeId) - .AddJumpLink(message, executingUser); + embed.WithColor(new Color(95, 186, 125)) + .AddContent(message) + .AddOtherEmbed(message) + .AddActivity(message) + .AddMeta(message, AuthorOptions.IncludeId) + .AddJumpLink(message, executingUser); - return embed; - } + return embed; + } - public async Task BuildRemovableEmbed(IMessage message, IUser executingUser, - Func>? callback) - { - var embed = BuildQuoteEmbed(message, executingUser); + public async Task BuildRemovableEmbed(IMessage message, IUser executingUser, + Func>? callback) + { + var embed = BuildQuoteEmbed(message, executingUser); - if (callback is null || embed is null) return; + if (callback is null || embed is null) return; - await _autoRemoveMessageService.RegisterRemovableMessageAsync(executingUser, embed, - async e => await callback.Invoke(e)); - } + await _autoRemoveMessageService.RegisterRemovableMessageAsync(executingUser, embed, + async e => await callback.Invoke(e)); + } - private static bool IsQuote(IMessage message) - { - return message - .Embeds? - .SelectMany(d => d.Fields) - .Any(d => d.Name == "Quoted by") == true; - } + private static bool IsQuote(IMessage message) + { + return message + .Embeds? + .SelectMany(d => d.Fields) + .Any(d => d.Name == "Quoted by") == true; } } \ No newline at end of file diff --git a/Zhongli.Services/TimeTracking/GenshinTimeTrackingBehavior.cs b/Zhongli.Services/TimeTracking/GenshinTimeTrackingBehavior.cs index ca7833a..e79ddd0 100644 --- a/Zhongli.Services/TimeTracking/GenshinTimeTrackingBehavior.cs +++ b/Zhongli.Services/TimeTracking/GenshinTimeTrackingBehavior.cs @@ -5,26 +5,25 @@ using Zhongli.Data; using Zhongli.Services.Core.Messages; -namespace Zhongli.Services.TimeTracking +namespace Zhongli.Services.TimeTracking; + +public class GenshinTimeTrackingBehavior : INotificationHandler { - public class GenshinTimeTrackingBehavior : INotificationHandler - { - private readonly GenshinTimeTrackingService _tracking; - private readonly ZhongliContext _db; + private readonly GenshinTimeTrackingService _tracking; + private readonly ZhongliContext _db; - public GenshinTimeTrackingBehavior(GenshinTimeTrackingService tracking, ZhongliContext db) - { - _tracking = tracking; - _db = db; - } + public GenshinTimeTrackingBehavior(GenshinTimeTrackingService tracking, ZhongliContext db) + { + _tracking = tracking; + _db = db; + } - public async Task Handle(ReadyNotification notification, CancellationToken cancellationToken) + public async Task Handle(ReadyNotification notification, CancellationToken cancellationToken) + { + var guilds = await _db.Guilds.ToAsyncEnumerable().ToListAsync(cancellationToken); + foreach (var rules in guilds.Select(guild => guild.GenshinRules).Where(rules => rules is not null)) { - var guilds = await _db.Guilds.ToAsyncEnumerable().ToListAsync(cancellationToken); - foreach (var rules in guilds.Select(guild => guild.GenshinRules).Where(rules => rules is not null)) - { - _tracking.TrackGenshinTime(rules!); - } + _tracking.TrackGenshinTime(rules!); } } } \ No newline at end of file diff --git a/Zhongli.Services/TimeTracking/GenshinTimeTrackingService.cs b/Zhongli.Services/TimeTracking/GenshinTimeTrackingService.cs index 9ddccc5..2c95819 100644 --- a/Zhongli.Services/TimeTracking/GenshinTimeTrackingService.cs +++ b/Zhongli.Services/TimeTracking/GenshinTimeTrackingService.cs @@ -11,154 +11,153 @@ using Zhongli.Data.Models.TimeTracking; using Zhongli.Services.Utilities; -namespace Zhongli.Services.TimeTracking +namespace Zhongli.Services.TimeTracking; + +public class GenshinTimeTrackingService { - public class GenshinTimeTrackingService + public enum ServerRegion { - public enum ServerRegion - { - America, - Europe, - Asia, - SAR - } + America, + Europe, + Asia, + SAR + } - private readonly DiscordSocketClient _client; + private readonly DiscordSocketClient _client; - public GenshinTimeTrackingService(DiscordSocketClient client) { _client = client; } + public GenshinTimeTrackingService(DiscordSocketClient client) { _client = client; } - private static Dictionary ServerOffsets { get; } = new() - { - [ServerRegion.America] = ("NA", -5), - [ServerRegion.Europe] = ("EU", 1), - [ServerRegion.Asia] = ("ASIA", 8), - [ServerRegion.SAR] = ("SAR", 8) - }; - - // ReSharper disable once MemberCanBePrivate.Global - public async Task UpdateChannelAsync(ulong guildId, ulong channelId, ServerRegion region) - { - var guild = await GetGuildAsync(guildId); - if (guild is null) return; + private static Dictionary ServerOffsets { get; } = new() + { + [ServerRegion.America] = ("NA", -5), + [ServerRegion.Europe] = ("EU", 1), + [ServerRegion.Asia] = ("ASIA", 8), + [ServerRegion.SAR] = ("SAR", 8) + }; + + // ReSharper disable once MemberCanBePrivate.Global + public async Task UpdateChannelAsync(ulong guildId, ulong channelId, ServerRegion region) + { + var guild = await GetGuildAsync(guildId); + if (guild is null) return; - var channel = await guild.GetTextChannelAsync(channelId); - if (channel is null) return; + var channel = await guild.GetTextChannelAsync(channelId); + if (channel is null) return; - await TrackRegionAsync(channel, region); - } + await TrackRegionAsync(channel, region); + } - [AutomaticRetry(Attempts = 0)] - // ReSharper disable once MemberCanBePrivate.Global - public async Task UpdateMessageAsync(ulong guildId, ulong channelId, ulong messageId) - { - var guild = await GetGuildAsync(guildId); - if (guild is null) return; - - var channel = await guild.GetTextChannelAsync(channelId); - if (channel is null) return; - - if (await channel.GetMessageAsync(messageId) is not IUserMessage message) - return; - - var embed = new EmbedBuilder() - .WithTitle("Server Status") - .WithGuildAsAuthor(guild, AuthorOptions.UseFooter) - .WithCurrentTimestamp(); - - AddRegion(embed, ServerRegion.America, "md"); - AddRegion(embed, ServerRegion.Europe, string.Empty); - AddRegion(embed, ServerRegion.Asia, "cs"); - AddRegion(embed, ServerRegion.SAR, "fix"); - - await message.ModifyAsync(m => - { - m.Content = string.Empty; - m.Embed = embed.Build(); - }); - } + [AutomaticRetry(Attempts = 0)] + // ReSharper disable once MemberCanBePrivate.Global + public async Task UpdateMessageAsync(ulong guildId, ulong channelId, ulong messageId) + { + var guild = await GetGuildAsync(guildId); + if (guild is null) return; + + var channel = await guild.GetTextChannelAsync(channelId); + if (channel is null) return; + + if (await channel.GetMessageAsync(messageId) is not IUserMessage message) + return; - public void TrackGenshinTime(GenshinTimeTrackingRules rules) + var embed = new EmbedBuilder() + .WithTitle("Server Status") + .WithGuildAsAuthor(guild, AuthorOptions.UseFooter) + .WithCurrentTimestamp(); + + AddRegion(embed, ServerRegion.America, "md"); + AddRegion(embed, ServerRegion.Europe, string.Empty); + AddRegion(embed, ServerRegion.Asia, "cs"); + AddRegion(embed, ServerRegion.SAR, "fix"); + + await message.ModifyAsync(m => { - var serverStatus = rules.ServerStatus?.Adapt(); - if (serverStatus is not null) - { - var id = serverStatus.Id.ToString(); - RecurringJob.AddOrUpdate(id, () - => UpdateMessageAsync( - serverStatus.GuildId, - serverStatus.ChannelId, - serverStatus.MessageId), - Cron.Minutely); - - RecurringJob.Trigger(id); - } - - AddJob(rules.AmericaChannel, ServerRegion.America); - AddJob(rules.EuropeChannel, ServerRegion.America); - AddJob(rules.AsiaChannel, ServerRegion.America); - AddJob(rules.SARChannel, ServerRegion.America); - } + m.Content = string.Empty; + m.Embed = embed.Build(); + }); + } - private static DateTimeOffset GetDailyReset(int offset) + public void TrackGenshinTime(GenshinTimeTrackingRules rules) + { + var serverStatus = rules.ServerStatus?.Adapt(); + if (serverStatus is not null) { - var baseUtcOffset = TimeSpan.FromHours(offset); - var timezone = TimeZoneInfo.CreateCustomTimeZone(offset.ToString(), baseUtcOffset, null, null); - var expression = CronExpression.Parse(Cron.Daily(4)); - var next = expression.GetNextOccurrence(DateTime.UtcNow, timezone); + var id = serverStatus.Id.ToString(); + RecurringJob.AddOrUpdate(id, () + => UpdateMessageAsync( + serverStatus.GuildId, + serverStatus.ChannelId, + serverStatus.MessageId), + Cron.Minutely); - return next!.Value; + RecurringJob.Trigger(id); } - private static DateTimeOffset GetTime(int offset) => DateTimeOffset.UtcNow - .ToOffset(TimeSpan.FromHours(offset)); + AddJob(rules.AmericaChannel, ServerRegion.America); + AddJob(rules.EuropeChannel, ServerRegion.America); + AddJob(rules.AsiaChannel, ServerRegion.America); + AddJob(rules.SARChannel, ServerRegion.America); + } - private static DateTimeOffset GetWeeklyReset(int offset) - { - var baseUtcOffset = TimeSpan.FromHours(offset); - var timezone = TimeZoneInfo.CreateCustomTimeZone(offset.ToString(), baseUtcOffset, null, null); - var expression = CronExpression.Parse(Cron.Weekly(DayOfWeek.Monday, 4)); - var next = expression.GetNextOccurrence(DateTime.UtcNow, timezone); + private static DateTimeOffset GetDailyReset(int offset) + { + var baseUtcOffset = TimeSpan.FromHours(offset); + var timezone = TimeZoneInfo.CreateCustomTimeZone(offset.ToString(), baseUtcOffset, null, null); + var expression = CronExpression.Parse(Cron.Daily(4)); + var next = expression.GetNextOccurrence(DateTime.UtcNow, timezone); - return next!.Value; - } + return next!.Value; + } - private static Task TrackRegionAsync(IGuildChannel channel, ServerRegion region) - { - var (name, offset) = ServerOffsets[region]; - return channel.ModifyAsync(c => c.Name = $"{name}: {GetTime(offset)}"); - } + private static DateTimeOffset GetTime(int offset) => DateTimeOffset.UtcNow + .ToOffset(TimeSpan.FromHours(offset)); - private async Task GetGuildAsync(ulong guildId) => - _client.GetGuild(guildId) as IGuild - ?? await _client.Rest.GetGuildAsync(guildId); + private static DateTimeOffset GetWeeklyReset(int offset) + { + var baseUtcOffset = TimeSpan.FromHours(offset); + var timezone = TimeZoneInfo.CreateCustomTimeZone(offset.ToString(), baseUtcOffset, null, null); + var expression = CronExpression.Parse(Cron.Weekly(DayOfWeek.Monday, 4)); + var next = expression.GetNextOccurrence(DateTime.UtcNow, timezone); - private void AddJob(ChannelTimeTracking? tracking, ServerRegion region) - { - if (tracking is null) return; + return next!.Value; + } - var id = tracking.Id.ToString(); - RecurringJob.AddOrUpdate(id, () - => UpdateChannelAsync( - tracking.GuildId, - tracking.ChannelId, - region), - "*/5 * * * *"); + private static Task TrackRegionAsync(IGuildChannel channel, ServerRegion region) + { + var (name, offset) = ServerOffsets[region]; + return channel.ModifyAsync(c => c.Name = $"{name}: {GetTime(offset)}"); + } - RecurringJob.Trigger(id); - } + private async Task GetGuildAsync(ulong guildId) => + _client.GetGuild(guildId) as IGuild + ?? await _client.Rest.GetGuildAsync(guildId); - private static void AddRegion(EmbedBuilder builder, ServerRegion region, string language) - { - var (name, offset) = ServerOffsets[region]; - var time = GetTime(offset); - builder - .AddField($"{region.Humanize()} Time", Format.Bold(Format.Code($"# {name} {time}", language))) - .AddField("Daily", - $"Resets in {Format.Bold(GetDailyReset(offset).TimeLeft().Humanize(4, minUnit: TimeUnit.Minute))}", - true) - .AddField("Weekly", - $"Resets in {Format.Bold(GetWeeklyReset(offset).TimeLeft().Humanize(4, minUnit: TimeUnit.Minute))}", - true); - } + private void AddJob(ChannelTimeTracking? tracking, ServerRegion region) + { + if (tracking is null) return; + + var id = tracking.Id.ToString(); + RecurringJob.AddOrUpdate(id, () + => UpdateChannelAsync( + tracking.GuildId, + tracking.ChannelId, + region), + "*/5 * * * *"); + + RecurringJob.Trigger(id); + } + + private static void AddRegion(EmbedBuilder builder, ServerRegion region, string language) + { + var (name, offset) = ServerOffsets[region]; + var time = GetTime(offset); + builder + .AddField($"{region.Humanize()} Time", Format.Bold(Format.Code($"# {name} {time}", language))) + .AddField("Daily", + $"Resets in {Format.Bold(GetDailyReset(offset).TimeLeft().Humanize(4, minUnit: TimeUnit.Minute))}", + true) + .AddField("Weekly", + $"Resets in {Format.Bold(GetWeeklyReset(offset).TimeLeft().Humanize(4, minUnit: TimeUnit.Minute))}", + true); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/DateTimeExtensions.cs b/Zhongli.Services/Utilities/DateTimeExtensions.cs index 906ca14..4ef7c5a 100644 --- a/Zhongli.Services/Utilities/DateTimeExtensions.cs +++ b/Zhongli.Services/Utilities/DateTimeExtensions.cs @@ -3,47 +3,46 @@ using Discord; using static Zhongli.Services.Utilities.DateTimeExtensions.TimestampStyle; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class DateTimeExtensions { - public static class DateTimeExtensions + public enum TimestampStyle { - public enum TimestampStyle - { - ShortTime, - LongTime, - ShortDate, - LongDate, - ShortDateTime, - LongDateTime, - RelativeTime, - Default = ShortDateTime - } - - private static readonly Dictionary TimestampStyles = new() - { - [ShortTime] = "t", - [LongTime] = "T", - [ShortDate] = "d", - [LongDate] = "D", - [ShortDateTime] = "f", - [LongDateTime] = "F", - [RelativeTime] = "R" - }; - - public static string ToDiscordTimestamp(this TimeSpan length, TimestampStyle style = Default) - => ToDiscordTimestamp(DateTimeOffset.UtcNow + length, style); - - public static string ToDiscordTimestamp(this DateTimeOffset date, TimestampStyle style = Default) - => $""; - - public static string ToUniversalTimestamp(this DateTimeOffset date) - => $"{Format.Bold(date.ToDiscordTimestamp(RelativeTime))} ({date.ToDiscordTimestamp()})"; - - public static string ToUniversalTimestamp(this TimeSpan length) - => ToUniversalTimestamp(DateTimeOffset.UtcNow + length); - - public static TimeSpan TimeLeft(this DateTimeOffset date) => date.ToUniversalTime() - DateTimeOffset.UtcNow; - - public static TimeSpan TimeLeft(this DateTime date) => date.ToUniversalTime() - DateTime.UtcNow; + ShortTime, + LongTime, + ShortDate, + LongDate, + ShortDateTime, + LongDateTime, + RelativeTime, + Default = ShortDateTime } + + private static readonly Dictionary TimestampStyles = new() + { + [ShortTime] = "t", + [LongTime] = "T", + [ShortDate] = "d", + [LongDate] = "D", + [ShortDateTime] = "f", + [LongDateTime] = "F", + [RelativeTime] = "R" + }; + + public static string ToDiscordTimestamp(this TimeSpan length, TimestampStyle style = Default) + => ToDiscordTimestamp(DateTimeOffset.UtcNow + length, style); + + public static string ToDiscordTimestamp(this DateTimeOffset date, TimestampStyle style = Default) + => $""; + + public static string ToUniversalTimestamp(this DateTimeOffset date) + => $"{Format.Bold(date.ToDiscordTimestamp(RelativeTime))} ({date.ToDiscordTimestamp()})"; + + public static string ToUniversalTimestamp(this TimeSpan length) + => ToUniversalTimestamp(DateTimeOffset.UtcNow + length); + + public static TimeSpan TimeLeft(this DateTimeOffset date) => date.ToUniversalTime() - DateTimeOffset.UtcNow; + + public static TimeSpan TimeLeft(this DateTime date) => date.ToUniversalTime() - DateTime.UtcNow; } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/DbSetExtensions.cs b/Zhongli.Services/Utilities/DbSetExtensions.cs index a5795a8..f4e45e0 100644 --- a/Zhongli.Services/Utilities/DbSetExtensions.cs +++ b/Zhongli.Services/Utilities/DbSetExtensions.cs @@ -9,77 +9,76 @@ using Zhongli.Data.Models.Moderation; using Zhongli.Data.Models.Moderation.Infractions.Reprimands; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class DbSetExtensions { - public static class DbSetExtensions + public static async Task TrackGuildAsync(this DbSet set, IGuild guild, + CancellationToken cancellationToken = default) { - public static async Task TrackGuildAsync(this DbSet set, IGuild guild, - CancellationToken cancellationToken = default) - { - var guildEntity = await set.FindByIdAsync(guild.Id, cancellationToken) - ?? set.Add(new GuildEntity(guild.Id)).Entity; + var guildEntity = await set.FindByIdAsync(guild.Id, cancellationToken) + ?? set.Add(new GuildEntity(guild.Id)).Entity; - // ReSharper disable ConstantNullCoalescingCondition - guildEntity.ModerationRules ??= new ModerationRules(); - guildEntity.LoggingRules ??= new LoggingRules(); - // ReSharper restore ConstantNullCoalescingCondition + // ReSharper disable ConstantNullCoalescingCondition + guildEntity.ModerationRules ??= new ModerationRules(); + guildEntity.LoggingRules ??= new LoggingRules(); + // ReSharper restore ConstantNullCoalescingCondition - return guildEntity; - } + return guildEntity; + } - public static async Task TrackEmoteAsync(this DbContext db, IEmote reaction, - CancellationToken cancellationToken = default) + public static async Task TrackEmoteAsync(this DbContext db, IEmote reaction, + CancellationToken cancellationToken = default) + { + if (reaction is Emote emote) { - if (reaction is Emote emote) - { - var entity = await db.Set().ToAsyncEnumerable() - .FirstOrDefaultAsync(e => e.EmoteId == emote.Id, cancellationToken); + var entity = await db.Set().ToAsyncEnumerable() + .FirstOrDefaultAsync(e => e.EmoteId == emote.Id, cancellationToken); - return entity ?? db.Add(new EmoteEntity(emote)).Entity; - } - else - { - var entity = await db.Set().ToAsyncEnumerable() - .FirstOrDefaultAsync(e => e.Name == reaction.Name, cancellationToken); - - return entity ?? db.Add(new EmojiEntity(reaction)).Entity; - } + return entity ?? db.Add(new EmoteEntity(emote)).Entity; } - - public static async ValueTask TrackUserAsync(this DbSet set, IGuildUser user, - CancellationToken cancellationToken = default) + else { - var userEntity = await set - .FindAsync(new object[] { user.Id, user.Guild.Id }, cancellationToken); + var entity = await db.Set().ToAsyncEnumerable() + .FirstOrDefaultAsync(e => e.Name == reaction.Name, cancellationToken); - if (userEntity is null) - userEntity = set.Add(new GuildUserEntity(user)).Entity; - else if (user.Username is not null) - { - userEntity.Username = user.Username; - userEntity.Nickname = user.Nickname; - userEntity.DiscriminatorValue = user.DiscriminatorValue; - } - - return userEntity; + return entity ?? db.Add(new EmojiEntity(reaction)).Entity; } + } - public static async ValueTask TrackUserAsync( - this DbSet set, ReprimandDetails details, - CancellationToken cancellationToken = default) + public static async ValueTask TrackUserAsync(this DbSet set, IGuildUser user, + CancellationToken cancellationToken = default) + { + var userEntity = await set + .FindAsync(new object[] { user.Id, user.Guild.Id }, cancellationToken); + + if (userEntity is null) + userEntity = set.Add(new GuildUserEntity(user)).Entity; + else if (user.Username is not null) { - var user = await details.GetUserAsync(); - if (user is not null) return await set.TrackUserAsync(user, cancellationToken); + userEntity.Username = user.Username; + userEntity.Nickname = user.Nickname; + userEntity.DiscriminatorValue = user.DiscriminatorValue; + } + + return userEntity; + } - var userEntity = - await set.FindAsync(new object[] { details.User.Id, details.Guild.Id }, cancellationToken) - ?? set.Add(new GuildUserEntity(details.User, details.Guild)).Entity; + public static async ValueTask TrackUserAsync( + this DbSet set, ReprimandDetails details, + CancellationToken cancellationToken = default) + { + var user = await details.GetUserAsync(); + if (user is not null) return await set.TrackUserAsync(user, cancellationToken); - return userEntity; - } + var userEntity = + await set.FindAsync(new object[] { details.User.Id, details.Guild.Id }, cancellationToken) + ?? set.Add(new GuildUserEntity(details.User, details.Guild)).Entity; - public static ValueTask FindByIdAsync(this DbSet dbSet, object key, - CancellationToken cancellationToken = default) - where T : class => dbSet.FindAsync(new[] { key }, cancellationToken)!; + return userEntity; } + + public static ValueTask FindByIdAsync(this DbSet dbSet, object key, + CancellationToken cancellationToken = default) + where T : class => dbSet.FindAsync(new[] { key }, cancellationToken)!; } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/DictionaryCache.cs b/Zhongli.Services/Utilities/DictionaryCache.cs index d0100e7..5d8c438 100644 --- a/Zhongli.Services/Utilities/DictionaryCache.cs +++ b/Zhongli.Services/Utilities/DictionaryCache.cs @@ -1,26 +1,25 @@ using System; using System.Collections.Concurrent; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public class DictionaryCache where TKey : notnull { - public class DictionaryCache where TKey : notnull - { - private readonly ConcurrentDictionary _cachedItems = new(); - private readonly Func _func; + private readonly ConcurrentDictionary _cachedItems = new(); + private readonly Func _func; - public DictionaryCache(Func func) { _func = func; } + public DictionaryCache(Func func) { _func = func; } - public TValue this[TKey key] + public TValue this[TKey key] + { + get { - get - { - if (_cachedItems.TryGetValue(key, out var value)) - return value; + if (_cachedItems.TryGetValue(key, out var value)) + return value; - var val = _func(key); - _cachedItems[key] = val; - return val; - } + var val = _func(key); + _cachedItems[key] = val; + return val; } } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/EmbedBuilderExtensions.cs b/Zhongli.Services/Utilities/EmbedBuilderExtensions.cs index 9186cfb..6d1a0d6 100644 --- a/Zhongli.Services/Utilities/EmbedBuilderExtensions.cs +++ b/Zhongli.Services/Utilities/EmbedBuilderExtensions.cs @@ -4,136 +4,135 @@ using System.Text; using Discord; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +[Flags] +public enum AuthorOptions { - [Flags] - public enum AuthorOptions - { - None = 0, - IncludeId = 1 << 0, - UseFooter = 1 << 1, - UseThumbnail = 1 << 2, - Requested = 1 << 3 - } + None = 0, + IncludeId = 1 << 0, + UseFooter = 1 << 1, + UseThumbnail = 1 << 2, + Requested = 1 << 3 +} + +public static class EmbedBuilderExtensions +{ + public delegate (string Title, StringBuilder Value) EntityViewerDelegate(T entity); - public static class EmbedBuilderExtensions + public static EmbedAuthorBuilder WithGuildAsAuthor(this EmbedAuthorBuilder embed, IGuild guild, + AuthorOptions authorOptions = AuthorOptions.None) { - public delegate (string Title, StringBuilder Value) EntityViewerDelegate(T entity); + var name = guild.Name; + if (authorOptions.HasFlag(AuthorOptions.Requested)) + name = $"Requested from {name}"; - public static EmbedAuthorBuilder WithGuildAsAuthor(this EmbedAuthorBuilder embed, IGuild guild, - AuthorOptions authorOptions = AuthorOptions.None) - { - var name = guild.Name; - if (authorOptions.HasFlag(AuthorOptions.Requested)) - name = $"Requested from {name}"; + return embed.WithEntityAsAuthor(guild, name, guild.IconUrl, authorOptions); + } - return embed.WithEntityAsAuthor(guild, name, guild.IconUrl, authorOptions); - } + public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, + IEnumerable items, Func selector, string? separator = null) => + builder.AddItemsIntoFields(title, items.Select(selector), separator); - public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, - IEnumerable items, Func selector, string? separator = null) => - builder.AddItemsIntoFields(title, items.Select(selector), separator); + public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, + IEnumerable items, Func selector, string? separator = null) => + builder.AddItemsIntoFields(title, items.Select(selector), separator); - public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, - IEnumerable items, Func selector, string? separator = null) => - builder.AddItemsIntoFields(title, items.Select(selector), separator); + public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, + IEnumerable items, string? separator = null) + { + var splitLines = SplitItemsIntoChunks(items, separator: separator).ToArray(); - public static EmbedBuilder AddItemsIntoFields(this EmbedBuilder builder, string title, - IEnumerable items, string? separator = null) - { - var splitLines = SplitItemsIntoChunks(items, separator: separator).ToArray(); + if (!splitLines.Any()) return builder; - if (!splitLines.Any()) return builder; + builder.AddField(title, splitLines.First()); + foreach (var line in splitLines.Skip(1)) + { + builder.AddField("\x200b", line); + } - builder.AddField(title, splitLines.First()); - foreach (var line in splitLines.Skip(1)) - { - builder.AddField("\x200b", line); - } + return builder; + } - return builder; - } + public static EmbedBuilder WithGuildAsAuthor(this EmbedBuilder embed, IGuild? guild, + AuthorOptions authorOptions = AuthorOptions.None) + { + if (guild is null) return embed; - public static EmbedBuilder WithGuildAsAuthor(this EmbedBuilder embed, IGuild? guild, - AuthorOptions authorOptions = AuthorOptions.None) - { - if (guild is null) return embed; + var name = guild.Name; + if (authorOptions.HasFlag(AuthorOptions.Requested)) + name = $"Requested from {name}"; - var name = guild.Name; - if (authorOptions.HasFlag(AuthorOptions.Requested)) - name = $"Requested from {name}"; + return embed.WithEntityAsAuthor(guild, name, guild.IconUrl, authorOptions); + } - return embed.WithEntityAsAuthor(guild, name, guild.IconUrl, authorOptions); - } + public static EmbedBuilder WithUserAsAuthor(this EmbedBuilder embed, IUser user, + AuthorOptions authorOptions = AuthorOptions.None, ushort size = 128) + { + var username = user.GetFullUsername(); + if (authorOptions.HasFlag(AuthorOptions.Requested)) + username = $"Requested by {username}"; - public static EmbedBuilder WithUserAsAuthor(this EmbedBuilder embed, IUser user, - AuthorOptions authorOptions = AuthorOptions.None, ushort size = 128) - { - var username = user.GetFullUsername(); - if (authorOptions.HasFlag(AuthorOptions.Requested)) - username = $"Requested by {username}"; + return embed.WithEntityAsAuthor(user, username, user.GetDefiniteAvatarUrl(size), authorOptions); + } - return embed.WithEntityAsAuthor(user, username, user.GetDefiniteAvatarUrl(size), authorOptions); - } + public static IEnumerable ToEmbedFields(this IEnumerable collection, + EntityViewerDelegate entityViewer) + { + return collection + .Select(entityViewer.Invoke) + .Select((e, i) => new EmbedFieldBuilder() + .WithName($"{i}: {e.Title}") + .WithValue(e.Value)); + } - public static IEnumerable ToEmbedFields(this IEnumerable collection, - EntityViewerDelegate entityViewer) - { - return collection - .Select(entityViewer.Invoke) - .Select((e, i) => new EmbedFieldBuilder() - .WithName($"{i}: {e.Title}") - .WithValue(e.Value)); - } + public static IEnumerable SplitItemsIntoChunks(this IEnumerable items, + int maxLength = EmbedFieldBuilder.MaxFieldValueLength, string? separator = null) + { + var sb = new StringBuilder(0, maxLength); + var builders = new List(); - public static IEnumerable SplitItemsIntoChunks(this IEnumerable items, - int maxLength = EmbedFieldBuilder.MaxFieldValueLength, string? separator = null) + foreach (var item in items) { - var sb = new StringBuilder(0, maxLength); - var builders = new List(); - - foreach (var item in items) + if (sb.Length + (separator ?? Environment.NewLine).Length + item.Length > maxLength) { - if (sb.Length + (separator ?? Environment.NewLine).Length + item.Length > maxLength) - { - builders.Add(sb); - sb = new StringBuilder(0, maxLength); - } - - if (separator is null) - sb.AppendLine(item); - else - sb.Append(item).Append(separator); + builders.Add(sb); + sb = new StringBuilder(0, maxLength); } - builders.Add(sb); - - return builders - .Where(s => s.Length > 0) - .Select(s => s.ToString()); + if (separator is null) + sb.AppendLine(item); + else + sb.Append(item).Append(separator); } - private static EmbedAuthorBuilder WithEntityAsAuthor(this EmbedAuthorBuilder embed, IEntity entity, - string name, string iconUrl, AuthorOptions authorOptions) - { - if (authorOptions.HasFlag(AuthorOptions.IncludeId)) - name += $" ({entity.Id})"; + builders.Add(sb); - return embed.WithName(name).WithIconUrl(iconUrl); - } + return builders + .Where(s => s.Length > 0) + .Select(s => s.ToString()); + } - private static EmbedBuilder WithEntityAsAuthor(this EmbedBuilder embed, IEntity entity, - string name, string? iconUrl, AuthorOptions authorOptions) - { - if (authorOptions.HasFlag(AuthorOptions.IncludeId)) - name += $" ({entity.Id})"; + private static EmbedAuthorBuilder WithEntityAsAuthor(this EmbedAuthorBuilder embed, IEntity entity, + string name, string iconUrl, AuthorOptions authorOptions) + { + if (authorOptions.HasFlag(AuthorOptions.IncludeId)) + name += $" ({entity.Id})"; - if (authorOptions.HasFlag(AuthorOptions.UseThumbnail)) - embed.WithThumbnailUrl(iconUrl); + return embed.WithName(name).WithIconUrl(iconUrl); + } - return authorOptions.HasFlag(AuthorOptions.UseFooter) - ? embed.WithFooter(name, iconUrl) - : embed.WithAuthor(name, iconUrl); - } + private static EmbedBuilder WithEntityAsAuthor(this EmbedBuilder embed, IEntity entity, + string name, string? iconUrl, AuthorOptions authorOptions) + { + if (authorOptions.HasFlag(AuthorOptions.IncludeId)) + name += $" ({entity.Id})"; + + if (authorOptions.HasFlag(AuthorOptions.UseThumbnail)) + embed.WithThumbnailUrl(iconUrl); + + return authorOptions.HasFlag(AuthorOptions.UseFooter) + ? embed.WithFooter(name, iconUrl) + : embed.WithAuthor(name, iconUrl); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/EnumExtensions.cs b/Zhongli.Services/Utilities/EnumExtensions.cs index fd2da95..19585b9 100644 --- a/Zhongli.Services/Utilities/EnumExtensions.cs +++ b/Zhongli.Services/Utilities/EnumExtensions.cs @@ -3,33 +3,32 @@ using Discord; using Zhongli.Data.Models.Moderation; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class EnumExtensions { - public static class EnumExtensions - { - private static readonly GenericBitwise ReprimandNoticeTypeBitwise = new(); - private static readonly GenericBitwise ReprimandOptionsBitwise = new(); - private static readonly GenericBitwise GuildPermissionBitwise = new(); + private static readonly GenericBitwise ReprimandNoticeTypeBitwise = new(); + private static readonly GenericBitwise ReprimandOptionsBitwise = new(); + private static readonly GenericBitwise GuildPermissionBitwise = new(); - public static GuildPermissions ToGuildPermissions(this IEnumerable permissions) - => new((uint) GuildPermissionBitwise.Or(permissions)); + public static GuildPermissions ToGuildPermissions(this IEnumerable permissions) + => new((uint) GuildPermissionBitwise.Or(permissions)); - public static ReprimandNoticeType SetValue(this ReprimandNoticeType options, ReprimandNoticeType flag, - bool? state) - => ReprimandNoticeTypeBitwise.SetValue(options, flag, state); + public static ReprimandNoticeType SetValue(this ReprimandNoticeType options, ReprimandNoticeType flag, + bool? state) + => ReprimandNoticeTypeBitwise.SetValue(options, flag, state); - public static ReprimandOptions SetValue(this ReprimandOptions options, ReprimandOptions flag, bool? state) - => ReprimandOptionsBitwise.SetValue(options, flag, state); + public static ReprimandOptions SetValue(this ReprimandOptions options, ReprimandOptions flag, bool? state) + => ReprimandOptionsBitwise.SetValue(options, flag, state); - public static T SetValue(this GenericBitwise generic, T @enum, T flag, bool? state) - where T : Enum - { - if (state is null) - return generic.Xor(@enum, flag); + public static T SetValue(this GenericBitwise generic, T @enum, T flag, bool? state) + where T : Enum + { + if (state is null) + return generic.Xor(@enum, flag); - return state.Value - ? generic.Or(@enum, flag) - : generic.And(@enum, generic.Not(flag)); - } + return state.Value + ? generic.Or(@enum, flag) + : generic.And(@enum, generic.Not(flag)); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/EnumerableExtensions.cs b/Zhongli.Services/Utilities/EnumerableExtensions.cs index 8baa774..1e981ef 100644 --- a/Zhongli.Services/Utilities/EnumerableExtensions.cs +++ b/Zhongli.Services/Utilities/EnumerableExtensions.cs @@ -1,64 +1,63 @@ using System; using System.Collections.Generic; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class EnumerableExtensions { - public static class EnumerableExtensions + public static IEnumerable<(T Value, int Index)> AsIndexable(this IEnumerable source) { - public static IEnumerable<(T Value, int Index)> AsIndexable(this IEnumerable source) - { - var index = 0; + var index = 0; - foreach (var item in source) - { - yield return (item, index); + foreach (var item in source) + { + yield return (item, index); - index++; - } + index++; } + } - public static IEnumerable<(TFirst? First, TSecond? Second)> ZipOrDefault( - this IEnumerable first, IEnumerable second) - { - using var e1 = first.GetEnumerator(); - using var e2 = second.GetEnumerator(); + public static IEnumerable<(TFirst? First, TSecond? Second)> ZipOrDefault( + this IEnumerable first, IEnumerable second) + { + using var e1 = first.GetEnumerator(); + using var e2 = second.GetEnumerator(); - while (true) - { - var e1Moved = e1.MoveNext(); - var e2Moved = e2.MoveNext(); + while (true) + { + var e1Moved = e1.MoveNext(); + var e2Moved = e2.MoveNext(); - if (!e1Moved && !e2Moved) - break; + if (!e1Moved && !e2Moved) + break; - yield return - ( - e1Moved ? e1.Current : default, - e2Moved ? e2.Current : default - ); - } + yield return + ( + e1Moved ? e1.Current : default, + e2Moved ? e2.Current : default + ); } + } - public static IEnumerable SkipLast(this IEnumerable source, Func predicate) - { - var possibleTail = new Queue(); + public static IEnumerable SkipLast(this IEnumerable source, Func predicate) + { + var possibleTail = new Queue(); - foreach (var item in source) + foreach (var item in source) + { + if (!predicate(item)) { - if (!predicate(item)) + // We found an item that doesn't match the predicate, so + // anything we weren't sure about is now safe to return. + while (possibleTail.TryDequeue(out var queuedItem)) { - // We found an item that doesn't match the predicate, so - // anything we weren't sure about is now safe to return. - while (possibleTail.TryDequeue(out var queuedItem)) - { - yield return queuedItem; - } - - yield return item; + yield return queuedItem; } - else - possibleTail.Enqueue(item); + + yield return item; } + else + possibleTail.Enqueue(item); } } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/FormatUtilities.cs b/Zhongli.Services/Utilities/FormatUtilities.cs index 22533ba..71829a5 100644 --- a/Zhongli.Services/Utilities/FormatUtilities.cs +++ b/Zhongli.Services/Utilities/FormatUtilities.cs @@ -8,180 +8,179 @@ using Humanizer; using Humanizer.Localisation; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class FormatUtilities { - public static class FormatUtilities + private static readonly Regex BuildContentRegex = new(@"```([^\s]+|)", RegexOptions.Compiled); + private static readonly Regex UserMentionRegex = new("<@!?(?[0-9]+)>", RegexOptions.Compiled); + private static readonly Regex RoleMentionRegex = new("<@&(?[0-9]+)>", RegexOptions.Compiled); + private static readonly Regex ContainsSpoilerRegex = new(@"\|\|.+\|\|", RegexOptions.Compiled); + + public static bool ContainsSpoiler(string text) + => ContainsSpoilerRegex.IsMatch(text); + + /// + /// Collapses plural forms into a "singular(s)"-type format. + /// + /// The collection of sentences for which to collapse plurals. + /// A collection of formatted sentences. + public static IEnumerable CollapsePlurals(IEnumerable sentences) { - private static readonly Regex BuildContentRegex = new(@"```([^\s]+|)", RegexOptions.Compiled); - private static readonly Regex UserMentionRegex = new("<@!?(?[0-9]+)>", RegexOptions.Compiled); - private static readonly Regex RoleMentionRegex = new("<@&(?[0-9]+)>", RegexOptions.Compiled); - private static readonly Regex ContainsSpoilerRegex = new(@"\|\|.+\|\|", RegexOptions.Compiled); - - public static bool ContainsSpoiler(string text) - => ContainsSpoilerRegex.IsMatch(text); - - /// - /// Collapses plural forms into a "singular(s)"-type format. - /// - /// The collection of sentences for which to collapse plurals. - /// A collection of formatted sentences. - public static IEnumerable CollapsePlurals(IEnumerable sentences) - { - var splitIntoWords = sentences.Select(x => x.Split(" ", StringSplitOptions.RemoveEmptyEntries)); + var splitIntoWords = sentences.Select(x => x.Split(" ", StringSplitOptions.RemoveEmptyEntries)); + + var withSingulars = splitIntoWords.Select(x => + ( + Singular: x.Select(y => y.Singularize(false)).ToArray(), + Value: x + )); - var withSingulars = splitIntoWords.Select(x => - ( - Singular: x.Select(y => y.Singularize(false)).ToArray(), - Value: x - )); + var groupedBySingulars = + withSingulars.GroupBy(x => x.Singular, x => x.Value) + .ToList(); - var groupedBySingulars = - withSingulars.GroupBy(x => x.Singular, x => x.Value) - .ToList(); + var withDistinctParts = new HashSet[groupedBySingulars.Count][]; - var withDistinctParts = new HashSet[groupedBySingulars.Count][]; + foreach (var (singular, singularIndex) in groupedBySingulars.AsIndexable()) + { + var parts = new HashSet[singular.Key.Length]; - foreach (var (singular, singularIndex) in groupedBySingulars.AsIndexable()) + for (var i = 0; i < parts.Length; i++) { - var parts = new HashSet[singular.Key.Length]; + parts[i] = new HashSet(); + } - for (var i = 0; i < parts.Length; i++) + foreach (var variation in singular) + { + foreach (var (part, partIndex) in variation.AsIndexable()) { - parts[i] = new HashSet(); + parts[partIndex].Add(part); } + } - foreach (var variation in singular) - { - foreach (var (part, partIndex) in variation.AsIndexable()) - { - parts[partIndex].Add(part); - } - } + withDistinctParts[singularIndex] = parts; + } - withDistinctParts[singularIndex] = parts; - } + var parenthesized = new string[withDistinctParts.Length][]; - var parenthesized = new string[withDistinctParts.Length][]; + foreach (var (alias, aliasIndex) in withDistinctParts.AsIndexable()) + { + parenthesized[aliasIndex] = new string[alias.Length]; - foreach (var (alias, aliasIndex) in withDistinctParts.AsIndexable()) + foreach (var (word, wordIndex) in alias.AsIndexable()) { - parenthesized[aliasIndex] = new string[alias.Length]; - - foreach (var (word, wordIndex) in alias.AsIndexable()) + if (word.Count == 2) { - if (word.Count == 2) - { - var indexOfDifference = word.First() - .ZipOrDefault(word.Last()) - .AsIndexable() - .First(x => x.Value.First != x.Value.Second) - .Index; - - var longestForm = word.First().Length > word.Last().Length - ? word.First() - : word.Last(); - - parenthesized[aliasIndex][wordIndex] = - $"{longestForm[..indexOfDifference]}({longestForm[indexOfDifference..]})"; - } - else - parenthesized[aliasIndex][wordIndex] = word.Single(); + var indexOfDifference = word.First() + .ZipOrDefault(word.Last()) + .AsIndexable() + .First(x => x.Value.First != x.Value.Second) + .Index; + + var longestForm = word.First().Length > word.Last().Length + ? word.First() + : word.Last(); + + parenthesized[aliasIndex][wordIndex] = + $"{longestForm[..indexOfDifference]}({longestForm[indexOfDifference..]})"; } + else + parenthesized[aliasIndex][wordIndex] = word.Single(); } + } - var formatted = parenthesized.Select(aliasParts => string.Join(" ", aliasParts)).ToArray(); + var formatted = parenthesized.Select(aliasParts => string.Join(" ", aliasParts)).ToArray(); - return formatted; - } + return formatted; + } - /// - /// Attempts to fix the indentation of a piece of code by aligning the left sidie. - /// - /// The code to align - /// The newly aligned code - public static string FixIndentation(string code) + /// + /// Attempts to fix the indentation of a piece of code by aligning the left sidie. + /// + /// The code to align + /// The newly aligned code + public static string FixIndentation(string code) + { + var lines = code.Split('\n'); + var indentLine = lines.SkipWhile(d => d.FirstOrDefault() != ' ').FirstOrDefault(); + + if (indentLine is not null) { - var lines = code.Split('\n'); - var indentLine = lines.SkipWhile(d => d.FirstOrDefault() != ' ').FirstOrDefault(); + var indent = indentLine.LastIndexOf(' ') + 1; - if (indentLine is not null) - { - var indent = indentLine.LastIndexOf(' ') + 1; + var pattern = $@"^[^\S\n]{{{indent}}}"; - var pattern = $@"^[^\S\n]{{{indent}}}"; + return Regex.Replace(code, pattern, "", RegexOptions.Multiline); + } - return Regex.Replace(code, pattern, "", RegexOptions.Multiline); - } + return code; + } - return code; - } + public static string FormatTimeAgo(DateTimeOffset now, DateTimeOffset ago) + { + var span = now - ago; - public static string FormatTimeAgo(DateTimeOffset now, DateTimeOffset ago) - { - var span = now - ago; + var humanizedTimeAgo = span > TimeSpan.FromSeconds(60) + ? span.Humanize(maxUnit: TimeUnit.Year, culture: CultureInfo.InvariantCulture) + : "a few seconds"; - var humanizedTimeAgo = span > TimeSpan.FromSeconds(60) - ? span.Humanize(maxUnit: TimeUnit.Year, culture: CultureInfo.InvariantCulture) - : "a few seconds"; + return $"{humanizedTimeAgo} ago ({ago.UtcDateTime:yyyy-MM-ddTHH:mm:ssK})"; + } - return $"{humanizedTimeAgo} ago ({ago.UtcDateTime:yyyy-MM-ddTHH:mm:ssK})"; - } + public static string SanitizeAllMentions(string text) + { + var everyoneSanitized = SanitizeEveryone(text); + var userSanitized = SanitizeUserMentions(everyoneSanitized); + var roleSanitized = SanitizeRoleMentions(userSanitized); - public static string SanitizeAllMentions(string text) - { - var everyoneSanitized = SanitizeEveryone(text); - var userSanitized = SanitizeUserMentions(everyoneSanitized); - var roleSanitized = SanitizeRoleMentions(userSanitized); + return roleSanitized; + } - return roleSanitized; - } + public static string SanitizeEveryone(string text) + => text.Replace("@everyone", "@\x200beveryone") + .Replace("@here", "@\x200bhere"); - public static string SanitizeEveryone(string text) - => text.Replace("@everyone", "@\x200beveryone") - .Replace("@here", "@\x200bhere"); + public static string SanitizeRoleMentions(string text) + => RoleMentionRegex.Replace(text, "<@&\x200b${Id}>"); - public static string SanitizeRoleMentions(string text) - => RoleMentionRegex.Replace(text, "<@&\x200b${Id}>"); + public static string SanitizeUserMentions(string text) + => UserMentionRegex.Replace(text, "<@\x200b${Id}>"); - public static string SanitizeUserMentions(string text) - => UserMentionRegex.Replace(text, "<@\x200b${Id}>"); + public static string StripFormatting(string code) + { + var cleanCode = + BuildContentRegex.Replace(code.Trim(), + string.Empty); //strip out the ` characters and code block markers + cleanCode = cleanCode.Replace("\t", " "); //spaces > tabs + cleanCode = FixIndentation(cleanCode); + return cleanCode; + } - public static string StripFormatting(string code) + /// + /// Attempts to get the language of the code piece + /// + /// The code + /// The code language if a match is found, null of none are found + public static string? GetCodeLanguage(string message) + { + var match = BuildContentRegex.Match(message); + if (match.Success) { - var cleanCode = - BuildContentRegex.Replace(code.Trim(), - string.Empty); //strip out the ` characters and code block markers - cleanCode = cleanCode.Replace("\t", " "); //spaces > tabs - cleanCode = FixIndentation(cleanCode); - return cleanCode; + var codeLanguage = match.Groups[1].Value; + return string.IsNullOrEmpty(codeLanguage) ? null : codeLanguage; } - /// - /// Attempts to get the language of the code piece - /// - /// The code - /// The code language if a match is found, null of none are found - public static string? GetCodeLanguage(string message) - { - var match = BuildContentRegex.Match(message); - if (match.Success) - { - var codeLanguage = match.Groups[1].Value; - return string.IsNullOrEmpty(codeLanguage) ? null : codeLanguage; - } - - return null; - } + return null; + } - /// - /// Prepares a piece of input code for use in HTTP operations - /// - /// The code to prepare - /// The resulting StringContent for HTTP operations - public static StringContent BuildContent(string code) - { - var cleanCode = StripFormatting(code); - return new StringContent(cleanCode, Encoding.UTF8, "text/plain"); - } + /// + /// Prepares a piece of input code for use in HTTP operations + /// + /// The code to prepare + /// The resulting StringContent for HTTP operations + public static StringContent BuildContent(string code) + { + var cleanCode = StripFormatting(code); + return new StringContent(cleanCode, Encoding.UTF8, "text/plain"); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/GenericBitwise.cs b/Zhongli.Services/Utilities/GenericBitwise.cs index 60a7698..31a8b3a 100644 --- a/Zhongli.Services/Utilities/GenericBitwise.cs +++ b/Zhongli.Services/Utilities/GenericBitwise.cs @@ -3,117 +3,116 @@ using System.Linq; using System.Linq.Expressions; -namespace Zhongli.Services.Utilities -{ - public class GenericBitwise where T : Enum - { - private readonly Func _and; - private readonly Func _or; - private readonly Func _xor; - private readonly Func _not; +namespace Zhongli.Services.Utilities; - public GenericBitwise() - { - _and = And().Compile(); - _not = Not().Compile(); - _or = Or().Compile(); - _xor = Xor().Compile(); - } +public class GenericBitwise where T : Enum +{ + private readonly Func _and; + private readonly Func _or; + private readonly Func _xor; + private readonly Func _not; - public T All() - { - var allFlags = Enum.GetValues(typeof(T)).Cast(); - return Or(allFlags); - } + public GenericBitwise() + { + _and = And().Compile(); + _not = Not().Compile(); + _or = Or().Compile(); + _xor = Xor().Compile(); + } - public T And(T value1, T value2) => _and(value1, value2); + public T All() + { + var allFlags = Enum.GetValues(typeof(T)).Cast(); + return Or(allFlags); + } - public T And(IEnumerable list) => list.Aggregate(And); + public T And(T value1, T value2) => _and(value1, value2); - public T Not(T value) => _not(value); + public T And(IEnumerable list) => list.Aggregate(And); - public T Or(T value1, T value2) => _or(value1, value2); + public T Not(T value) => _not(value); - public T Or(IEnumerable list) => list.Aggregate(Or); + public T Or(T value1, T value2) => _or(value1, value2); - public T Xor(T value1, T value2) => _xor(value1, value2); + public T Or(IEnumerable list) => list.Aggregate(Or); - public T Xor(IEnumerable list) => list.Aggregate(Xor); + public T Xor(T value1, T value2) => _xor(value1, value2); - private static Expression> And() - { - Type underlyingType = Enum.GetUnderlyingType(typeof(T)); - var v1 = Expression.Parameter(typeof(T)); - var v2 = Expression.Parameter(typeof(T)); + public T Xor(IEnumerable list) => list.Aggregate(Xor); - return Expression.Lambda>( - Expression.Convert( - Expression.And( // combine the flags with an AND - Expression.Convert(v1, - underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) - Expression.Convert(v2, underlyingType) - ), - typeof(T) // convert the result of the AND back into the enum type + private static Expression> And() + { + var underlyingType = Enum.GetUnderlyingType(typeof(T)); + var v1 = Expression.Parameter(typeof(T)); + var v2 = Expression.Parameter(typeof(T)); + + return Expression.Lambda>( + Expression.Convert( + Expression.And( // combine the flags with an AND + Expression.Convert(v1, + underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) + Expression.Convert(v2, underlyingType) ), - v1, // the first argument of the function - v2 // the second argument of the function - ); - } - - private static Expression> Or() - { - Type underlyingType = Enum.GetUnderlyingType(typeof(T)); - var v1 = Expression.Parameter(typeof(T)); - var v2 = Expression.Parameter(typeof(T)); - - return Expression.Lambda>( - Expression.Convert( - Expression.Or( // combine the flags with an OR - Expression.Convert(v1, - underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) - Expression.Convert(v2, underlyingType) - ), - typeof(T) // convert the result of the OR back into the enum type + typeof(T) // convert the result of the AND back into the enum type + ), + v1, // the first argument of the function + v2 // the second argument of the function + ); + } + + private static Expression> Or() + { + var underlyingType = Enum.GetUnderlyingType(typeof(T)); + var v1 = Expression.Parameter(typeof(T)); + var v2 = Expression.Parameter(typeof(T)); + + return Expression.Lambda>( + Expression.Convert( + Expression.Or( // combine the flags with an OR + Expression.Convert(v1, + underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) + Expression.Convert(v2, underlyingType) ), - v1, // the first argument of the function - v2 // the second argument of the function - ); - } - - private static Expression> Xor() - { - Type underlyingType = Enum.GetUnderlyingType(typeof(T)); - var v1 = Expression.Parameter(typeof(T)); - var v2 = Expression.Parameter(typeof(T)); - - return Expression.Lambda>( - Expression.Convert( - Expression.ExclusiveOr( // combine the flags with an XOR - Expression.Convert(v1, - underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) - Expression.Convert(v2, underlyingType) - ), - typeof(T) // convert the result of the OR back into the enum type + typeof(T) // convert the result of the OR back into the enum type + ), + v1, // the first argument of the function + v2 // the second argument of the function + ); + } + + private static Expression> Xor() + { + var underlyingType = Enum.GetUnderlyingType(typeof(T)); + var v1 = Expression.Parameter(typeof(T)); + var v2 = Expression.Parameter(typeof(T)); + + return Expression.Lambda>( + Expression.Convert( + Expression.ExclusiveOr( // combine the flags with an XOR + Expression.Convert(v1, + underlyingType), // convert the values to a bit maskable type (i.e. the underlying numeric type of the enum) + Expression.Convert(v2, underlyingType) ), - v1, // the first argument of the function - v2 // the second argument of the function - ); - } - - private static Expression> Not() - { - Type underlyingType = Enum.GetUnderlyingType(typeof(T)); - var v1 = Expression.Parameter(typeof(T)); - - return Expression.Lambda>( - Expression.Convert( - Expression.Not( // ~ - Expression.Convert(v1, underlyingType) - ), - typeof(T) // convert the result of the tilde back into the enum type + typeof(T) // convert the result of the OR back into the enum type + ), + v1, // the first argument of the function + v2 // the second argument of the function + ); + } + + private static Expression> Not() + { + var underlyingType = Enum.GetUnderlyingType(typeof(T)); + var v1 = Expression.Parameter(typeof(T)); + + return Expression.Lambda>( + Expression.Convert( + Expression.Not( // ~ + Expression.Convert(v1, underlyingType) ), - v1 // the argument of the function - ); - } + typeof(T) // convert the result of the tilde back into the enum type + ), + v1 // the argument of the function + ); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/ImageExtensions.cs b/Zhongli.Services/Utilities/ImageExtensions.cs index 729c06b..51977e1 100644 --- a/Zhongli.Services/Utilities/ImageExtensions.cs +++ b/Zhongli.Services/Utilities/ImageExtensions.cs @@ -4,20 +4,19 @@ using DColor = Discord.Color; using SColor = System.Drawing.Color; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class ImageExtensions { - public static class ImageExtensions + public static Bitmap ToBitmap(this byte[] bytes) { - public static Bitmap ToBitmap(this byte[] bytes) - { - using var stream = new MemoryStream(bytes); - return (Bitmap) System.Drawing.Image.FromStream(stream); - } + using var stream = new MemoryStream(bytes); + return (Bitmap) System.Drawing.Image.FromStream(stream); + } - public static DColor ToDiscordColor(this QuantizedColor color) - { - var c = color.Color; - return (DColor) SColor.FromArgb(c.A, c.R, c.G, c.B); - } + public static DColor ToDiscordColor(this QuantizedColor color) + { + var c = color.Color; + return (DColor) SColor.FromArgb(c.A, c.R, c.G, c.B); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/MessageExtensions.cs b/Zhongli.Services/Utilities/MessageExtensions.cs index 3be1084..1b5350f 100644 --- a/Zhongli.Services/Utilities/MessageExtensions.cs +++ b/Zhongli.Services/Utilities/MessageExtensions.cs @@ -4,75 +4,74 @@ using Discord.Commands; using Discord.WebSocket; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class MessageExtensions { - public static class MessageExtensions + public static bool TryGetJumpUrl(Match match, out ulong guildId, out ulong channelId, out ulong messageId) => + ulong.TryParse(match.Groups["GuildId"].Value, out guildId) + & ulong.TryParse(match.Groups["ChannelId"].Value, out channelId) + & ulong.TryParse(match.Groups["MessageId"].Value, out messageId); + + public static string GetJumpUrlForEmbed(this IMessage message) + => Format.Url($"#{message.Channel.Name} (click here)", message.GetJumpUrl()); + + public static async Task GetMessageAsync(this BaseSocketClient client, ulong channelId, + ulong messageId, bool allowNsfw = false) + { + if (client.GetChannel(channelId) is not ITextChannel channel) + return null; + + return await GetMessageAsync(channel, messageId); + } + + public static async Task GetMessageAsync(this ICommandContext context, + ulong messageId, bool allowNsfw = false) + { + if (context.Channel is not ITextChannel textChannel) + return null; + + if (textChannel.IsNsfw && !allowNsfw) + return null; + + return await GetMessageAsync(textChannel, messageId); + } + + public static async Task GetMessageFromUrlAsync(this ICommandContext context, string jumpUrl, + bool allowNsfw = false) { - public static bool TryGetJumpUrl(Match match, out ulong guildId, out ulong channelId, out ulong messageId) => - ulong.TryParse(match.Groups["GuildId"].Value, out guildId) - & ulong.TryParse(match.Groups["ChannelId"].Value, out channelId) - & ulong.TryParse(match.Groups["MessageId"].Value, out messageId); - - public static string GetJumpUrlForEmbed(this IMessage message) - => Format.Url($"#{message.Channel.Name} (click here)", message.GetJumpUrl()); - - public static async Task GetMessageAsync(this BaseSocketClient client, ulong channelId, - ulong messageId, bool allowNsfw = false) - { - if (client.GetChannel(channelId) is not ITextChannel channel) - return null; - - return await GetMessageAsync(channel, messageId); - } - - public static async Task GetMessageAsync(this ICommandContext context, - ulong messageId, bool allowNsfw = false) - { - if (context.Channel is not ITextChannel textChannel) - return null; - - if (textChannel.IsNsfw && !allowNsfw) - return null; - - return await GetMessageAsync(textChannel, messageId); - } - - public static async Task GetMessageFromUrlAsync(this ICommandContext context, string jumpUrl, - bool allowNsfw = false) - { - if (!TryGetJumpUrl(RegexUtilities.JumpUrl.Match(jumpUrl), out _, out var channelId, out var messageId)) - return null; - - var channel = await context.Guild.GetTextChannelAsync(channelId); - return await GetMessageAsync(channel, messageId); - } - - public static async Task GetMessageFromUrlAsync(this BaseSocketClient client, string jumpUrl, - bool allowNsfw = false) - { - if (!TryGetJumpUrl(RegexUtilities.JumpUrl.Match(jumpUrl), out _, out var channelId, out var messageId)) - return null; - - return await client.GetMessageAsync(channelId, messageId, allowNsfw); - } - - private static async Task GetMessageAsync(this ITextChannel channel, ulong messageId, - bool allowHidden = false, bool allowNsfw = false) - { - if (channel.IsNsfw && !allowNsfw) - return null; - - var currentUser = await channel.Guild.GetCurrentUserAsync(); - var channelPermissions = currentUser.GetPermissions(channel); - - if (!channelPermissions.ViewChannel && !allowHidden) - return null; - - var cacheMode = channelPermissions.ReadMessageHistory - ? CacheMode.AllowDownload - : CacheMode.CacheOnly; - - return await channel.GetMessageAsync(messageId, cacheMode); - } + if (!TryGetJumpUrl(RegexUtilities.JumpUrl.Match(jumpUrl), out _, out var channelId, out var messageId)) + return null; + + var channel = await context.Guild.GetTextChannelAsync(channelId); + return await GetMessageAsync(channel, messageId); + } + + public static async Task GetMessageFromUrlAsync(this BaseSocketClient client, string jumpUrl, + bool allowNsfw = false) + { + if (!TryGetJumpUrl(RegexUtilities.JumpUrl.Match(jumpUrl), out _, out var channelId, out var messageId)) + return null; + + return await client.GetMessageAsync(channelId, messageId, allowNsfw); + } + + private static async Task GetMessageAsync(this ITextChannel channel, ulong messageId, + bool allowHidden = false, bool allowNsfw = false) + { + if (channel.IsNsfw && !allowNsfw) + return null; + + var currentUser = await channel.Guild.GetCurrentUserAsync(); + var channelPermissions = currentUser.GetPermissions(channel); + + if (!channelPermissions.ViewChannel && !allowHidden) + return null; + + var cacheMode = channelPermissions.ReadMessageHistory + ? CacheMode.AllowDownload + : CacheMode.CacheOnly; + + return await channel.GetMessageAsync(messageId, cacheMode); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/NotificationContext.cs b/Zhongli.Services/Utilities/NotificationContext.cs index 4f0c308..be425ae 100644 --- a/Zhongli.Services/Utilities/NotificationContext.cs +++ b/Zhongli.Services/Utilities/NotificationContext.cs @@ -1,18 +1,17 @@ using Discord.Commands; using MediatR; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public class NotificationContext : INotification { - public class NotificationContext : INotification + public NotificationContext(T message, ICommandContext context) { - public NotificationContext(T message, ICommandContext context) - { - Message = message; - Context = context; - } + Message = message; + Context = context; + } - public ICommandContext Context { get; } + public ICommandContext Context { get; } - public T Message { get; } - } + public T Message { get; } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/ReflectionExtensions.cs b/Zhongli.Services/Utilities/ReflectionExtensions.cs index f3c38bb..067bc1c 100644 --- a/Zhongli.Services/Utilities/ReflectionExtensions.cs +++ b/Zhongli.Services/Utilities/ReflectionExtensions.cs @@ -4,93 +4,92 @@ using System.Reflection; using Zhongli.Services.CommandHelp; -namespace Zhongli.Services.Utilities -{ - public static class ReflectionExtensions - { - private static readonly DictionaryCache> CachedPrimitives = - new(GetPrimitives); +namespace Zhongli.Services.Utilities; - private static readonly DictionaryCache> CachedLists = - new(GetLists); +public static class ReflectionExtensions +{ + private static readonly DictionaryCache> CachedPrimitives = + new(GetPrimitives); - private static readonly DictionaryCache> CachedProperties = - new(GetPublicProperties); + private static readonly DictionaryCache> CachedLists = + new(GetLists); - private static readonly DictionaryCache TypeCache = new(GetRealTypeInternal); + private static readonly DictionaryCache> CachedProperties = + new(GetPublicProperties); - private static readonly DictionaryCache<(MemberInfo, Type), Attribute?> AttributeCache = - new DictionaryCache<(MemberInfo Member, Type Type), Attribute?>(GetAttributeFromMember); + private static readonly DictionaryCache TypeCache = new(GetRealTypeInternal); - private static readonly DictionaryCache<(Enum, Type), Attribute?> EnumAttributeCache = - new(GetAttributeFromEnum); + private static readonly DictionaryCache<(MemberInfo, Type), Attribute?> AttributeCache = + new DictionaryCache<(MemberInfo Member, Type Type), Attribute?>(GetAttributeFromMember); - private static readonly IEnumerable EnumerableTypes = new[] - { - typeof(IEnumerable<>), - typeof(ICollection<>), - typeof(IReadOnlyCollection<>), - typeof(List<>), - typeof(IList<>), - typeof(IReadOnlyList<>) - }; + private static readonly DictionaryCache<(Enum, Type), Attribute?> EnumAttributeCache = + new(GetAttributeFromEnum); - public static bool IsEnumerableOfT(this Type type) - => type.IsGenericType && EnumerableTypes.Contains(type.GetGenericTypeDefinition()); + private static readonly IEnumerable EnumerableTypes = new[] + { + typeof(IEnumerable<>), + typeof(ICollection<>), + typeof(IReadOnlyCollection<>), + typeof(List<>), + typeof(IList<>), + typeof(IReadOnlyList<>) + }; - public static IReadOnlyCollection GetLists() => CachedLists[typeof(T)]; + public static bool IsEnumerableOfT(this Type type) + => type.IsGenericType && EnumerableTypes.Contains(type.GetGenericTypeDefinition()); - public static IReadOnlyCollection GetPrimitives() => CachedPrimitives[typeof(T)]; + public static IReadOnlyCollection GetLists() => CachedLists[typeof(T)]; - public static IReadOnlyCollection GetProperties() => CachedProperties[typeof(T)]; + public static IReadOnlyCollection GetPrimitives() => CachedPrimitives[typeof(T)]; - public static T? GetAttribute(this MemberInfo member) where T : Attribute - => AttributeCache[(member, typeof(T))] as T; + public static IReadOnlyCollection GetProperties() => CachedProperties[typeof(T)]; - public static T? GetAttributeOfEnum(this Enum obj) where T : Attribute - => EnumAttributeCache[(obj, typeof(T))] as T; + public static T? GetAttribute(this MemberInfo member) where T : Attribute + => AttributeCache[(member, typeof(T))] as T; - public static Type GetRealType(this Type type) => TypeCache[type]; + public static T? GetAttributeOfEnum(this Enum obj) where T : Attribute + => EnumAttributeCache[(obj, typeof(T))] as T; - public static Type GetRealType(this PropertyInfo property) => TypeCache[property.PropertyType]; + public static Type GetRealType(this Type type) => TypeCache[type]; - public static Type GetRealType(this ParameterHelpData type) => TypeCache[type.Type]; + public static Type GetRealType(this PropertyInfo property) => TypeCache[property.PropertyType]; - private static Attribute? GetAttributeFromEnum((Enum @enum, Type attribute) o) - { - var (@enum, attribute) = o; - var enumType = @enum.GetType(); - var name = Enum.GetName(enumType, @enum); - if (name is null) - return null; + public static Type GetRealType(this ParameterHelpData type) => TypeCache[type.Type]; - var field = enumType.GetField(name); - return field is null ? null : GetAttributeFromMember((field, attribute)); - } + private static Attribute? GetAttributeFromEnum((Enum @enum, Type attribute) o) + { + var (@enum, attribute) = o; + var enumType = @enum.GetType(); + var name = Enum.GetName(enumType, @enum); + if (name is null) + return null; + + var field = enumType.GetField(name); + return field is null ? null : GetAttributeFromMember((field, attribute)); + } - private static Attribute? GetAttributeFromMember((MemberInfo Member, Type Type) o) => - o.Member.GetCustomAttribute(o.Type); + private static Attribute? GetAttributeFromMember((MemberInfo Member, Type Type) o) => + o.Member.GetCustomAttribute(o.Type); - private static IReadOnlyCollection GetLists(Type t) - { - return CachedProperties[t] - .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) - .ToArray(); - } + private static IReadOnlyCollection GetLists(Type t) + { + return CachedProperties[t] + .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) + .ToArray(); + } - private static IReadOnlyCollection GetPrimitives(Type t) - { - return CachedProperties[t] - .Where(p => !p.PropertyType.IsGenericType) - .ToArray(); - } + private static IReadOnlyCollection GetPrimitives(Type t) + { + return CachedProperties[t] + .Where(p => !p.PropertyType.IsGenericType) + .ToArray(); + } - private static IReadOnlyCollection GetPublicProperties(this Type t) - => t.GetProperties().ToArray(); + private static IReadOnlyCollection GetPublicProperties(this Type t) + => t.GetProperties().ToArray(); - private static Type GetRealTypeInternal(Type type) => - type.IsGenericType - ? type.GetGenericArguments()[0] - : type; - } + private static Type GetRealTypeInternal(Type type) => + type.IsGenericType + ? type.GetGenericArguments()[0] + : type; } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/RegexUtilities.cs b/Zhongli.Services/Utilities/RegexUtilities.cs index f6539ee..3269130 100644 --- a/Zhongli.Services/Utilities/RegexUtilities.cs +++ b/Zhongli.Services/Utilities/RegexUtilities.cs @@ -1,19 +1,18 @@ using System.Text.RegularExpressions; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public class RegexUtilities { - public class RegexUtilities - { - private const string JumpLinkPattern - = @"(?\S+\s+\S*)?(?<)?https?://(?:(?:ptb|canary)\.)?discord(app)?\.com/channels/(?\d+)/(?\d+)/(?\d+)/?(?>)?(?\S*\s+\S+)?"; + private const string JumpLinkPattern + = @"(?\S+\s+\S*)?(?<)?https?://(?:(?:ptb|canary)\.)?discord(app)?\.com/channels/(?\d+)/(?\d+)/(?\d+)/?(?>)?(?\S*\s+\S+)?"; - private const string DiscordInvitePattern - = @"discord(?:\.com|app\.com|\.gg)[\/invite\/]?(?:[a-zA-Z0-9\-]{2,32})"; + private const string DiscordInvitePattern + = @"discord(?:\.com|app\.com|\.gg)[\/invite\/]?(?:[a-zA-Z0-9\-]{2,32})"; - public static Regex DiscordInvite { get; } - = new(DiscordInvitePattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static Regex DiscordInvite { get; } + = new(DiscordInvitePattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static Regex JumpUrl { get; } - = new(JumpLinkPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } + public static Regex JumpUrl { get; } + = new(JumpLinkPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/SequenceEqualityComparer.cs b/Zhongli.Services/Utilities/SequenceEqualityComparer.cs index dc2f291..0d26748 100644 --- a/Zhongli.Services/Utilities/SequenceEqualityComparer.cs +++ b/Zhongli.Services/Utilities/SequenceEqualityComparer.cs @@ -2,23 +2,22 @@ using System.Collections.Generic; using System.Linq; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public class SequenceEqualityComparer : IEqualityComparer> { - public class SequenceEqualityComparer : IEqualityComparer> + public bool Equals(IReadOnlyCollection? x, IReadOnlyCollection? y) + => x is not null && y is not null && x.SequenceEqual(y); + + public int GetHashCode(IReadOnlyCollection obj) { - public bool Equals(IReadOnlyCollection? x, IReadOnlyCollection? y) - => x is not null && y is not null && x.SequenceEqual(y); + var hashCode = new HashCode(); - public int GetHashCode(IReadOnlyCollection obj) + foreach (var item in obj) { - var hashCode = new HashCode(); - - foreach (var item in obj) - { - hashCode.Add(item); - } - - return hashCode.ToHashCode(); + hashCode.Add(item); } + + return hashCode.ToHashCode(); } } \ No newline at end of file diff --git a/Zhongli.Services/Utilities/UserExtensions.cs b/Zhongli.Services/Utilities/UserExtensions.cs index 7f64c6c..5d74cb2 100644 --- a/Zhongli.Services/Utilities/UserExtensions.cs +++ b/Zhongli.Services/Utilities/UserExtensions.cs @@ -2,26 +2,25 @@ using Discord; using Discord.WebSocket; -namespace Zhongli.Services.Utilities +namespace Zhongli.Services.Utilities; + +public static class UserExtensions { - public static class UserExtensions - { - public static bool HasRole(this IGuildUser user, ulong roleId) - => user.RoleIds.Contains(roleId); + public static bool HasRole(this IGuildUser user, ulong roleId) + => user.RoleIds.Contains(roleId); - public static bool HasRole(this IGuildUser user, IRole role) - => user.HasRole(role.Id); + public static bool HasRole(this IGuildUser user, IRole role) + => user.HasRole(role.Id); - public static bool HasRole(this SocketGuildUser user, ulong roleId) - => user.Roles.Any(r => r.Id == roleId); + public static bool HasRole(this SocketGuildUser user, ulong roleId) + => user.Roles.Any(r => r.Id == roleId); - public static bool HasRole(this SocketGuildUser user, IRole role) - => user.HasRole(role.Id); + public static bool HasRole(this SocketGuildUser user, IRole role) + => user.HasRole(role.Id); - public static string GetDefiniteAvatarUrl(this IUser user, ushort size = 128) - => user.GetAvatarUrl(size: size) ?? user.GetDefaultAvatarUrl(); + public static string GetDefiniteAvatarUrl(this IUser user, ushort size = 128) + => user.GetAvatarUrl(size: size) ?? user.GetDefaultAvatarUrl(); - public static string GetFullUsername(this IUser user) - => $"{user.Username}#{user.Discriminator}"; - } + public static string GetFullUsername(this IUser user) + => $"{user.Username}#{user.Discriminator}"; } \ No newline at end of file