mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-18 23:46:36 +00:00
Replace the date format option with a locale option (#1130)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
@@ -19,7 +20,10 @@ public static class ImageCdn
|
||||
? runes
|
||||
: runes.Where(r => r.Value != 0xfe0f);
|
||||
|
||||
var twemojiId = string.Join("-", filteredRunes.Select(r => r.Value.ToString("x")));
|
||||
var twemojiId = string.Join(
|
||||
"-",
|
||||
filteredRunes.Select(r => r.Value.ToString("x", CultureInfo.InvariantCulture))
|
||||
);
|
||||
|
||||
return $"https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/{twemojiId}.svg";
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ internal class ExportContext
|
||||
);
|
||||
}
|
||||
|
||||
public DateTimeOffset NormalizeDate(DateTimeOffset instant) =>
|
||||
Request.IsUtcNormalizationEnabled ? instant.ToUniversalTime() : instant.ToLocalTime();
|
||||
|
||||
public string FormatDate(DateTimeOffset instant, string format = "g") =>
|
||||
NormalizeDate(instant).ToString(format, Request.CultureInfo);
|
||||
|
||||
public async ValueTask PopulateChannelsAndRolesAsync(
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
@@ -41,10 +47,14 @@ internal class ExportContext
|
||||
await foreach (
|
||||
var channel in Discord.GetGuildChannelsAsync(Request.Guild.Id, cancellationToken)
|
||||
)
|
||||
{
|
||||
_channelsById[channel.Id] = channel;
|
||||
}
|
||||
|
||||
await foreach (var role in Discord.GetGuildRolesAsync(Request.Guild.Id, cancellationToken))
|
||||
{
|
||||
_rolesById[role.Id] = role;
|
||||
}
|
||||
}
|
||||
|
||||
// Because members cannot be pulled in bulk, we need to populate them on demand
|
||||
@@ -84,14 +94,6 @@ internal class ExportContext
|
||||
CancellationToken cancellationToken = default
|
||||
) => await PopulateMemberAsync(user.Id, user, cancellationToken);
|
||||
|
||||
public string FormatDate(DateTimeOffset instant) =>
|
||||
Request.DateFormat switch
|
||||
{
|
||||
"unix" => instant.ToUnixTimeSeconds().ToString(),
|
||||
"unixms" => instant.ToUnixTimeMilliseconds().ToString(),
|
||||
var format => instant.ToLocalString(format)
|
||||
};
|
||||
|
||||
public Member? TryGetMember(Snowflake id) => _membersById.GetValueOrDefault(id);
|
||||
|
||||
public Channel? TryGetChannel(Snowflake id) => _channelsById.GetValueOrDefault(id);
|
||||
|
||||
@@ -39,7 +39,11 @@ public partial class ExportRequest
|
||||
|
||||
public bool ShouldReuseAssets { get; }
|
||||
|
||||
public string DateFormat { get; }
|
||||
public string Locale { get; }
|
||||
|
||||
public CultureInfo CultureInfo { get; }
|
||||
|
||||
public bool IsUtcNormalizationEnabled { get; }
|
||||
|
||||
public ExportRequest(
|
||||
Guild guild,
|
||||
@@ -54,7 +58,8 @@ public partial class ExportRequest
|
||||
bool shouldFormatMarkdown,
|
||||
bool shouldDownloadAssets,
|
||||
bool shouldReuseAssets,
|
||||
string dateFormat
|
||||
string locale,
|
||||
bool isUtcNormalizationEnabled
|
||||
)
|
||||
{
|
||||
Guild = guild;
|
||||
@@ -67,7 +72,8 @@ public partial class ExportRequest
|
||||
ShouldFormatMarkdown = shouldFormatMarkdown;
|
||||
ShouldDownloadAssets = shouldDownloadAssets;
|
||||
ShouldReuseAssets = shouldReuseAssets;
|
||||
DateFormat = dateFormat;
|
||||
Locale = locale;
|
||||
IsUtcNormalizationEnabled = isUtcNormalizationEnabled;
|
||||
|
||||
OutputFilePath = GetOutputBaseFilePath(Guild, Channel, outputPath, Format, After, Before);
|
||||
|
||||
@@ -76,6 +82,8 @@ public partial class ExportRequest
|
||||
AssetsDirPath = !string.IsNullOrWhiteSpace(assetsDirPath)
|
||||
? FormatPath(assetsDirPath, Guild, Channel, After, Before)
|
||||
: $"{OutputFilePath}_Files{Path.DirectorySeparatorChar}";
|
||||
|
||||
CultureInfo = CultureInfo.GetCultureInfo(Locale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,12 +317,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||
)
|
||||
{
|
||||
var formatted = timestamp.Instant is not null
|
||||
? !string.IsNullOrWhiteSpace(timestamp.Format)
|
||||
? timestamp.Instant.Value.ToLocalString(timestamp.Format)
|
||||
: _context.FormatDate(timestamp.Instant.Value)
|
||||
? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g")
|
||||
: "Invalid date";
|
||||
|
||||
var formattedLong = timestamp.Instant?.ToLocalString("dddd, MMMM d, yyyy h:mm tt") ?? "";
|
||||
var formattedLong = timestamp.Instant is not null
|
||||
? _context.FormatDate(timestamp.Instant.Value, "f")
|
||||
: "";
|
||||
|
||||
_buffer.Append(
|
||||
// lang=html
|
||||
|
||||
@@ -139,7 +139,7 @@ internal class HtmlMessageWriter : MessageWriter
|
||||
Minify(
|
||||
await new PostambleTemplate
|
||||
{
|
||||
ExportContext = Context,
|
||||
Context = Context,
|
||||
MessagesWritten = MessagesWritten
|
||||
}.RenderAsync(cancellationToken)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
@@ -196,7 +197,7 @@ internal class JsonMessageWriter : MessageWriter
|
||||
await FormatMarkdownAsync(embed.Title ?? "", cancellationToken)
|
||||
);
|
||||
_writer.WriteString("url", embed.Url);
|
||||
_writer.WriteString("timestamp", embed.Timestamp);
|
||||
_writer.WriteString("timestamp", embed.Timestamp?.Pipe(Context.NormalizeDate));
|
||||
_writer.WriteString(
|
||||
"description",
|
||||
await FormatMarkdownAsync(embed.Description ?? "", cancellationToken)
|
||||
@@ -292,12 +293,12 @@ internal class JsonMessageWriter : MessageWriter
|
||||
|
||||
// Date range
|
||||
_writer.WriteStartObject("dateRange");
|
||||
_writer.WriteString("after", Context.Request.After?.ToDate());
|
||||
_writer.WriteString("before", Context.Request.Before?.ToDate());
|
||||
_writer.WriteString("after", Context.Request.After?.ToDate().Pipe(Context.NormalizeDate));
|
||||
_writer.WriteString("before", Context.Request.Before?.ToDate().Pipe(Context.NormalizeDate));
|
||||
_writer.WriteEndObject();
|
||||
|
||||
// Timestamp
|
||||
_writer.WriteString("exportedAt", System.DateTimeOffset.UtcNow);
|
||||
_writer.WriteString("exportedAt", Context.NormalizeDate(DateTimeOffset.UtcNow));
|
||||
|
||||
// Message array (start)
|
||||
_writer.WriteStartArray("messages");
|
||||
@@ -316,9 +317,15 @@ internal class JsonMessageWriter : MessageWriter
|
||||
// Metadata
|
||||
_writer.WriteString("id", message.Id.ToString());
|
||||
_writer.WriteString("type", message.Kind.ToString());
|
||||
_writer.WriteString("timestamp", message.Timestamp);
|
||||
_writer.WriteString("timestampEdited", message.EditedTimestamp);
|
||||
_writer.WriteString("callEndedTimestamp", message.CallEndedTimestamp);
|
||||
_writer.WriteString("timestamp", Context.NormalizeDate(message.Timestamp));
|
||||
_writer.WriteString(
|
||||
"timestampEdited",
|
||||
message.EditedTimestamp?.Pipe(Context.NormalizeDate)
|
||||
);
|
||||
_writer.WriteString(
|
||||
"callEndedTimestamp",
|
||||
message.CallEndedTimestamp?.Pipe(Context.NormalizeDate)
|
||||
);
|
||||
_writer.WriteBoolean("isPinned", message.IsPinned);
|
||||
|
||||
// Content
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@using System
|
||||
@using System.Collections.Generic
|
||||
@using System.Globalization
|
||||
@using System.Linq
|
||||
@using System.Threading.Tasks
|
||||
@using DiscordChatExporter.Core.Discord.Data
|
||||
@@ -20,8 +19,8 @@
|
||||
ValueTask<string> ResolveAssetUrlAsync(string url) =>
|
||||
Context.ResolveAssetUrlAsync(url, CancellationToken);
|
||||
|
||||
string FormatDate(DateTimeOffset instant) =>
|
||||
Context.FormatDate(instant);
|
||||
string FormatDate(DateTimeOffset instant, string format = "g") =>
|
||||
Context.FormatDate(instant, format);
|
||||
|
||||
async ValueTask<string> FormatMarkdownAsync(string markdown) =>
|
||||
Context.Request.ShouldFormatMarkdown
|
||||
@@ -100,7 +99,7 @@
|
||||
}
|
||||
else if (message.Kind == MessageKind.Call)
|
||||
{
|
||||
<span>started a call that lasted @(((message.CallEndedTimestamp ?? message.Timestamp) - message.Timestamp).TotalMinutes.ToString("n0", CultureInfo.InvariantCulture)) minutes</span>
|
||||
<span>started a call that lasted @(((message.CallEndedTimestamp ?? message.Timestamp) - message.Timestamp).TotalMinutes.ToString("n0", Context.Request.CultureInfo)) minutes</span>
|
||||
}
|
||||
else if (message.Kind == MessageKind.ChannelNameChange)
|
||||
{
|
||||
@@ -132,7 +131,7 @@
|
||||
</span>
|
||||
|
||||
@* Timestamp *@
|
||||
<span class="chatlog__system-notification-timestamp">
|
||||
<span class="chatlog__system-notification-timestamp" title="@FormatDate(message.Timestamp, "f")">
|
||||
<a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a>
|
||||
</span>
|
||||
</div>
|
||||
@@ -154,7 +153,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
|
||||
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp, "f")">@FormatDate(message.Timestamp, "t")</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -194,7 +193,7 @@
|
||||
|
||||
@if (message.ReferencedMessage.EditedTimestamp is not null)
|
||||
{
|
||||
<span class="chatlog__reply-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
|
||||
<span class="chatlog__reply-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value, "f")">(edited)</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -241,7 +240,7 @@
|
||||
}
|
||||
|
||||
@* Timestamp *@
|
||||
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
|
||||
<span class="chatlog__timestamp" title="@FormatDate(message.Timestamp, "f")"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -258,7 +257,7 @@
|
||||
@* Edited timestamp *@
|
||||
@if (message.EditedTimestamp is not null)
|
||||
{
|
||||
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value)">(edited)</span>
|
||||
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value, "f")">(edited)</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -91,9 +91,7 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
|
||||
{
|
||||
_buffer.Append(
|
||||
timestamp.Instant is not null
|
||||
? !string.IsNullOrWhiteSpace(timestamp.Format)
|
||||
? timestamp.Instant.Value.ToLocalString(timestamp.Format)
|
||||
: _context.FormatDate(timestamp.Instant.Value)
|
||||
? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g")
|
||||
: "Invalid date"
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@inherits RazorBlade.HtmlTemplate
|
||||
@using System
|
||||
|
||||
@inherits RazorBlade.HtmlTemplate
|
||||
|
||||
@functions {
|
||||
public required ExportContext ExportContext { get; init; }
|
||||
public required ExportContext Context { get; init; }
|
||||
|
||||
public required long MessagesWritten { get; init; }
|
||||
}
|
||||
@@ -14,7 +16,8 @@
|
||||
<!--/wmm:ignore-->
|
||||
|
||||
<div class="postamble">
|
||||
<div class="postamble__entry">Exported @MessagesWritten.ToString("n0") message(s)</div>
|
||||
<div class="postamble__entry">Exported @MessagesWritten.ToString("n0", Context.Request.CultureInfo) message(s)</div>
|
||||
<div class="postamble__entry">Timezone: UTC@((Context.Request.IsUtcNormalizationEnabled ? 0 : TimeZoneInfo.Local.BaseUtcOffset.TotalHours).ToString("+#.#;-#.#;+0", Context.Request.CultureInfo))</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
ValueTask<string> ResolveAssetUrlAsync(string url) =>
|
||||
Context.ResolveAssetUrlAsync(url, CancellationToken);
|
||||
|
||||
string FormatDate(DateTimeOffset instant) =>
|
||||
Context.FormatDate(instant);
|
||||
string FormatDate(DateTimeOffset instant, string format = "g") =>
|
||||
Context.FormatDate(instant, format);
|
||||
|
||||
async ValueTask<string> FormatMarkdownAsync(string markdown) =>
|
||||
Context.Request.ShouldFormatMarkdown
|
||||
|
||||
@@ -340,16 +340,13 @@ internal static partial class MarkdownParser
|
||||
)
|
||||
);
|
||||
|
||||
var format = m.Groups[2].Value switch
|
||||
var format = m.Groups[2].Value.NullIfWhiteSpace() switch
|
||||
{
|
||||
"t" => "h:mm tt",
|
||||
"T" => "h:mm:ss tt",
|
||||
"d" => "MM/dd/yyyy",
|
||||
"D" => "MMMM dd, yyyy",
|
||||
"f" => "MMMM dd, yyyy h:mm tt",
|
||||
"F" => "dddd, MMMM dd, yyyy h:mm tt",
|
||||
// Relative format is ignored because it doesn't make much sense in a static export
|
||||
_ => null
|
||||
// Ignore the 'relative' format because it doesn't make sense in a static export
|
||||
"r" => null,
|
||||
"R" => null,
|
||||
// Discord's date formats are (mostly) compatible with .NET's date formats
|
||||
var f => f
|
||||
};
|
||||
|
||||
return new TimestampNode(instant, format);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
public static class DateExtensions
|
||||
{
|
||||
public static string ToLocalString(this DateTimeOffset instant, string format) =>
|
||||
instant.ToLocalTime().ToString(format, CultureInfo.InvariantCulture);
|
||||
}
|
||||
Reference in New Issue
Block a user