Embrace Snowflake as first class citizen

This commit is contained in:
Tyrrrz
2020-12-27 19:41:28 +02:00
parent 4ff7990967
commit 3d9ee3b339
36 changed files with 243 additions and 195 deletions

View File

@@ -1,49 +1,40 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.IO;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Utilities;
using DiscordChatExporter.Domain.Discord;
using DiscordChatExporter.Domain.Discord.Models;
using DiscordChatExporter.Domain.Exporting;
namespace DiscordChatExporter.Cli.Commands.Base
{
public abstract partial class ExportCommandBase : TokenCommandBase
public abstract class ExportCommandBase : TokenCommandBase
{
[CommandOption("output", 'o',
Description = "Output file or directory path.")]
public string OutputPath { get; set; } = Directory.GetCurrentDirectory();
[CommandOption("output", 'o', Description = "Output file or directory path.")]
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
[CommandOption("format", 'f',
Description = "Export format.")]
public ExportFormat ExportFormat { get; set; } = ExportFormat.HtmlDark;
[CommandOption("format", 'f', Description = "Export format.")]
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark;
[CommandOption("after",
Description = "Only include messages sent after this date. Alternatively, provide the ID of a message.")]
public string? After { get; set; }
[CommandOption("after", Description = "Only include messages sent after this date or message ID.")]
public Snowflake? After { get; init; }
[CommandOption("before",
Description = "Only include messages sent before this date. Alternatively, provide the ID of a message.")]
public string? Before { get; set; }
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
public Snowflake? Before { get; init; }
[CommandOption("partition", 'p',
Description = "Split output into partitions limited to this number of messages.")]
public int? PartitionLimit { get; set; }
[CommandOption("partition", 'p', Description = "Split output into partitions limited to this number of messages.")]
public int? PartitionLimit { get; init; }
[CommandOption("media",
Description = "Download referenced media content.")]
public bool ShouldDownloadMedia { get; set; }
[CommandOption("media", Description = "Download referenced media content.")]
public bool ShouldDownloadMedia { get; init; }
[CommandOption("reuse-media",
Description = "Reuse already existing media content to skip redundant downloads.")]
public bool ShouldReuseMedia { get; set; }
[CommandOption("reuse-media", Description = "Reuse already existing media content to skip redundant downloads.")]
public bool ShouldReuseMedia { get; init; }
[CommandOption("dateformat",
Description = "Format used when writing dates.")]
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
[CommandOption("dateformat", Description = "Format used when writing dates.")]
public string DateFormat { get; init; } = "dd-MMM-yy hh:mm tt";
protected ChannelExporter GetChannelExporter() => new(GetDiscordClient());
@@ -57,8 +48,8 @@ namespace DiscordChatExporter.Cli.Commands.Base
channel,
OutputPath,
ExportFormat,
ParseRangeOption(After, "--after"),
ParseRangeOption(Before, "--before"),
After,
Before,
PartitionLimit,
ShouldDownloadMedia,
ShouldReuseMedia,
@@ -77,7 +68,7 @@ namespace DiscordChatExporter.Cli.Commands.Base
await ExportAsync(console, guild, channel);
}
protected async ValueTask ExportAsync(IConsole console, string channelId)
protected async ValueTask ExportAsync(IConsole console, Snowflake channelId)
{
var channel = await GetDiscordClient().GetChannelAsync(channelId);
await ExportAsync(console, channel);
@@ -93,29 +84,4 @@ namespace DiscordChatExporter.Cli.Commands.Base
return default;
}
}
public abstract partial class ExportCommandBase : TokenCommandBase
{
protected static DateTimeOffset? ParseRangeOption(string? value, string optionName)
{
if (value == null) return null;
var isSnowflake = Regex.IsMatch(value, @"^\d{18}$");
var isDate = DateTimeOffset.TryParse(value, out var datetime);
if (!isSnowflake && !isDate)
{
throw new ArgumentException($"Value for ${optionName} must be either a date or a message ID.");
}
return isSnowflake ? ExtractDateTimeFromSnowflake() : datetime;
DateTimeOffset ExtractDateTimeFromSnowflake()
{
var unixTimestampMs = (long.Parse(value) / 4194304 + 1420070400000);
return DateTimeOffset.FromUnixTimeMilliseconds(unixTimestampMs);
}
}
}
}

View File

@@ -17,9 +17,8 @@ namespace DiscordChatExporter.Cli.Commands.Base
{
public abstract class ExportMultipleCommandBase : ExportCommandBase
{
[CommandOption("parallel",
Description = "Limits how many channels can be exported in parallel.")]
public int ParallelLimit { get; set; } = 1;
[CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")]
public int ParallelLimit { get; init; } = 1;
protected async ValueTask ExportMultipleAsync(IConsole console, IReadOnlyList<Channel> channels)
{
@@ -47,8 +46,8 @@ namespace DiscordChatExporter.Cli.Commands.Base
channel,
OutputPath,
ExportFormat,
ParseRangeOption(After, "--after"),
ParseRangeOption(Before, "--before"),
After,
Before,
PartitionLimit,
ShouldDownloadMedia,
ShouldReuseMedia,

View File

@@ -7,15 +7,11 @@ namespace DiscordChatExporter.Cli.Commands.Base
{
public abstract class TokenCommandBase : ICommand
{
[CommandOption("token", 't', IsRequired = true,
EnvironmentVariableName = "DISCORD_TOKEN",
Description = "Authorization token.")]
public string TokenValue { get; set; } = "";
[CommandOption("token", 't', IsRequired = true, EnvironmentVariableName = "DISCORD_TOKEN", Description = "Authorization token.")]
public string TokenValue { get; init; } = "";
[CommandOption("bot", 'b',
EnvironmentVariableName = "DISCORD_TOKEN_BOT",
Description = "Authorize as a bot.")]
public bool IsBotToken { get; set; }
[CommandOption("bot", 'b', EnvironmentVariableName = "DISCORD_TOKEN_BOT", Description = "Authorize as a bot.")]
public bool IsBotToken { get; init; }
protected AuthToken GetAuthToken() => new(
IsBotToken