diff --git a/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs index a5fe30bf..b6c79c7b 100644 --- a/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs @@ -28,6 +28,9 @@ public partial record Embed( // but the client can render multiple images in some cases. public EmbedImage? Image => Images.FirstOrDefault(); + public EmbedField? TryGetField(string name) => + Fields.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal)); + public PollResultEmbedProjection? TryGetPollResult() => PollResultEmbedProjection.TryResolve(this); diff --git a/DiscordChatExporter.Core/Discord/Data/Embeds/PollResultEmbedProjection.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/PollResultEmbedProjection.cs index 41054e87..586372a0 100644 --- a/DiscordChatExporter.Core/Discord/Data/Embeds/PollResultEmbedProjection.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/PollResultEmbedProjection.cs @@ -1,50 +1,37 @@ -using System; using System.Globalization; -using System.Linq; -using DiscordChatExporter.Core.Discord; -using DiscordChatExporter.Core.Discord.Data; using PowerKit.Extensions; namespace DiscordChatExporter.Core.Discord.Data.Embeds; // https://docs.discord.com/developers/resources/message#embed-fields-by-embed-type-poll-result-embed-fields public partial record PollResultEmbedProjection( - string QuestionText, - int WinningVoteCount, + string Question, int TotalVoteCount, + int WinningVoteCount, int? WinningAnswerId, string? WinningAnswerText, Emoji? WinningAnswerEmoji ) { - public double WinningVotePercentage { get; } = + public double WinningVoteShare { get; } = TotalVoteCount > 0 ? (double)WinningVoteCount / TotalVoteCount : 0; } public partial record PollResultEmbedProjection { - private static string? TryGetFieldValue(Embed embed, string name) => - embed - .Fields.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal)) - ?.Value; - private static Emoji? TryParseWinningAnswerEmoji(Embed embed) { - var name = TryGetFieldValue(embed, "victor_answer_emoji_name"); + var name = embed.TryGetField("victor_answer_emoji_name")?.Value; if (string.IsNullOrWhiteSpace(name)) return null; - var id = - TryGetFieldValue(embed, "victor_answer_emoji_id") is { } idValue - && Snowflake.TryParse(idValue) is { } parsedId - ? parsedId - : (Snowflake?)null; + var id = embed + .TryGetField("victor_answer_emoji_id") + ?.Value?.Pipe(v => Snowflake.TryParse(v)); var isAnimated = - bool.TryParse( - TryGetFieldValue(embed, "victor_answer_emoji_animated"), - out var parsedIsAnimated - ) && parsedIsAnimated; + embed.TryGetField("victor_answer_emoji_animated")?.Value?.Pipe(bool.ParseOrNull) + ?? false; return new Emoji(id, name, isAnimated); } @@ -54,27 +41,31 @@ public partial record PollResultEmbedProjection if (embed.Kind != EmbedKind.PollResult) return null; - var questionText = TryGetFieldValue(embed, "poll_question_text") ?? ""; - var winningVoteCount = int.ParseOrDefault( - TryGetFieldValue(embed, "victor_answer_votes"), - CultureInfo.InvariantCulture - ); - var totalVoteCount = int.ParseOrDefault( - TryGetFieldValue(embed, "total_votes"), - CultureInfo.InvariantCulture - ); - var winningAnswerId = int.ParseOrNull( - TryGetFieldValue(embed, "victor_answer_id"), - CultureInfo.InvariantCulture - ); + var question = embed.TryGetField("poll_question_text")?.Value ?? ""; - var winningAnswerText = TryGetFieldValue(embed, "victor_answer_text"); + var totalVoteCount = + embed + .TryGetField("total_votes") + ?.Value?.Pipe(v => int.ParseOrNull(v, CultureInfo.InvariantCulture)) + ?? 0; + + var winningVoteCount = + embed + .TryGetField("victor_answer_votes") + ?.Value?.Pipe(v => int.ParseOrNull(v, CultureInfo.InvariantCulture)) + ?? 0; + + var winningAnswerId = embed + .TryGetField("victor_answer_id") + ?.Value?.Pipe(v => int.ParseOrNull(v, CultureInfo.InvariantCulture)); + + var winningAnswerText = embed.TryGetField("victor_answer_text")?.Value; var winningAnswerEmoji = TryParseWinningAnswerEmoji(embed); return new PollResultEmbedProjection( - questionText, - winningVoteCount, + question, totalVoteCount, + winningVoteCount, winningAnswerId, winningAnswerText, winningAnswerEmoji diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs index bae435ad..670b2d65 100644 --- a/DiscordChatExporter.Core/Discord/Data/Message.cs +++ b/DiscordChatExporter.Core/Discord/Data/Message.cs @@ -41,8 +41,9 @@ public partial record Message( && Poll is null; public bool IsSystemNotification { get; } = - Kind is >= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated - || Kind == MessageKind.PollResult; + Kind + is (>= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated) + or MessageKind.PollResult; public bool IsReply { get; } = Kind == MessageKind.Reply; diff --git a/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswer.cs b/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswer.cs index a93038cb..4dfb1009 100644 --- a/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswer.cs +++ b/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswer.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using DiscordChatExporter.Core.Discord.Data; using JsonExtensions.Reading; using PowerKit.Extensions; @@ -11,9 +10,14 @@ public record PollAnswer(int Id, string Text, Emoji? Emoji) public static PollAnswer Parse(JsonElement json) { var id = json.GetProperty("answer_id").GetInt32(); - var media = json.GetProperty("poll_media"); - var text = media.GetPropertyOrNull("text")?.GetStringOrNull() ?? ""; - var emoji = media.GetPropertyOrNull("emoji")?.Pipe(Emoji.Parse); + + var text = + json.GetPropertyOrNull("poll_media")?.GetPropertyOrNull("text")?.GetStringOrNull() + ?? ""; + + var emoji = json.GetPropertyOrNull("poll_media") + ?.GetPropertyOrNull("emoji") + ?.Pipe(Emoji.Parse); return new PollAnswer(id, text, emoji); } diff --git a/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswerResult.cs b/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswerResult.cs index d07192ac..8f074ae1 100644 --- a/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswerResult.cs +++ b/DiscordChatExporter.Core/Discord/Data/Polls/PollAnswerResult.cs @@ -4,14 +4,14 @@ using JsonExtensions.Reading; namespace DiscordChatExporter.Core.Discord.Data.Polls; // https://discord.com/developers/docs/resources/poll#poll-answer-count-object -public record PollAnswerResult(int AnswerId, int Count, bool DidCurrentUserVote) +public record PollAnswerResult(int Id, int Count, bool DidCurrentUserVote) { public static PollAnswerResult Parse(JsonElement json) { - var answerId = json.GetProperty("id").GetInt32(); + var id = json.GetProperty("id").GetInt32(); var count = json.GetProperty("count").GetInt32(); var didCurrentUserVote = json.GetPropertyOrNull("me_voted")?.GetBooleanOrNull() ?? false; - return new PollAnswerResult(answerId, count, didCurrentUserVote); + return new PollAnswerResult(id, count, didCurrentUserVote); } } diff --git a/DiscordChatExporter.Core/Discord/Data/Polls/PollResults.cs b/DiscordChatExporter.Core/Discord/Data/Polls/PollResults.cs index b30ddce1..66882ba9 100644 --- a/DiscordChatExporter.Core/Discord/Data/Polls/PollResults.cs +++ b/DiscordChatExporter.Core/Discord/Data/Polls/PollResults.cs @@ -13,7 +13,7 @@ public record PollResults(bool IsFinalized, IReadOnlyList Answ public int WinningVoteCount { get; } = Answers.Select(a => a.Count).DefaultIfEmpty().Max(); public PollAnswerResult? TryGetAnswerResult(int answerId) => - Answers.FirstOrDefault(a => a.AnswerId == answerId); + Answers.FirstOrDefault(a => a.Id == answerId); public static PollResults Parse(JsonElement json) { diff --git a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs index 22d61e96..8942290e 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs @@ -22,13 +22,13 @@ internal class ContainsMessageFilter(string text) : MessageFilter public override bool IsMatch(Message message) => IsMatch(message.Content) - || IsMatch(message.Poll?.Question) - || message.Poll?.Answers.Any(a => IsMatch(a.Text)) == true || message.Embeds.Any(e => IsMatch(e.Title) || IsMatch(e.Author?.Name) || IsMatch(e.Description) || IsMatch(e.Footer?.Text) || e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value)) - ); + ) + || IsMatch(message.Poll?.Question) + || message.Poll?.Answers.Any(a => IsMatch(a.Text)) == true; } diff --git a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml index 9c50f563..8906f589 100644 --- a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml +++ b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml @@ -138,17 +138,17 @@ } else if (message.Kind == MessageKind.PollResult && pollResult is not null) { - @if (!string.IsNullOrWhiteSpace(pollResult.QuestionText)) + @if (!string.IsNullOrWhiteSpace(pollResult.Question)) { 's poll @if (message.Reference?.MessageId is not null) { - @pollResult.QuestionText + @pollResult.Question } else { - @pollResult.QuestionText + @pollResult.Question } has closed. @@ -175,8 +175,6 @@ @if (message.Kind == MessageKind.PollResult && pollResult is not null) { - var hasWinningAnswer = pollResult.WinningAnswerId is not null; -
@if (pollResult.WinningAnswerEmoji is not null) @@ -188,19 +186,19 @@
- @(hasWinningAnswer ? pollResult.WinningAnswerText : "Poll closed") + @(pollResult.WinningAnswerId is not null ? pollResult.WinningAnswerText : "Poll closed") - @if (hasWinningAnswer) + @if (pollResult.WinningAnswerId is not null) { Winning answer }
- @if (hasWinningAnswer) + @if (pollResult.WinningAnswerId is not null) { Winning answer - @pollResult.WinningVotePercentage.ToString("P0", Context.Request.CultureInfo) + @pollResult.WinningVoteShare.ToString("P0", Context.Request.CultureInfo) } else { @@ -490,22 +488,19 @@
@foreach (var answer in poll.Answers) { - var pollResults = poll.Results; - var answerResult = pollResults?.TryGetAnswerResult(answer.Id); - var isSelected = answerResult?.DidCurrentUserVote == true; - var isWinning = - pollResults is { IsFinalized: true, WinningVoteCount: > 0 } - && answerResult?.Count == pollResults.WinningVoteCount; - var isActiveSelection = pollResults?.IsFinalized != true && isSelected; - var answerVotePercentage = pollResults is { TotalVoteCount: > 0 } - ? (double)(answerResult?.Count ?? 0) / pollResults.TotalVoteCount - : 0; - var answerVotePercentageCss = - answerVotePercentage - .ToString("P2", CultureInfo.InvariantCulture) - .Replace(" ", ""); + var answerResult = poll.Results?.TryGetAnswerResult(answer.Id); -
+ var isSelected = answerResult?.DidCurrentUserVote == true; + + var isWinning = + poll.Results is { IsFinalized: true, WinningVoteCount: > 0 } + && answerResult?.Count >= poll.Results.WinningVoteCount; + + var answerVoteShare = poll.Results?.TotalVoteCount > 0 + ? (double)(answerResult?.Count ?? 0) / poll.Results.TotalVoteCount + : 0; + +
@if (answer.Emoji is not null) { @@ -515,12 +510,12 @@
- @if (pollResults is not null) + @if (poll.Results is not null) { var answerVoteCount = answerResult?.Count ?? 0; @answerVoteCount.ToString("N0", Context.Request.CultureInfo) @(answerVoteCount == 1 ? "vote" : "votes") - @answerVotePercentage.ToString("P0", Context.Request.CultureInfo) + @answerVoteShare.ToString("P0", Context.Request.CultureInfo) } @if (isSelected) diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs b/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs index 3763580e..44f1db55 100644 --- a/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs +++ b/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs @@ -42,9 +42,9 @@ internal static class PlainTextMessageExtensions .WhereNotNull() .FirstOrDefault() is { } pollResult - ? string.IsNullOrWhiteSpace(pollResult.QuestionText) + ? string.IsNullOrWhiteSpace(pollResult.Question) ? $"{message.Author.DisplayName}'s poll has closed." - : $"{message.Author.DisplayName}'s poll {pollResult.QuestionText} has closed." + : $"{message.Author.DisplayName}'s poll {pollResult.Question} has closed." : "A poll has closed.", _ => message.Content,