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.
|
// but the client can render multiple images in some cases.
|
||||||
public EmbedImage? Image => Images.FirstOrDefault();
|
public EmbedImage? Image => Images.FirstOrDefault();
|
||||||
|
|
||||||
|
public EmbedField? TryGetField(string name) =>
|
||||||
|
Fields.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal));
|
||||||
|
|
||||||
public PollResultEmbedProjection? TryGetPollResult() =>
|
public PollResultEmbedProjection? TryGetPollResult() =>
|
||||||
PollResultEmbedProjection.TryResolve(this);
|
PollResultEmbedProjection.TryResolve(this);
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,37 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using DiscordChatExporter.Core.Discord;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using PowerKit.Extensions;
|
using PowerKit.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
|
|
||||||
// https://docs.discord.com/developers/resources/message#embed-fields-by-embed-type-poll-result-embed-fields
|
// https://docs.discord.com/developers/resources/message#embed-fields-by-embed-type-poll-result-embed-fields
|
||||||
public partial record PollResultEmbedProjection(
|
public partial record PollResultEmbedProjection(
|
||||||
string QuestionText,
|
string Question,
|
||||||
int WinningVoteCount,
|
|
||||||
int TotalVoteCount,
|
int TotalVoteCount,
|
||||||
|
int WinningVoteCount,
|
||||||
int? WinningAnswerId,
|
int? WinningAnswerId,
|
||||||
string? WinningAnswerText,
|
string? WinningAnswerText,
|
||||||
Emoji? WinningAnswerEmoji
|
Emoji? WinningAnswerEmoji
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public double WinningVotePercentage { get; } =
|
public double WinningVoteShare { get; } =
|
||||||
TotalVoteCount > 0 ? (double)WinningVoteCount / TotalVoteCount : 0;
|
TotalVoteCount > 0 ? (double)WinningVoteCount / TotalVoteCount : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record PollResultEmbedProjection
|
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)
|
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))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var id =
|
var id = embed
|
||||||
TryGetFieldValue(embed, "victor_answer_emoji_id") is { } idValue
|
.TryGetField("victor_answer_emoji_id")
|
||||||
&& Snowflake.TryParse(idValue) is { } parsedId
|
?.Value?.Pipe(v => Snowflake.TryParse(v));
|
||||||
? parsedId
|
|
||||||
: (Snowflake?)null;
|
|
||||||
|
|
||||||
var isAnimated =
|
var isAnimated =
|
||||||
bool.TryParse(
|
embed.TryGetField("victor_answer_emoji_animated")?.Value?.Pipe(bool.ParseOrNull)
|
||||||
TryGetFieldValue(embed, "victor_answer_emoji_animated"),
|
?? false;
|
||||||
out var parsedIsAnimated
|
|
||||||
) && parsedIsAnimated;
|
|
||||||
|
|
||||||
return new Emoji(id, name, isAnimated);
|
return new Emoji(id, name, isAnimated);
|
||||||
}
|
}
|
||||||
@@ -54,27 +41,31 @@ public partial record PollResultEmbedProjection
|
|||||||
if (embed.Kind != EmbedKind.PollResult)
|
if (embed.Kind != EmbedKind.PollResult)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var questionText = TryGetFieldValue(embed, "poll_question_text") ?? "";
|
var question = embed.TryGetField("poll_question_text")?.Value ?? "";
|
||||||
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 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);
|
var winningAnswerEmoji = TryParseWinningAnswerEmoji(embed);
|
||||||
|
|
||||||
return new PollResultEmbedProjection(
|
return new PollResultEmbedProjection(
|
||||||
questionText,
|
question,
|
||||||
winningVoteCount,
|
|
||||||
totalVoteCount,
|
totalVoteCount,
|
||||||
|
winningVoteCount,
|
||||||
winningAnswerId,
|
winningAnswerId,
|
||||||
winningAnswerText,
|
winningAnswerText,
|
||||||
winningAnswerEmoji
|
winningAnswerEmoji
|
||||||
|
|||||||
@@ -41,8 +41,9 @@ public partial record Message(
|
|||||||
&& Poll is null;
|
&& Poll is null;
|
||||||
|
|
||||||
public bool IsSystemNotification { get; } =
|
public bool IsSystemNotification { get; } =
|
||||||
Kind is >= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated
|
Kind
|
||||||
|| Kind == MessageKind.PollResult;
|
is (>= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated)
|
||||||
|
or MessageKind.PollResult;
|
||||||
|
|
||||||
public bool IsReply { get; } = Kind == MessageKind.Reply;
|
public bool IsReply { get; } = Kind == MessageKind.Reply;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
using PowerKit.Extensions;
|
using PowerKit.Extensions;
|
||||||
|
|
||||||
@@ -11,9 +10,14 @@ public record PollAnswer(int Id, string Text, Emoji? Emoji)
|
|||||||
public static PollAnswer Parse(JsonElement json)
|
public static PollAnswer Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("answer_id").GetInt32();
|
var id = json.GetProperty("answer_id").GetInt32();
|
||||||
var media = json.GetProperty("poll_media");
|
|
||||||
var text = media.GetPropertyOrNull("text")?.GetStringOrNull() ?? "";
|
var text =
|
||||||
var emoji = media.GetPropertyOrNull("emoji")?.Pipe(Emoji.Parse);
|
json.GetPropertyOrNull("poll_media")?.GetPropertyOrNull("text")?.GetStringOrNull()
|
||||||
|
?? "";
|
||||||
|
|
||||||
|
var emoji = json.GetPropertyOrNull("poll_media")
|
||||||
|
?.GetPropertyOrNull("emoji")
|
||||||
|
?.Pipe(Emoji.Parse);
|
||||||
|
|
||||||
return new PollAnswer(id, text, emoji);
|
return new PollAnswer(id, text, emoji);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ using JsonExtensions.Reading;
|
|||||||
namespace DiscordChatExporter.Core.Discord.Data.Polls;
|
namespace DiscordChatExporter.Core.Discord.Data.Polls;
|
||||||
|
|
||||||
// https://discord.com/developers/docs/resources/poll#poll-answer-count-object
|
// 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)
|
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 count = json.GetProperty("count").GetInt32();
|
||||||
var didCurrentUserVote = json.GetPropertyOrNull("me_voted")?.GetBooleanOrNull() ?? false;
|
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 int WinningVoteCount { get; } = Answers.Select(a => a.Count).DefaultIfEmpty().Max();
|
||||||
|
|
||||||
public PollAnswerResult? TryGetAnswerResult(int answerId) =>
|
public PollAnswerResult? TryGetAnswerResult(int answerId) =>
|
||||||
Answers.FirstOrDefault(a => a.AnswerId == answerId);
|
Answers.FirstOrDefault(a => a.Id == answerId);
|
||||||
|
|
||||||
public static PollResults Parse(JsonElement json)
|
public static PollResults Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ internal class ContainsMessageFilter(string text) : MessageFilter
|
|||||||
|
|
||||||
public override bool IsMatch(Message message) =>
|
public override bool IsMatch(Message message) =>
|
||||||
IsMatch(message.Content)
|
IsMatch(message.Content)
|
||||||
|| IsMatch(message.Poll?.Question)
|
|
||||||
|| message.Poll?.Answers.Any(a => IsMatch(a.Text)) == true
|
|
||||||
|| message.Embeds.Any(e =>
|
|| message.Embeds.Any(e =>
|
||||||
IsMatch(e.Title)
|
IsMatch(e.Title)
|
||||||
|| IsMatch(e.Author?.Name)
|
|| IsMatch(e.Author?.Name)
|
||||||
|| IsMatch(e.Description)
|
|| IsMatch(e.Description)
|
||||||
|| IsMatch(e.Footer?.Text)
|
|| IsMatch(e.Footer?.Text)
|
||||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
|| 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)
|
else if (message.Kind == MessageKind.PollResult && pollResult is not null)
|
||||||
{
|
{
|
||||||
@if (!string.IsNullOrWhiteSpace(pollResult.QuestionText))
|
@if (!string.IsNullOrWhiteSpace(pollResult.Question))
|
||||||
{
|
{
|
||||||
<span>'s poll </span>
|
<span>'s poll </span>
|
||||||
|
|
||||||
@if (message.Reference?.MessageId is not null)
|
@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
|
else
|
||||||
{
|
{
|
||||||
<span class="chatlog__system-notification-link">@pollResult.QuestionText</span>
|
<span class="chatlog__system-notification-link">@pollResult.Question</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
<span> has closed.</span>
|
<span> has closed.</span>
|
||||||
@@ -175,8 +175,6 @@
|
|||||||
|
|
||||||
@if (message.Kind == MessageKind.PollResult && pollResult is not null)
|
@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">
|
||||||
<div class="chatlog__poll-result-answer">
|
<div class="chatlog__poll-result-answer">
|
||||||
@if (pollResult.WinningAnswerEmoji is not null)
|
@if (pollResult.WinningAnswerEmoji is not null)
|
||||||
@@ -188,19 +186,19 @@
|
|||||||
|
|
||||||
<div class="chatlog__poll-result-answer-info">
|
<div class="chatlog__poll-result-answer-info">
|
||||||
<div class="chatlog__poll-result-answer-text">
|
<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>
|
<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>
|
||||||
<div class="chatlog__poll-result-answer-details">
|
<div class="chatlog__poll-result-answer-details">
|
||||||
@if (hasWinningAnswer)
|
@if (pollResult.WinningAnswerId is not null)
|
||||||
{
|
{
|
||||||
<span>Winning answer</span>
|
<span>Winning answer</span>
|
||||||
<span aria-hidden="true"> • </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
|
else
|
||||||
{
|
{
|
||||||
@@ -490,22 +488,19 @@
|
|||||||
<div class="chatlog__poll-answers">
|
<div class="chatlog__poll-answers">
|
||||||
@foreach (var answer in poll.Answers)
|
@foreach (var answer in poll.Answers)
|
||||||
{
|
{
|
||||||
var pollResults = poll.Results;
|
var answerResult = poll.Results?.TryGetAnswerResult(answer.Id);
|
||||||
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(" ", "");
|
|
||||||
|
|
||||||
<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">
|
<div class="chatlog__poll-answer-content">
|
||||||
@if (answer.Emoji is not null)
|
@if (answer.Emoji is not null)
|
||||||
{
|
{
|
||||||
@@ -515,12 +510,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chatlog__poll-answer-details">
|
<div class="chatlog__poll-answer-details">
|
||||||
@if (pollResults is not null)
|
@if (poll.Results is not null)
|
||||||
{
|
{
|
||||||
var answerVoteCount = answerResult?.Count ?? 0;
|
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-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)
|
@if (isSelected)
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ internal static class PlainTextMessageExtensions
|
|||||||
.WhereNotNull()
|
.WhereNotNull()
|
||||||
.FirstOrDefault()
|
.FirstOrDefault()
|
||||||
is { } pollResult
|
is { } pollResult
|
||||||
? string.IsNullOrWhiteSpace(pollResult.QuestionText)
|
? string.IsNullOrWhiteSpace(pollResult.Question)
|
||||||
? $"{message.Author.DisplayName}'s poll has closed."
|
? $"{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.",
|
: "A poll has closed.",
|
||||||
|
|
||||||
_ => message.Content,
|
_ => message.Content,
|
||||||
|
|||||||
Reference in New Issue
Block a user