mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-08-27 22:34:49 +02:00
Refactor polls
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public record PollResults(bool IsFinalized, IReadOnlyList<PollAnswerResult> 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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -138,17 +138,17 @@
|
||||
}
|
||||
else if (message.Kind == MessageKind.PollResult && pollResult is not null)
|
||||
{
|
||||
@if (!string.IsNullOrWhiteSpace(pollResult.QuestionText))
|
||||
@if (!string.IsNullOrWhiteSpace(pollResult.Question))
|
||||
{
|
||||
<span>'s poll </span>
|
||||
|
||||
@if (message.Reference?.MessageId is not null)
|
||||
{
|
||||
<a class="chatlog__system-notification-link" href="#chatlog__message-container-@message.Reference.MessageId">@pollResult.QuestionText</a>
|
||||
<a class="chatlog__system-notification-link" href="#chatlog__message-container-@message.Reference.MessageId">@pollResult.Question</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chatlog__system-notification-link">@pollResult.QuestionText</span>
|
||||
<span class="chatlog__system-notification-link">@pollResult.Question</span>
|
||||
}
|
||||
|
||||
<span> has closed.</span>
|
||||
@@ -175,8 +175,6 @@
|
||||
|
||||
@if (message.Kind == MessageKind.PollResult && pollResult is not null)
|
||||
{
|
||||
var hasWinningAnswer = pollResult.WinningAnswerId is not null;
|
||||
|
||||
<div class="chatlog__poll-result">
|
||||
<div class="chatlog__poll-result-answer">
|
||||
@if (pollResult.WinningAnswerEmoji is not null)
|
||||
@@ -188,19 +186,19 @@
|
||||
|
||||
<div class="chatlog__poll-result-answer-info">
|
||||
<div class="chatlog__poll-result-answer-text">
|
||||
@(hasWinningAnswer ? pollResult.WinningAnswerText : "Poll closed")
|
||||
@(pollResult.WinningAnswerId is not null ? pollResult.WinningAnswerText : "Poll closed")
|
||||
|
||||
@if (hasWinningAnswer)
|
||||
@if (pollResult.WinningAnswerId is not null)
|
||||
{
|
||||
<span class="chatlog__poll-result-winner" title="Winning answer"><span aria-hidden="true">✓</span><span class="chatlog__poll-accessible-label">Winning answer</span></span>
|
||||
}
|
||||
</div>
|
||||
<div class="chatlog__poll-result-answer-details">
|
||||
@if (hasWinningAnswer)
|
||||
@if (pollResult.WinningAnswerId is not null)
|
||||
{
|
||||
<span>Winning answer</span>
|
||||
<span aria-hidden="true"> • </span>
|
||||
<span>@pollResult.WinningVotePercentage.ToString("P0", Context.Request.CultureInfo)</span>
|
||||
<span>@pollResult.WinningVoteShare.ToString("P0", Context.Request.CultureInfo)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -490,22 +488,19 @@
|
||||
<div class="chatlog__poll-answers">
|
||||
@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);
|
||||
|
||||
<div class="chatlog__poll-answer @(isWinning ? "chatlog__poll-answer--winning" : isActiveSelection ? "chatlog__poll-answer--selected" : null)" style="--poll-answer-fill: @answerVotePercentageCss">
|
||||
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;
|
||||
|
||||
<div class="chatlog__poll-answer @(isWinning ? "chatlog__poll-answer--winning" : poll.Results?.IsFinalized != true && isSelected ? "chatlog__poll-answer--selected" : null)" style="--poll-answer-fill: @answerVoteShare.ToString("P2", CultureInfo.InvariantCulture).Replace(" ", "")">
|
||||
<div class="chatlog__poll-answer-content">
|
||||
@if (answer.Emoji is not null)
|
||||
{
|
||||
@@ -515,12 +510,12 @@
|
||||
</div>
|
||||
|
||||
<div class="chatlog__poll-answer-details">
|
||||
@if (pollResults is not null)
|
||||
@if (poll.Results is not null)
|
||||
{
|
||||
var answerVoteCount = answerResult?.Count ?? 0;
|
||||
|
||||
<span class="chatlog__poll-answer-count">@answerVoteCount.ToString("N0", Context.Request.CultureInfo) @(answerVoteCount == 1 ? "vote" : "votes")</span>
|
||||
<span class="chatlog__poll-answer-percentage">@answerVotePercentage.ToString("P0", Context.Request.CultureInfo)</span>
|
||||
<span class="chatlog__poll-answer-percentage">@answerVoteShare.ToString("P0", Context.Request.CultureInfo)</span>
|
||||
}
|
||||
|
||||
@if (isSelected)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user