mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-23 11:16:59 -04:00
Switch from DateTime to DateTimeOffset
This commit is contained in:
parent
4bfb2ec7fd
commit
6d9bc3625f
20 changed files with 122 additions and 79 deletions
|
@ -12,9 +12,11 @@ namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
[Option('o', "output", Default = null, HelpText = "Output file or directory path.")]
|
[Option('o', "output", Default = null, HelpText = "Output file or directory path.")]
|
||||||
public string OutputPath { get; set; }
|
public string OutputPath { get; set; }
|
||||||
|
|
||||||
|
// HACK: CommandLineParser doesn't support DateTimeOffset
|
||||||
[Option("after", Default = null, HelpText = "Limit to messages sent after this date.")]
|
[Option("after", Default = null, HelpText = "Limit to messages sent after this date.")]
|
||||||
public DateTime? After { get; set; }
|
public DateTime? After { get; set; }
|
||||||
|
|
||||||
|
// HACK: CommandLineParser doesn't support DateTimeOffset
|
||||||
[Option("before", Default = null, HelpText = "Limit to messages sent before this date.")]
|
[Option("before", Default = null, HelpText = "Limit to messages sent before this date.")]
|
||||||
public DateTime? Before { get; set; }
|
public DateTime? Before { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -9,21 +9,21 @@ namespace DiscordChatExporter.Core.Models
|
||||||
|
|
||||||
public Channel Channel { get; }
|
public Channel Channel { get; }
|
||||||
|
|
||||||
public DateTime? From { get; }
|
public DateTimeOffset? After { get; }
|
||||||
|
|
||||||
public DateTime? To { get; }
|
public DateTimeOffset? Before { get; }
|
||||||
|
|
||||||
public IReadOnlyList<Message> Messages { get; }
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
public Mentionables Mentionables { get; }
|
public Mentionables Mentionables { get; }
|
||||||
|
|
||||||
public ChatLog(Guild guild, Channel channel, DateTime? from, DateTime? to,
|
public ChatLog(Guild guild, Channel channel, DateTimeOffset? after, DateTimeOffset? before,
|
||||||
IReadOnlyList<Message> messages, Mentionables mentionables)
|
IReadOnlyList<Message> messages, Mentionables mentionables)
|
||||||
{
|
{
|
||||||
Guild = guild;
|
Guild = guild;
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
From = from;
|
After = after;
|
||||||
To = to;
|
Before = before;
|
||||||
Messages = messages;
|
Messages = messages;
|
||||||
Mentionables = mentionables;
|
Mentionables = mentionables;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace DiscordChatExporter.Core.Models
|
||||||
|
|
||||||
public string Url { get; }
|
public string Url { get; }
|
||||||
|
|
||||||
public DateTime? Timestamp { get; }
|
public DateTimeOffset? Timestamp { get; }
|
||||||
|
|
||||||
public Color Color { get; }
|
public Color Color { get; }
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace DiscordChatExporter.Core.Models
|
||||||
|
|
||||||
public EmbedFooter Footer { get; }
|
public EmbedFooter Footer { get; }
|
||||||
|
|
||||||
public Embed(string title, string url, DateTime? timestamp, Color color, EmbedAuthor author, string description,
|
public Embed(string title, string url, DateTimeOffset? timestamp, Color color, EmbedAuthor author, string description,
|
||||||
IReadOnlyList<EmbedField> fields, EmbedImage thumbnail, EmbedImage image, EmbedFooter footer)
|
IReadOnlyList<EmbedField> fields, EmbedImage thumbnail, EmbedImage image, EmbedFooter footer)
|
||||||
{
|
{
|
||||||
Title = title;
|
Title = title;
|
||||||
|
|
|
@ -15,9 +15,9 @@ namespace DiscordChatExporter.Core.Models
|
||||||
|
|
||||||
public User Author { get; }
|
public User Author { get; }
|
||||||
|
|
||||||
public DateTime Timestamp { get; }
|
public DateTimeOffset Timestamp { get; }
|
||||||
|
|
||||||
public DateTime? EditedTimestamp { get; }
|
public DateTimeOffset? EditedTimestamp { get; }
|
||||||
|
|
||||||
public string Content { get; }
|
public string Content { get; }
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ namespace DiscordChatExporter.Core.Models
|
||||||
|
|
||||||
public IReadOnlyList<User> MentionedUsers { get; }
|
public IReadOnlyList<User> MentionedUsers { get; }
|
||||||
|
|
||||||
public Message(string id, string channelId, MessageType type, User author, DateTime timestamp,
|
public Message(string id, string channelId, MessageType type, User author, DateTimeOffset timestamp,
|
||||||
DateTime? editedTimestamp, string content, IReadOnlyList<Attachment> attachments,
|
DateTimeOffset? editedTimestamp, string content, IReadOnlyList<Attachment> attachments,
|
||||||
IReadOnlyList<Embed> embeds, IReadOnlyList<Reaction> reactions, IReadOnlyList<User> mentionedUsers)
|
IReadOnlyList<Embed> embeds, IReadOnlyList<Reaction> reactions, IReadOnlyList<User> mentionedUsers)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
|
|
|
@ -22,7 +22,8 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
_dateFormat = dateFormat;
|
_dateFormat = dateFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatDate(DateTime date) => date.ToString(_dateFormat, CultureInfo.InvariantCulture);
|
private string FormatDate(DateTimeOffset date) =>
|
||||||
|
date.ToLocalTime().ToString(_dateFormat, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
private string FormatMarkdown(Node node)
|
private string FormatMarkdown(Node node)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,11 +10,11 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
{
|
{
|
||||||
public User Author { get; }
|
public User Author { get; }
|
||||||
|
|
||||||
public DateTime Timestamp { get; }
|
public DateTimeOffset Timestamp { get; }
|
||||||
|
|
||||||
public IReadOnlyList<Message> Messages { get; }
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
public MessageGroup(User author, DateTime timestamp, IReadOnlyList<Message> messages)
|
public MessageGroup(User author, DateTimeOffset timestamp, IReadOnlyList<Message> messages)
|
||||||
{
|
{
|
||||||
Author = author;
|
Author = author;
|
||||||
Timestamp = timestamp;
|
Timestamp = timestamp;
|
||||||
|
|
|
@ -29,7 +29,8 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
|
|
||||||
private string HtmlEncode(string s) => WebUtility.HtmlEncode(s);
|
private string HtmlEncode(string s) => WebUtility.HtmlEncode(s);
|
||||||
|
|
||||||
private string FormatDate(DateTime date) => date.ToString(_dateFormat, CultureInfo.InvariantCulture);
|
private string FormatDate(DateTimeOffset date) =>
|
||||||
|
date.ToLocalTime().ToString(_dateFormat, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
private IEnumerable<MessageGroup> GroupMessages(IEnumerable<Message> messages) =>
|
private IEnumerable<MessageGroup> GroupMessages(IEnumerable<Message> messages) =>
|
||||||
messages.GroupContiguous((buffer, message) =>
|
messages.GroupContiguous((buffer, message) =>
|
||||||
|
@ -182,7 +183,7 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
var model = new ScriptObject();
|
var model = new ScriptObject();
|
||||||
model.SetValue("Model", _chatLog, true);
|
model.SetValue("Model", _chatLog, true);
|
||||||
model.Import(nameof(GroupMessages), new Func<IEnumerable<Message>, IEnumerable<MessageGroup>>(GroupMessages));
|
model.Import(nameof(GroupMessages), new Func<IEnumerable<Message>, IEnumerable<MessageGroup>>(GroupMessages));
|
||||||
model.Import(nameof(FormatDate), new Func<DateTime, string>(FormatDate));
|
model.Import(nameof(FormatDate), new Func<DateTimeOffset, string>(FormatDate));
|
||||||
model.Import(nameof(FormatMarkdown), new Func<string, string>(FormatMarkdown));
|
model.Import(nameof(FormatMarkdown), new Func<string, string>(FormatMarkdown));
|
||||||
context.PushGlobal(model);
|
context.PushGlobal(model);
|
||||||
|
|
||||||
|
|
|
@ -22,21 +22,22 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
_dateFormat = dateFormat;
|
_dateFormat = dateFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatDate(DateTime date) => date.ToString(_dateFormat, CultureInfo.InvariantCulture);
|
private string FormatDate(DateTimeOffset date) =>
|
||||||
|
date.ToLocalTime().ToString(_dateFormat, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
private string FormatDateRange(DateTime? from, DateTime? to)
|
private string FormatDateRange(DateTimeOffset? after, DateTimeOffset? before)
|
||||||
{
|
{
|
||||||
// Both 'from' and 'to'
|
// Both 'after' and 'before'
|
||||||
if (from.HasValue && to.HasValue)
|
if (after != null && before != null)
|
||||||
return $"{FormatDate(from.Value)} to {FormatDate(to.Value)}";
|
return $"{FormatDate(after.Value)} to {FormatDate(before.Value)}";
|
||||||
|
|
||||||
// Just 'from'
|
// Just 'after'
|
||||||
if (from.HasValue)
|
if (after != null)
|
||||||
return $"after {FormatDate(from.Value)}";
|
return $"after {FormatDate(after.Value)}";
|
||||||
|
|
||||||
// Just 'to'
|
// Just 'before'
|
||||||
if (to.HasValue)
|
if (before != null)
|
||||||
return $"before {FormatDate(to.Value)}";
|
return $"before {FormatDate(before.Value)}";
|
||||||
|
|
||||||
// Neither
|
// Neither
|
||||||
return null;
|
return null;
|
||||||
|
@ -113,7 +114,7 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
await writer.WriteLineAsync($"Channel: {_chatLog.Channel.Name}");
|
await writer.WriteLineAsync($"Channel: {_chatLog.Channel.Name}");
|
||||||
await writer.WriteLineAsync($"Topic: {_chatLog.Channel.Topic}");
|
await writer.WriteLineAsync($"Topic: {_chatLog.Channel.Topic}");
|
||||||
await writer.WriteLineAsync($"Messages: {_chatLog.Messages.Count:N0}");
|
await writer.WriteLineAsync($"Messages: {_chatLog.Messages.Count:N0}");
|
||||||
await writer.WriteLineAsync($"Range: {FormatDateRange(_chatLog.From, _chatLog.To)}");
|
await writer.WriteLineAsync($"Range: {FormatDateRange(_chatLog.After, _chatLog.Before)}");
|
||||||
await writer.WriteLineAsync('='.Repeat(62));
|
await writer.WriteLineAsync('='.Repeat(62));
|
||||||
await writer.WriteLineAsync();
|
await writer.WriteLineAsync();
|
||||||
|
|
||||||
|
|
|
@ -43,14 +43,14 @@
|
||||||
|
|
||||||
<div class="info__channel-message-count">{{ Model.Messages | array.size | object.format "N0" }} messages</div>
|
<div class="info__channel-message-count">{{ Model.Messages | array.size | object.format "N0" }} messages</div>
|
||||||
|
|
||||||
{{~ if Model.From || Model.To ~}}
|
{{~ if Model.After || Model.Before ~}}
|
||||||
<div class="info__channel-date-range">
|
<div class="info__channel-date-range">
|
||||||
{{~ if Model.From && Model.To ~}}
|
{{~ if Model.After && Model.Before ~}}
|
||||||
Between {{ Model.From | FormatDate | html.escape }} and {{ Model.To | FormatDate | html.escape }}
|
Between {{ Model.After | FormatDate | html.escape }} and {{ Model.Before | FormatDate | html.escape }}
|
||||||
{{~ else if Model.From ~}}
|
{{~ else if Model.After ~}}
|
||||||
After {{ Model.From | FormatDate | html.escape }}
|
After {{ Model.After | FormatDate | html.escape }}
|
||||||
{{~ else if Model.To ~}}
|
{{~ else if Model.Before ~}}
|
||||||
Before {{ Model.To | FormatDate | html.escape }}
|
Before {{ Model.Before | FormatDate | html.escape }}
|
||||||
{{~ end ~}}
|
{{~ end ~}}
|
||||||
</div>
|
</div>
|
||||||
{{~ end ~}}
|
{{~ end ~}}
|
||||||
|
|
|
@ -117,7 +117,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
var title = json["title"]?.Value<string>();
|
var title = json["title"]?.Value<string>();
|
||||||
var description = json["description"]?.Value<string>();
|
var description = json["description"]?.Value<string>();
|
||||||
var url = json["url"]?.Value<string>();
|
var url = json["url"]?.Value<string>();
|
||||||
var timestamp = json["timestamp"]?.Value<DateTime>();
|
var timestamp = json["timestamp"]?.Value<DateTimeOffset>();
|
||||||
|
|
||||||
// Get color
|
// Get color
|
||||||
var color = json["color"] != null
|
var color = json["color"] != null
|
||||||
|
@ -164,8 +164,8 @@ namespace DiscordChatExporter.Core.Services
|
||||||
// Get basic data
|
// Get basic data
|
||||||
var id = json["id"].Value<string>();
|
var id = json["id"].Value<string>();
|
||||||
var channelId = json["channel_id"].Value<string>();
|
var channelId = json["channel_id"].Value<string>();
|
||||||
var timestamp = json["timestamp"].Value<DateTime>();
|
var timestamp = json["timestamp"].Value<DateTimeOffset>();
|
||||||
var editedTimestamp = json["edited_timestamp"]?.Value<DateTime?>();
|
var editedTimestamp = json["edited_timestamp"]?.Value<DateTimeOffset?>();
|
||||||
var content = json["content"].Value<string>();
|
var content = json["content"].Value<string>();
|
||||||
var type = (MessageType) json["type"].Value<int>();
|
var type = (MessageType) json["type"].Value<int>();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
@ -8,6 +9,7 @@ using DiscordChatExporter.Core.Models;
|
||||||
using DiscordChatExporter.Core.Services.Exceptions;
|
using DiscordChatExporter.Core.Services.Exceptions;
|
||||||
using DiscordChatExporter.Core.Services.Internal;
|
using DiscordChatExporter.Core.Services.Internal;
|
||||||
using Failsafe;
|
using Failsafe;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
@ -64,7 +66,12 @@ namespace DiscordChatExporter.Core.Services
|
||||||
var raw = await response.Content.ReadAsStringAsync();
|
var raw = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
return JToken.Parse(raw);
|
using (var reader = new JsonTextReader(new StringReader(raw)))
|
||||||
|
{
|
||||||
|
reader.DateParseHandling = DateParseHandling.DateTimeOffset;
|
||||||
|
|
||||||
|
return JToken.Load(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -123,24 +130,24 @@ namespace DiscordChatExporter.Core.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(AuthToken token, string channelId,
|
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(AuthToken token, string channelId,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
var result = new List<Message>();
|
var result = new List<Message>();
|
||||||
|
|
||||||
// Get the last message
|
// Get the last message
|
||||||
var response = await GetApiResponseAsync(token, "channels", $"{channelId}/messages",
|
var response = await GetApiResponseAsync(token, "channels", $"{channelId}/messages",
|
||||||
"limit=1", $"before={to?.ToSnowflake()}");
|
"limit=1", $"before={before?.ToSnowflake()}");
|
||||||
var lastMessage = response.Select(ParseMessage).FirstOrDefault();
|
var lastMessage = response.Select(ParseMessage).FirstOrDefault();
|
||||||
|
|
||||||
// If the last message doesn't exist or it's outside of range - return
|
// If the last message doesn't exist or it's outside of range - return
|
||||||
if (lastMessage == null || lastMessage.Timestamp < from)
|
if (lastMessage == null || lastMessage.Timestamp < after)
|
||||||
{
|
{
|
||||||
progress?.Report(1);
|
progress?.Report(1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get other messages
|
// Get other messages
|
||||||
var offsetId = from?.ToSnowflake() ?? "0";
|
var offsetId = after?.ToSnowflake() ?? "0";
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Get message batch
|
// Get message batch
|
||||||
|
@ -215,19 +222,19 @@ namespace DiscordChatExporter.Core.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Guild guild, Channel channel,
|
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Guild guild, Channel channel,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
// Get messages
|
// Get messages
|
||||||
var messages = await GetChannelMessagesAsync(token, channel.Id, from, to, progress);
|
var messages = await GetChannelMessagesAsync(token, channel.Id, after, before, progress);
|
||||||
|
|
||||||
// Get mentionables
|
// Get mentionables
|
||||||
var mentionables = await GetMentionablesAsync(token, guild.Id, messages);
|
var mentionables = await GetMentionablesAsync(token, guild.Id, messages);
|
||||||
|
|
||||||
return new ChatLog(guild, channel, from, to, messages, mentionables);
|
return new ChatLog(guild, channel, after, before, messages, mentionables);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Channel channel,
|
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Channel channel,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
// Get guild
|
// Get guild
|
||||||
var guild = channel.GuildId == Guild.DirectMessages.Id
|
var guild = channel.GuildId == Guild.DirectMessages.Id
|
||||||
|
@ -235,17 +242,17 @@ namespace DiscordChatExporter.Core.Services
|
||||||
: await GetGuildAsync(token, channel.GuildId);
|
: await GetGuildAsync(token, channel.GuildId);
|
||||||
|
|
||||||
// Get the chat log
|
// Get the chat log
|
||||||
return await GetChatLogAsync(token, guild, channel, from, to, progress);
|
return await GetChatLogAsync(token, guild, channel, after, before, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, string channelId,
|
public async Task<ChatLog> GetChatLogAsync(AuthToken token, string channelId,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
// Get channel
|
// Get channel
|
||||||
var channel = await GetChannelAsync(token, channelId);
|
var channel = await GetChannelAsync(token, channelId);
|
||||||
|
|
||||||
// Get the chat log
|
// Get the chat log
|
||||||
return await GetChatLogAsync(token, channel, from, to, progress);
|
return await GetChatLogAsync(token, channel, after, before, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
// Create partitions by grouping up to X contiguous messages into separate chat logs
|
// Create partitions by grouping up to X contiguous messages into separate chat logs
|
||||||
var partitions = chatLog.Messages.GroupContiguous(g => g.Count < partitionLimit.Value)
|
var partitions = chatLog.Messages.GroupContiguous(g => g.Count < partitionLimit.Value)
|
||||||
.Select(g => new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.From, chatLog.To, g, chatLog.Mentionables))
|
.Select(g => new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.After, chatLog.Before, g, chatLog.Mentionables))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Split file path into components
|
// Split file path into components
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace DiscordChatExporter.Core.Services.Helpers
|
||||||
Path.GetExtension(path) == null;
|
Path.GetExtension(path) == null;
|
||||||
|
|
||||||
public static string GetDefaultExportFileName(ExportFormat format, Guild guild, Channel channel,
|
public static string GetDefaultExportFileName(ExportFormat format, Guild guild, Channel channel,
|
||||||
DateTime? from = null, DateTime? to = null)
|
DateTimeOffset? after = null, DateTimeOffset? before = null)
|
||||||
{
|
{
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
|
|
||||||
|
@ -22,24 +22,24 @@ namespace DiscordChatExporter.Core.Services.Helpers
|
||||||
result.Append($"{guild.Name} - {channel.Name} [{channel.Id}]");
|
result.Append($"{guild.Name} - {channel.Name} [{channel.Id}]");
|
||||||
|
|
||||||
// Append date range
|
// Append date range
|
||||||
if (from != null || to != null)
|
if (after != null || before != null)
|
||||||
{
|
{
|
||||||
result.Append(" (");
|
result.Append(" (");
|
||||||
|
|
||||||
// Both 'from' and 'to' are set
|
// Both 'after' and 'before' are set
|
||||||
if (from != null && to != null)
|
if (after != null && before != null)
|
||||||
{
|
{
|
||||||
result.Append($"{from:yyyy-MM-dd} to {to:yyyy-MM-dd}");
|
result.Append($"{after:yyyy-MM-dd} to {before:yyyy-MM-dd}");
|
||||||
}
|
}
|
||||||
// Only 'from' is set
|
// Only 'after' is set
|
||||||
else if (from != null)
|
else if (after != null)
|
||||||
{
|
{
|
||||||
result.Append($"after {from:yyyy-MM-dd}");
|
result.Append($"after {after:yyyy-MM-dd}");
|
||||||
}
|
}
|
||||||
// Only 'to' is set
|
// Only 'before' is set
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.Append($"before {to:yyyy-MM-dd}");
|
result.Append($"before {before:yyyy-MM-dd}");
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Append(")");
|
result.Append(")");
|
||||||
|
|
|
@ -5,10 +5,10 @@ namespace DiscordChatExporter.Core.Services.Internal
|
||||||
{
|
{
|
||||||
internal static class Extensions
|
internal static class Extensions
|
||||||
{
|
{
|
||||||
public static string ToSnowflake(this DateTime dateTime)
|
public static string ToSnowflake(this DateTimeOffset date)
|
||||||
{
|
{
|
||||||
const long epoch = 62135596800000;
|
const long epoch = 62135596800000;
|
||||||
var unixTime = dateTime.ToUniversalTime().Ticks / TimeSpan.TicksPerMillisecond - epoch;
|
var unixTime = date.ToUniversalTime().Ticks / TimeSpan.TicksPerMillisecond - epoch;
|
||||||
var value = ((ulong) unixTime - 1420070400000UL) << 22;
|
var value = ((ulong) unixTime - 1420070400000UL) << 22;
|
||||||
return value.ToString();
|
return value.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Gui.Converters
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(DateTimeOffset), typeof(DateTime))]
|
||||||
|
public class DateTimeOffsetToDateTimeConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static DateTimeOffsetToDateTimeConverter Instance { get; } = new DateTimeOffsetToDateTimeConverter();
|
||||||
|
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is DateTimeOffset date)
|
||||||
|
return date.DateTime;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is DateTime date)
|
||||||
|
return new DateTimeOffset(date);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,10 @@ namespace DiscordChatExporter.Gui.Converters
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
var format = value as ExportFormat?;
|
if (value is ExportFormat format)
|
||||||
return format?.GetDisplayName();
|
return format.GetDisplayName();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
<Compile Include="Behaviors\ChannelViewModelMultiSelectionListBoxBehavior.cs" />
|
<Compile Include="Behaviors\ChannelViewModelMultiSelectionListBoxBehavior.cs" />
|
||||||
<Compile Include="Behaviors\MultiSelectionListBoxBehavior.cs" />
|
<Compile Include="Behaviors\MultiSelectionListBoxBehavior.cs" />
|
||||||
<Compile Include="Bootstrapper.cs" />
|
<Compile Include="Bootstrapper.cs" />
|
||||||
|
<Compile Include="Converters\DateTimeOffsetToDateTimeConverter.cs" />
|
||||||
<Compile Include="Converters\ExportFormatToStringConverter.cs" />
|
<Compile Include="Converters\ExportFormatToStringConverter.cs" />
|
||||||
<Compile Include="ViewModels\Components\ChannelViewModel.cs" />
|
<Compile Include="ViewModels\Components\ChannelViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Components\GuildViewModel.cs" />
|
<Compile Include="ViewModels\Components\GuildViewModel.cs" />
|
||||||
|
|
|
@ -27,9 +27,9 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
|
|
||||||
public ExportFormat SelectedFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat SelectedFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
||||||
public DateTime? From { get; set; }
|
public DateTimeOffset? After { get; set; }
|
||||||
|
|
||||||
public DateTime? To { get; set; }
|
public DateTimeOffset? Before { get; set; }
|
||||||
|
|
||||||
public int? PartitionLimit { get; set; }
|
public int? PartitionLimit { get; set; }
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
_settingsService.LastExportFormat = SelectedFormat;
|
_settingsService.LastExportFormat = SelectedFormat;
|
||||||
_settingsService.LastPartitionLimit = PartitionLimit;
|
_settingsService.LastPartitionLimit = PartitionLimit;
|
||||||
|
|
||||||
// Clamp 'from' and 'to' values
|
// Clamp 'after' and 'before' values
|
||||||
if (From > To)
|
if (After > Before)
|
||||||
From = To;
|
After = Before;
|
||||||
if (To < From)
|
if (Before < After)
|
||||||
To = From;
|
Before = After;
|
||||||
|
|
||||||
// If single channel - prompt file path
|
// If single channel - prompt file path
|
||||||
if (IsSingleChannel)
|
if (IsSingleChannel)
|
||||||
|
@ -67,7 +67,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
var channel = Channels.Single();
|
var channel = Channels.Single();
|
||||||
|
|
||||||
// Generate default file name
|
// Generate default file name
|
||||||
var defaultFileName = ExportHelper.GetDefaultExportFileName(SelectedFormat, Guild, channel, From, To);
|
var defaultFileName = ExportHelper.GetDefaultExportFileName(SelectedFormat, Guild, channel, After, Before);
|
||||||
|
|
||||||
// Generate filter
|
// Generate filter
|
||||||
var ext = SelectedFormat.GetFileExtension();
|
var ext = SelectedFormat.GetFileExtension();
|
||||||
|
|
|
@ -267,7 +267,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
{
|
{
|
||||||
// Generate default file name
|
// Generate default file name
|
||||||
var fileName = ExportHelper.GetDefaultExportFileName(dialog.SelectedFormat, dialog.Guild,
|
var fileName = ExportHelper.GetDefaultExportFileName(dialog.SelectedFormat, dialog.Guild,
|
||||||
channel, dialog.From, dialog.To);
|
channel, dialog.After, dialog.Before);
|
||||||
|
|
||||||
// Combine paths
|
// Combine paths
|
||||||
filePath = Path.Combine(filePath, fileName);
|
filePath = Path.Combine(filePath, fileName);
|
||||||
|
@ -275,7 +275,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
// Get chat log
|
// Get chat log
|
||||||
var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild, channel,
|
var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild, channel,
|
||||||
dialog.From, dialog.To, operation);
|
dialog.After, dialog.Before, operation);
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
await _exportService.ExportChatLogAsync(chatLog, filePath, dialog.SelectedFormat,
|
await _exportService.ExportChatLogAsync(chatLog, filePath, dialog.SelectedFormat,
|
||||||
|
|
|
@ -86,8 +86,8 @@
|
||||||
Margin="16,8"
|
Margin="16,8"
|
||||||
materialDesign:HintAssist.Hint="From (optional)"
|
materialDesign:HintAssist.Hint="From (optional)"
|
||||||
materialDesign:HintAssist.IsFloating="True"
|
materialDesign:HintAssist.IsFloating="True"
|
||||||
DisplayDateEnd="{Binding To}"
|
DisplayDateEnd="{Binding Before, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
|
||||||
SelectedDate="{Binding From}"
|
SelectedDate="{Binding After, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
|
||||||
ToolTip="If this is set, only messages sent after this date will be exported" />
|
ToolTip="If this is set, only messages sent after this date will be exported" />
|
||||||
<DatePicker
|
<DatePicker
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
|
@ -95,8 +95,8 @@
|
||||||
Margin="16,8"
|
Margin="16,8"
|
||||||
materialDesign:HintAssist.Hint="To (optional)"
|
materialDesign:HintAssist.Hint="To (optional)"
|
||||||
materialDesign:HintAssist.IsFloating="True"
|
materialDesign:HintAssist.IsFloating="True"
|
||||||
DisplayDateStart="{Binding From}"
|
DisplayDateStart="{Binding After, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
|
||||||
SelectedDate="{Binding To}"
|
SelectedDate="{Binding Before, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}"
|
||||||
ToolTip="If this is set, only messages sent before this date will be exported" />
|
ToolTip="If this is set, only messages sent before this date will be exported" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue