From 9efa344f2043187025c8da25b61b6856f8d05204 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 19 Feb 2023 13:57:19 +0000 Subject: [PATCH 1/5] Switch to Int64 giveaway ID instead of ShortGuid This change fundamentally breaks older versions, and therefore requires an EF migration or reset. --- Present/Commands/GiveawayCommand.Create.cs | 30 ---------------- Present/Commands/GiveawayCommand.End.cs | 12 ++----- Present/Commands/GiveawayCommand.Redraw.cs | 12 ++----- .../Commands/GiveawayCommand.SetWinners.cs | 12 ++----- Present/Commands/GiveawayCommand.View.cs | 12 ++----- .../Commands/GiveawayCommand.ViewEntrants.cs | 12 ++----- Present/Commands/GiveawayCommand.cs | 34 +++++++++++++++++- .../GiveawayConfiguration.cs | 2 +- Present/Data/Giveaway.cs | 6 ++-- .../ShortGuidToBytesConverter.cs | 36 ------------------- Present/Present.csproj | 1 - Present/Services/GiveawayEntrantService.cs | 5 ++- Present/Services/GiveawayService.cs | 8 ++--- 13 files changed, 51 insertions(+), 131 deletions(-) delete mode 100644 Present/Data/ValueConverters/ShortGuidToBytesConverter.cs diff --git a/Present/Commands/GiveawayCommand.Create.cs b/Present/Commands/GiveawayCommand.Create.cs index 0ee528f..78583d2 100644 --- a/Present/Commands/GiveawayCommand.Create.cs +++ b/Present/Commands/GiveawayCommand.Create.cs @@ -100,34 +100,4 @@ public async Task CreateAsync(InteractionContext context, followup.AddEmbed(embed); await context.FollowUpAsync(followup).ConfigureAwait(false); } - - private static async Task ValidateTimeStamp( - InteractionContext context, - DiscordModalTextInput timeInput, - DiscordEmbedBuilder embed, - DiscordFollowupMessageBuilder followup - ) - { - if (!TimeStampUtility.TryParse(timeInput.Value, out DateTimeOffset endTime)) - { - Logger.Warn($"Provided time was invalid ({timeInput.Value}). Giveaway creation has been cancelled"); - embed.WithDescription(EmbedStrings.GiveawayCreation_InvalidTimestamp); - followup.AsEphemeral(); - followup.AddEmbed(embed); - await context.FollowUpAsync(followup).ConfigureAwait(false); - return default; - } - - if (endTime < DateTimeOffset.UtcNow) - { - Logger.Warn($"Provided time ({timeInput.Value}) is in the past. Giveaway creation has been cancelled"); - embed.WithDescription(EmbedStrings.GiveawayCreation_FutureTimestamp); - followup.AsEphemeral(); - followup.AddEmbed(embed); - await context.FollowUpAsync(followup).ConfigureAwait(false); - return default; - } - - return endTime; - } } diff --git a/Present/Commands/GiveawayCommand.End.cs b/Present/Commands/GiveawayCommand.End.cs index 72bc095..cd589ee 100644 --- a/Present/Commands/GiveawayCommand.End.cs +++ b/Present/Commands/GiveawayCommand.End.cs @@ -1,5 +1,4 @@ -using CSharpVitamins; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using Present.Data; @@ -13,20 +12,13 @@ internal sealed partial class GiveawayCommand [SlashCommand(CommandNames.End, CommandDescriptions.End, false)] [SlashRequireGuild] public async Task EndAsync(InteractionContext context, - [Option(OptionNames.Id, OptionDescriptions.EndGiveawayId)] string idRaw + [Option(OptionNames.Id, OptionDescriptions.EndGiveawayId)] long giveawayId ) { var embed = new DiscordEmbedBuilder(); embed.WithColor(DiscordColor.Red); embed.WithTitle(EmbedStrings.InvalidGiveawayId); - if (!ShortGuid.TryParse(idRaw, out ShortGuid giveawayId)) - { - embed.WithDescription(string.Format(EmbedStrings.InvalidId, idRaw)); - await context.CreateResponseAsync(embed, true).ConfigureAwait(false); - return; - } - DiscordGuild guild = context.Guild; if (!_giveawayService.TryGetGiveaway(giveawayId, out Giveaway? giveaway) || giveaway.GuildId != guild.Id) { diff --git a/Present/Commands/GiveawayCommand.Redraw.cs b/Present/Commands/GiveawayCommand.Redraw.cs index 2493ab7..eed8377 100644 --- a/Present/Commands/GiveawayCommand.Redraw.cs +++ b/Present/Commands/GiveawayCommand.Redraw.cs @@ -1,5 +1,4 @@ -using CSharpVitamins; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using Present.Data; @@ -12,7 +11,7 @@ internal sealed partial class GiveawayCommand [SlashCommand(CommandNames.Redraw, CommandDescriptions.Redraw, false)] [SlashRequireGuild] public async Task RedrawAsync(InteractionContext context, - [Option(OptionNames.Id, OptionDescriptions.RedrawGiveawayId)] string idRaw, + [Option(OptionNames.Id, OptionDescriptions.RedrawGiveawayId)] long giveawayId, [Option(OptionNames.KeepIds, OptionDescriptions.RedrawKeep)] string? keepIds = null ) { @@ -20,13 +19,6 @@ public async Task RedrawAsync(InteractionContext context, embed.WithColor(DiscordColor.Red); embed.WithTitle(EmbedStrings.InvalidGiveawayId); - if (!ShortGuid.TryParse(idRaw, out ShortGuid giveawayId)) - { - embed.WithDescription(string.Format(EmbedStrings.InvalidId, idRaw)); - await context.CreateResponseAsync(embed, true).ConfigureAwait(false); - return; - } - DiscordGuild guild = context.Guild; if (!_giveawayService.TryGetGiveaway(giveawayId, out Giveaway? giveaway) || giveaway.GuildId != guild.Id) { diff --git a/Present/Commands/GiveawayCommand.SetWinners.cs b/Present/Commands/GiveawayCommand.SetWinners.cs index 16dc028..83f2797 100644 --- a/Present/Commands/GiveawayCommand.SetWinners.cs +++ b/Present/Commands/GiveawayCommand.SetWinners.cs @@ -1,5 +1,4 @@ -using CSharpVitamins; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using Present.Data; @@ -12,7 +11,7 @@ internal sealed partial class GiveawayCommand [SlashCommand(CommandNames.SetWinners, CommandDescriptions.End, false)] [SlashRequireGuild] public async Task SetWinnersAsync(InteractionContext context, - [Option(OptionNames.Id, OptionDescriptions.EndGiveawayId)] string idRaw, + [Option(OptionNames.Id, OptionDescriptions.EndGiveawayId)] long giveawayId, [Option(OptionNames.WinnerCount, OptionDescriptions.WinnerCount)] long winnerCount ) { @@ -20,13 +19,6 @@ public async Task SetWinnersAsync(InteractionContext context, embed.WithColor(DiscordColor.Red); embed.WithTitle(EmbedStrings.InvalidGiveawayId); - if (!ShortGuid.TryParse(idRaw, out ShortGuid giveawayId)) - { - embed.WithDescription(string.Format(EmbedStrings.InvalidId, idRaw)); - await context.CreateResponseAsync(embed, true).ConfigureAwait(false); - return; - } - DiscordGuild guild = context.Guild; if (!_giveawayService.TryGetGiveaway(giveawayId, out Giveaway? giveaway) || giveaway.GuildId != guild.Id) { diff --git a/Present/Commands/GiveawayCommand.View.cs b/Present/Commands/GiveawayCommand.View.cs index 936a273..87b4c27 100644 --- a/Present/Commands/GiveawayCommand.View.cs +++ b/Present/Commands/GiveawayCommand.View.cs @@ -1,5 +1,4 @@ -using CSharpVitamins; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using Present.Data; @@ -12,20 +11,13 @@ internal sealed partial class GiveawayCommand [SlashCommand(CommandNames.View, CommandDescriptions.ViewGiveaway, false)] [SlashRequireGuild] public async Task ViewAsync(InteractionContext context, - [Option(OptionNames.Id, OptionDescriptions.ViewGiveawayId)] string idRaw + [Option(OptionNames.Id, OptionDescriptions.ViewGiveawayId)] long giveawayId ) { var embed = new DiscordEmbedBuilder(); embed.WithColor(DiscordColor.Red); embed.WithTitle(EmbedStrings.InvalidGiveawayId); - if (!ShortGuid.TryParse(idRaw, out ShortGuid giveawayId)) - { - embed.WithDescription(string.Format(EmbedStrings.InvalidId, idRaw)); - await context.CreateResponseAsync(embed, true).ConfigureAwait(false); - return; - } - DiscordGuild guild = context.Guild; if (!_giveawayService.TryGetGiveaway(giveawayId, out Giveaway? giveaway) || giveaway.GuildId != guild.Id) { diff --git a/Present/Commands/GiveawayCommand.ViewEntrants.cs b/Present/Commands/GiveawayCommand.ViewEntrants.cs index 4d50b49..605379b 100644 --- a/Present/Commands/GiveawayCommand.ViewEntrants.cs +++ b/Present/Commands/GiveawayCommand.ViewEntrants.cs @@ -1,5 +1,4 @@ -using CSharpVitamins; -using DSharpPlus.Entities; +using DSharpPlus.Entities; using DSharpPlus.SlashCommands; using DSharpPlus.SlashCommands.Attributes; using Present.Data; @@ -13,20 +12,13 @@ internal sealed partial class GiveawayCommand [SlashCommand(CommandNames.ViewEntrants, CommandDescriptions.ViewEntrants, false)] [SlashRequireGuild] public async Task ViewEntrantsAsync(InteractionContext context, - [Option(OptionNames.Id, OptionDescriptions.ViewGiveawayId)] string idRaw + [Option(OptionNames.Id, OptionDescriptions.ViewGiveawayId)] long giveawayId ) { var embed = new DiscordEmbedBuilder(); embed.WithColor(DiscordColor.Red); embed.WithTitle(EmbedStrings.InvalidGiveawayId); - if (!ShortGuid.TryParse(idRaw, out ShortGuid giveawayId)) - { - embed.WithDescription(string.Format(EmbedStrings.InvalidId, idRaw)); - await context.CreateResponseAsync(embed, true).ConfigureAwait(false); - return; - } - DiscordGuild guild = context.Guild; if (!_giveawayService.TryGetGiveaway(giveawayId, out Giveaway? giveaway) || giveaway.GuildId != guild.Id) { diff --git a/Present/Commands/GiveawayCommand.cs b/Present/Commands/GiveawayCommand.cs index a1eaaee..7bea59e 100644 --- a/Present/Commands/GiveawayCommand.cs +++ b/Present/Commands/GiveawayCommand.cs @@ -1,7 +1,9 @@ -using DSharpPlus.SlashCommands; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; using Present.Resources; using Present.Services; using NLog; +using Present.Interactivity; namespace Present.Commands; @@ -41,4 +43,34 @@ UserExclusionService userExclusionService _roleExclusionService = roleExclusionService; _userExclusionService = userExclusionService; } + + private static async Task ValidateTimeStamp( + InteractionContext context, + DiscordModalTextInput timeInput, + DiscordEmbedBuilder embed, + DiscordFollowupMessageBuilder followup + ) + { + if (!TimeStampUtility.TryParse(timeInput.Value, out DateTimeOffset endTime)) + { + Logger.Warn($"Provided time was invalid ({timeInput.Value}). Giveaway creation has been cancelled"); + embed.WithDescription(EmbedStrings.GiveawayCreation_InvalidTimestamp); + followup.AsEphemeral(); + followup.AddEmbed(embed); + await context.FollowUpAsync(followup).ConfigureAwait(false); + return default; + } + + if (endTime < DateTimeOffset.UtcNow) + { + Logger.Warn($"Provided time ({timeInput.Value}) is in the past. Giveaway creation has been cancelled"); + embed.WithDescription(EmbedStrings.GiveawayCreation_FutureTimestamp); + followup.AsEphemeral(); + followup.AddEmbed(embed); + await context.FollowUpAsync(followup).ConfigureAwait(false); + return default; + } + + return endTime; + } } diff --git a/Present/Data/EntityConfigurations/GiveawayConfiguration.cs b/Present/Data/EntityConfigurations/GiveawayConfiguration.cs index a2ec64a..06f47ac 100644 --- a/Present/Data/EntityConfigurations/GiveawayConfiguration.cs +++ b/Present/Data/EntityConfigurations/GiveawayConfiguration.cs @@ -16,7 +16,7 @@ public void Configure(EntityTypeBuilder builder) builder.ToTable(nameof(Giveaway)); builder.HasKey(e => e.Id); - builder.Property(e => e.Id).HasConversion(); + builder.Property(e => e.Id); builder.Property(e => e.StartTime).HasConversion(); builder.Property(e => e.EndTime).HasConversion(); builder.Property(e => e.EndHandled); diff --git a/Present/Data/Giveaway.cs b/Present/Data/Giveaway.cs index 3fad465..c3acf49 100644 --- a/Present/Data/Giveaway.cs +++ b/Present/Data/Giveaway.cs @@ -1,6 +1,4 @@ -using CSharpVitamins; - -namespace Present.Data; +namespace Present.Data; /// /// Represents a giveaway. @@ -53,7 +51,7 @@ internal sealed class Giveaway : IEquatable /// Gets or sets the ID of the giveaway. /// /// The giveaway ID. - public ShortGuid Id { get; set; } + public long Id { get; set; } /// /// Gets or sets the image URI for this giveaway. diff --git a/Present/Data/ValueConverters/ShortGuidToBytesConverter.cs b/Present/Data/ValueConverters/ShortGuidToBytesConverter.cs deleted file mode 100644 index 69b1413..0000000 --- a/Present/Data/ValueConverters/ShortGuidToBytesConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using CSharpVitamins; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Present.Data.ValueConverters; - -/// -/// Converts a of values to and from an array of bytes. -/// -internal sealed class ShortGuidToBytesConverter : ValueConverter -{ - /// - /// Initializes a new instance of the class. - /// - public ShortGuidToBytesConverter() - : this(null) - { - } - - /// - /// Initializes a new instance of the class. - /// - public ShortGuidToBytesConverter(ConverterMappingHints? mappingHints) - : base(v => ToBytes(v), v => FromBytes(v), mappingHints) - { - } - - private static ShortGuid FromBytes(byte[] bytes) - { - return new ShortGuid(new Guid(bytes)); - } - - private static byte[] ToBytes(ShortGuid value) - { - return value.Guid.ToByteArray(); - } -} diff --git a/Present/Present.csproj b/Present/Present.csproj index ba63239..197fc83 100644 --- a/Present/Present.csproj +++ b/Present/Present.csproj @@ -28,7 +28,6 @@ - diff --git a/Present/Services/GiveawayEntrantService.cs b/Present/Services/GiveawayEntrantService.cs index 386716a..00d3e26 100644 --- a/Present/Services/GiveawayEntrantService.cs +++ b/Present/Services/GiveawayEntrantService.cs @@ -1,4 +1,3 @@ -using CSharpVitamins; using DSharpPlus; using DSharpPlus.Entities; using DSharpPlus.EventArgs; @@ -216,7 +215,7 @@ private async Task DiscordClientOnComponentInteractionCreated(DiscordClient send { if (e.User is not DiscordMember member) return; // interaction happened outside of guild if (!e.Id.StartsWith("join-ga-")) return; // not a valid giveaway button - if (!ShortGuid.TryParse(e.Id[8..], out ShortGuid giveawayId)) + if (!long.TryParse(e.Id[8..], out long giveawayId)) { Logger.Warn("Component starting with prefix 'join-ga-' was not one of ours. We shouldn't receive this message!"); return; @@ -225,7 +224,7 @@ private async Task DiscordClientOnComponentInteractionCreated(DiscordClient send await HandleJoinAsync(e.Interaction, giveawayId, member).ConfigureAwait(false); } - private async Task HandleJoinAsync(DiscordInteraction interaction, ShortGuid giveawayId, DiscordMember member) + private async Task HandleJoinAsync(DiscordInteraction interaction, long giveawayId, DiscordMember member) { const InteractionResponseType responseType = InteractionResponseType.ChannelMessageWithSource; var builder = new DiscordInteractionResponseBuilder(); diff --git a/Present/Services/GiveawayService.cs b/Present/Services/GiveawayService.cs index c789b86..6d9a5fc 100644 --- a/Present/Services/GiveawayService.cs +++ b/Present/Services/GiveawayService.cs @@ -1,6 +1,5 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; -using CSharpVitamins; using DSharpPlus; using DSharpPlus.Entities; using Present.Configuration; @@ -31,7 +30,7 @@ internal sealed class GiveawayService : BackgroundService private readonly UserExclusionService _userExclusionService; private readonly DiscordClient _discordClient; private readonly Random _random; - private readonly ConcurrentDictionary _giveaways = new(); + private readonly ConcurrentDictionary _giveaways = new(); /// /// Initializes a new instance of the class. @@ -139,7 +138,6 @@ public async Task CreateGiveawayAsync(GiveawayCreationOptions options) var giveaway = new Giveaway { - Id = Guid.NewGuid(), StartTime = options.StartTime, EndTime = options.EndTime, CreatorId = options.Creator.Id, @@ -306,10 +304,10 @@ public async Task EndGiveawayAsync(Giveaway giveaway) /// . /// /// if a matching giveaway was found; otherwise, . - public bool TryGetGiveaway(ShortGuid id, [NotNullWhen(true)] out Giveaway? giveaway) + public bool TryGetGiveaway(long id, [NotNullWhen(true)] out Giveaway? giveaway) { giveaway = null; - return id != ShortGuid.Empty && _giveaways.TryGetValue(id, out giveaway); + return _giveaways.TryGetValue(id, out giveaway); } /// From c7ea91e73b6f184414cc32fb77eb39a283672909 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 19 Feb 2023 17:18:39 +0000 Subject: [PATCH 2/5] Update dependencies - Microsoft.EntityFrameworkCore.* 7.0.3 - Microsoft.Extensions.Hosting 7.0.1 - NLog 5.1.2 - NLog.Extensions.Logging 5.2.2 - X10D.* 3.2.0-nightly.152 --- Present/Present.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Present/Present.csproj b/Present/Present.csproj index 197fc83..1d7c4a3 100644 --- a/Present/Present.csproj +++ b/Present/Present.csproj @@ -31,18 +31,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - + + + From 23002e94d7fca0d2fb9ab0dfc798cad9334b9c07 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 19 Feb 2023 17:19:14 +0000 Subject: [PATCH 3/5] Add per-giveaway exclusion columns This change fundamentally breaks older versions, and therefore requires an EF migration or reset. --- .../EntityConfigurations/GiveawayConfiguration.cs | 2 ++ Present/Data/Giveaway.cs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Present/Data/EntityConfigurations/GiveawayConfiguration.cs b/Present/Data/EntityConfigurations/GiveawayConfiguration.cs index 06f47ac..147750d 100644 --- a/Present/Data/EntityConfigurations/GiveawayConfiguration.cs +++ b/Present/Data/EntityConfigurations/GiveawayConfiguration.cs @@ -27,6 +27,8 @@ public void Configure(EntityTypeBuilder builder) builder.Property(e => e.Description); builder.Property(e => e.ImageUri).HasConversion(); builder.Property(e => e.Entrants).HasConversion(); + builder.Property(e => e.ExcludedRoles).HasConversion(); + builder.Property(e => e.ExcludedUsers).HasConversion(); builder.Property(e => e.WinnerCount); builder.Property(e => e.WinnerIds).HasConversion(); builder.Property(e => e.MessageId); diff --git a/Present/Data/Giveaway.cs b/Present/Data/Giveaway.cs index c3acf49..e5370e8 100644 --- a/Present/Data/Giveaway.cs +++ b/Present/Data/Giveaway.cs @@ -41,6 +41,18 @@ internal sealed class Giveaway : IEquatable /// The end date and time. public DateTimeOffset EndTime { get; set; } + /// + /// Gets or sets the list of excluded role IDs for this giveaway. + /// + /// A of representing the excluded role IDs. + public List ExcludedRoles { get; set; } = new(); + + /// + /// Gets or sets the list of excluded user IDs for this giveaway. + /// + /// A of representing the excluded user IDs. + public List ExcludedUsers { get; set; } = new(); + /// /// Gets or sets the guild ID of the giveaway. /// From cc832fe0010c9bbfb19eab1db4875631e9fbd08f Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 19 Feb 2023 17:19:39 +0000 Subject: [PATCH 4/5] Fetch user fresh, rather than using cache, when validating --- Present/Commands/GiveawayCommand.Redraw.cs | 16 +++-- .../Commands/GiveawayCommand.SetWinners.cs | 2 +- Present/Commands/GiveawayCommand.View.cs | 2 +- Present/Services/ActiveGiveawayService.cs | 2 +- Present/Services/GiveawayService.cs | 67 ++++++++++++------- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/Present/Commands/GiveawayCommand.Redraw.cs b/Present/Commands/GiveawayCommand.Redraw.cs index eed8377..844ba92 100644 --- a/Present/Commands/GiveawayCommand.Redraw.cs +++ b/Present/Commands/GiveawayCommand.Redraw.cs @@ -41,22 +41,26 @@ public async Task RedrawAsync(InteractionContext context, { foreach (string keepId in keepIds.Split((char[]?) null, StringSplitOptions.RemoveEmptyEntries)) { - if (ulong.TryParse(keepId, out ulong userId) && - _giveawayService.ValidateUser(userId, guild, out DiscordMember? member)) - keep.Add(member); - else + if (!ulong.TryParse(keepId, out ulong userId)) + { invalidKeepIds.Add(keepId); + continue; + } + + DiscordMember? member = await _giveawayService.ValidateUserAsync(userId, giveaway).ConfigureAwait(false); + if (member is null) invalidKeepIds.Add(keepId); + else keep.Add(member); } } - IReadOnlyList winners = _giveawayService.SelectWinners(giveaway, keep); + IReadOnlyList winners = await _giveawayService.SelectWinnersAsync(giveaway, keep).ConfigureAwait(false); await _giveawayService.UpdateWinnersAsync(giveaway, winners).ConfigureAwait(false); await _giveawayService.EditGiveawayAsync(giveaway).ConfigureAwait(false); await _giveawayService.UpdateGiveawayLogMessageAsync(giveaway).ConfigureAwait(false); await _giveawayService.UpdateGiveawayPublicMessageAsync(giveaway).ConfigureAwait(false); - embed = _giveawayService.CreateGiveawayInformationEmbed(giveaway); + embed = await _giveawayService.CreateGiveawayInformationEmbedAsync(giveaway).ConfigureAwait(false); embed.WithColor(DiscordColor.Green); embed.WithTitle(EmbedStrings.GiveawayEdited_Title); diff --git a/Present/Commands/GiveawayCommand.SetWinners.cs b/Present/Commands/GiveawayCommand.SetWinners.cs index 83f2797..17a7164 100644 --- a/Present/Commands/GiveawayCommand.SetWinners.cs +++ b/Present/Commands/GiveawayCommand.SetWinners.cs @@ -47,7 +47,7 @@ public async Task SetWinnersAsync(InteractionContext context, await _giveawayService.UpdateGiveawayLogMessageAsync(giveaway).ConfigureAwait(false); await _giveawayService.UpdateGiveawayPublicMessageAsync(giveaway).ConfigureAwait(false); - embed = _giveawayService.CreateGiveawayInformationEmbed(giveaway); + embed = await _giveawayService.CreateGiveawayInformationEmbedAsync(giveaway).ConfigureAwait(false); embed.WithColor(DiscordColor.Green); embed.WithTitle(EmbedStrings.GiveawayEdited_Title); await context.CreateResponseAsync(embed).ConfigureAwait(false); diff --git a/Present/Commands/GiveawayCommand.View.cs b/Present/Commands/GiveawayCommand.View.cs index 87b4c27..d99c603 100644 --- a/Present/Commands/GiveawayCommand.View.cs +++ b/Present/Commands/GiveawayCommand.View.cs @@ -26,7 +26,7 @@ public async Task ViewAsync(InteractionContext context, return; } - embed = _giveawayService.CreateGiveawayInformationEmbed(giveaway); + embed = await _giveawayService.CreateGiveawayInformationEmbedAsync(giveaway).ConfigureAwait(false); embed.WithTitle(EmbedStrings.GiveawayInformation); await context.CreateResponseAsync(embed).ConfigureAwait(false); diff --git a/Present/Services/ActiveGiveawayService.cs b/Present/Services/ActiveGiveawayService.cs index 194eca2..75ce8ed 100644 --- a/Present/Services/ActiveGiveawayService.cs +++ b/Present/Services/ActiveGiveawayService.cs @@ -147,7 +147,7 @@ private async Task HandleExpiredGiveawayAsync(Giveaway giveaway) giveaway.EndHandled = true; RemoveActiveGiveaway(giveaway); - IReadOnlyList winners = _giveawayService.SelectWinners(giveaway); + IReadOnlyList winners = await _giveawayService.SelectWinnersAsync(giveaway).ConfigureAwait(false); Logger.Info($"Selected {"winner".ToQuantity(winners.Count)} for giveaway {giveaway.Id} ({giveaway.Title})"); foreach (DiscordMember winner in winners) Logger.Info(winner); diff --git a/Present/Services/GiveawayService.cs b/Present/Services/GiveawayService.cs index 6d9a5fc..c3c3859 100644 --- a/Present/Services/GiveawayService.cs +++ b/Present/Services/GiveawayService.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Hosting; using NLog; using SmartFormat; -using X10D.Collections; using X10D.Core; using X10D.DSharpPlus; @@ -169,7 +168,7 @@ public async Task CreateGiveawayAsync(GiveawayCreationOptions options) /// The giveaway whose information to fetch. /// A , populated with information from . /// is . - public DiscordEmbedBuilder CreateGiveawayInformationEmbed(Giveaway giveaway) + public async Task CreateGiveawayInformationEmbedAsync(Giveaway giveaway) { ArgumentNullException.ThrowIfNull(giveaway); @@ -188,7 +187,14 @@ public DiscordEmbedBuilder CreateGiveawayInformationEmbed(Giveaway giveaway) var jumpLink = new Uri($"https://discord.com/channels/{giveaway.GuildId}/{giveaway.ChannelId}/{giveaway.MessageId}"); string conjugatedEnd = giveaway.EndTime < DateTimeOffset.UtcNow ? EmbedStrings.Ended : EmbedStrings.Ends; var entrantsCount = giveaway.Entrants.Count.ToString("N0"); - var excludedEntrantsCount = giveaway.Entrants.CountWhereNot(e => ValidateUser(e, guild, out _)).ToString("N0"); + var excludedEntrantsCount = 0; + foreach (ulong entrantId in giveaway.Entrants) + { + DiscordMember? member = await ValidateUserAsync(entrantId, giveaway).ConfigureAwait(false); + if (member is null) excludedEntrantsCount++; + } + + var excludedEntrants = excludedEntrantsCount.ToString("N0"); embed.AddField(EmbedStrings.Title, giveaway.Title); embed.AddField(EmbedStrings.Description, description); @@ -199,7 +205,7 @@ public DiscordEmbedBuilder CreateGiveawayInformationEmbed(Giveaway giveaway) embed.AddField(conjugatedEnd, Formatter.Timestamp(giveaway.EndTime), true); embed.AddField(EmbedStrings.Creator, MentionUtility.MentionUser(giveaway.CreatorId), true); embed.AddField(EmbedStrings.Entrants, entrantsCount, true); - embed.AddField(EmbedStrings.ExcludedEntrants, excludedEntrantsCount, true); + embed.AddField(EmbedStrings.ExcludedEntrants, excludedEntrants, true); embed.AddFieldIf(giveaway.ImageUri is not null, "Image", () => Formatter.MaskedUrl("View", giveaway.ImageUri), true); List winnerIds = giveaway.WinnerIds; @@ -218,11 +224,11 @@ public DiscordEmbedBuilder CreateGiveawayInformationEmbed(Giveaway giveaway) /// The giveaway whose log embed to return. /// The log embed. /// is . - public DiscordEmbed CreateGiveawayLogEmbed(Giveaway giveaway) + public async Task CreateGiveawayLogEmbedAsync(Giveaway giveaway) { ArgumentNullException.ThrowIfNull(giveaway); - DiscordEmbedBuilder embed = CreateGiveawayInformationEmbed(giveaway); + DiscordEmbedBuilder embed = await CreateGiveawayInformationEmbedAsync(giveaway).ConfigureAwait(false); embed.WithTitle(EmbedStrings.GiveawayCreated_Title_NoEmoji); embed.WithColor(DiscordColor.Green); return embed; @@ -425,7 +431,7 @@ public async Task LogGiveawayCreationAsync(Giveaway giveaway) return; } - DiscordEmbedBuilder embed = CreateGiveawayInformationEmbed(giveaway); + DiscordEmbedBuilder embed = await CreateGiveawayInformationEmbedAsync(giveaway).ConfigureAwait(false); embed.WithTitle(EmbedStrings.GiveawayCreated_Title_NoEmoji); embed.WithColor(DiscordColor.Green); @@ -461,7 +467,14 @@ public async Task LogGiveawayExpirationAsync(Giveaway giveaway) description = $"{description[..1021]}..."; var entrantsCount = giveaway.Entrants.Count.ToString("N0"); - var excludedEntrantsCount = giveaway.Entrants.CountWhereNot(e => ValidateUser(e, guild, out _)).ToString("N0"); + var excludedEntrantsCount = 0; + foreach (ulong entrantId in giveaway.Entrants) + { + DiscordMember? member = await ValidateUserAsync(entrantId, giveaway).ConfigureAwait(false); + if (member is null) excludedEntrantsCount++; + } + + var excludedEntrants = excludedEntrantsCount.ToString("N0"); embed.AddField(EmbedStrings.Title, giveaway.Title); embed.AddField(EmbedStrings.Description, description); @@ -469,7 +482,7 @@ public async Task LogGiveawayExpirationAsync(Giveaway giveaway) embed.AddField(EmbedStrings.NumberOfWinners, giveaway.WinnerCount, true); embed.AddField(EmbedStrings.Duration, (giveaway.EndTime - giveaway.StartTime).Humanize(), true); embed.AddField(EmbedStrings.Entrants, entrantsCount, true); - embed.AddField(EmbedStrings.ExcludedEntrants, excludedEntrantsCount, true); + embed.AddField(EmbedStrings.ExcludedEntrants, excludedEntrants, true); var winners = new List(); foreach (ulong winnerId in giveaway.WinnerIds) @@ -518,7 +531,8 @@ public async Task LogGiveawayExpirationAsync(Giveaway giveaway) /// The selected winners of the giveaway. The returned list does not include invalid users, users which are no longer in /// the guild, or users with at least one of the excluded role IDs. /// - public IReadOnlyList SelectWinners(Giveaway giveaway, IEnumerable? winnersToKeep = null) + public async Task> SelectWinnersAsync(Giveaway giveaway, + IEnumerable? winnersToKeep = null) { ArgumentNullException.ThrowIfNull(giveaway); @@ -536,7 +550,8 @@ public IReadOnlyList SelectWinners(Giveaway giveaway, IEnumerable // in the event that there are zero valid winners entrants.Remove(randomUserId); - if (ValidateUser(randomUserId, guild, out DiscordMember? member)) + DiscordMember? member = await ValidateUserAsync(randomUserId, giveaway).ConfigureAwait(false); + if (member is not null) winners.Add(member); } @@ -595,7 +610,7 @@ public async Task UpdateGiveawayLogMessageAsync(Giveaway giveaway) return; } - DiscordEmbed embed = CreateGiveawayLogEmbed(giveaway); + DiscordEmbed embed = await CreateGiveawayLogEmbedAsync(giveaway).ConfigureAwait(false); await message.ModifyAsync(embed).ConfigureAwait(false); } @@ -667,21 +682,25 @@ public bool ValidateMember(DiscordMember member, DiscordGuild guild) /// Validates that the user with the specified ID is a valid participant of giveaways in the specified guild. /// /// The ID of the user to validate. - /// The guild in which to perform the validation check. - /// - /// When this method returns, contains the with the ID that is a - /// member of . - /// + /// The giveaway used for validation. /// - /// if the user with the ID is a valid a participant of giveaways in - /// ; otherwise, . + /// A object encapsulating the member as part of the guild in , + /// if this user satisfies the giveaway requirements; otherwise, . /// - /// is . - public bool ValidateUser(ulong userId, DiscordGuild guild, [NotNullWhen(true)] out DiscordMember? member) + /// is . + public async Task ValidateUserAsync(ulong userId, Giveaway giveaway) { - ArgumentNullException.ThrowIfNull(guild); - member = null; - return userId != 0 && guild.Members.TryGetValue(userId, out member) && ValidateMember(member, guild); + ArgumentNullException.ThrowIfNull(giveaway); + + DiscordGuild guild = await _discordClient.GetGuildAsync(giveaway.GuildId).ConfigureAwait(false); + DiscordMember? member = await guild.GetMemberOrNullAsync(userId).ConfigureAwait(false); + + if (member is null) return null; + if (!ValidateMember(member, guild)) return null; + if (giveaway.ExcludedUsers.Contains(userId)) return null; + if (member.Roles.Any(r => giveaway.ExcludedRoles.Contains(r.Id))) return null; + + return member; } private async Task UpdateFromDatabaseAsync(CancellationToken cancellationToken) From ca1a0ed644d36c9e6f24c456f83744173fd19be1 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Sun, 19 Feb 2023 20:40:16 +0000 Subject: [PATCH 5/5] Bump to 2.0.0 --- Present/Present.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Present/Present.csproj b/Present/Present.csproj index 1d7c4a3..456e75e 100644 --- a/Present/Present.csproj +++ b/Present/Present.csproj @@ -6,7 +6,7 @@ enable enable Linux - 1.5.0 + 2.0.0