mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-14 07:43:31 -07:00
Refactor
This commit is contained in:
parent
ad2dab2157
commit
09f8937d99
|
|
@ -186,7 +186,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1124
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1124
|
||||||
ParallelLimit > 1
|
ParallelLimit > 1
|
||||||
)
|
)
|
||||||
.StartAsync(async progressContext =>
|
.StartAsync(async ctx =>
|
||||||
{
|
{
|
||||||
await Parallel.ForEachAsync(
|
await Parallel.ForEachAsync(
|
||||||
channels,
|
channels,
|
||||||
|
|
@ -199,7 +199,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await progressContext.StartTaskAsync(
|
await ctx.StartTaskAsync(
|
||||||
channel.GetHierarchicalName(),
|
channel.GetHierarchicalName(),
|
||||||
async progress =>
|
async progress =>
|
||||||
{
|
{
|
||||||
|
|
@ -257,7 +257,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||||
{
|
{
|
||||||
await console.Error.WriteLineAsync(
|
await console.Error.WriteLineAsync(
|
||||||
$"Failed to export {errorsByChannel.Count} channel(s):"
|
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Exceptions;
|
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Cli.Commands.Converters;
|
using DiscordChatExporter.Cli.Commands.Converters;
|
||||||
using DiscordChatExporter.Cli.Commands.Shared;
|
using DiscordChatExporter.Cli.Commands.Shared;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Cli.Utils.Extensions;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
using DiscordChatExporter.Core.Discord.Dump;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using JsonExtensions.Reading;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands;
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
@ -55,32 +53,53 @@ public class ExportAllCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
await console.Output.WriteLineAsync($"Fetching channels for guild '{guild.Name}'...");
|
|
||||||
|
|
||||||
// Regular channels
|
// Regular channels
|
||||||
await foreach (
|
await console.Output.WriteLineAsync(
|
||||||
var channel in Discord.GetGuildChannelsAsync(guild.Id, cancellationToken)
|
$"Fetching channels for guild '{guild.Name}'..."
|
||||||
)
|
);
|
||||||
{
|
|
||||||
if (channel.IsCategory)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!IncludeVoiceChannels && channel.IsVoice)
|
var fetchedChannelsCount = 0;
|
||||||
continue;
|
await console
|
||||||
|
.CreateStatusTicker()
|
||||||
|
.StartAsync(
|
||||||
|
"...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
await foreach (
|
||||||
|
var channel in Discord.GetGuildChannelsAsync(
|
||||||
|
guild.Id,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (channel.IsCategory)
|
||||||
|
continue;
|
||||||
|
|
||||||
channels.Add(channel);
|
if (!IncludeVoiceChannels && channel.IsVoice)
|
||||||
}
|
continue;
|
||||||
|
|
||||||
await console.Output.WriteLineAsync($" Found {channels.Count} channels.");
|
channels.Add(channel);
|
||||||
|
|
||||||
|
ctx.Status($"Fetched '{channel.GetHierarchicalName()}'.");
|
||||||
|
fetchedChannelsCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await console.Output.WriteLineAsync($"Fetched {fetchedChannelsCount} channel(s).");
|
||||||
|
|
||||||
// Threads
|
// Threads
|
||||||
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine("Fetching threads...");
|
await console.Output.WriteLineAsync(
|
||||||
await AnsiConsole
|
$"Fetching threads for guild '{guild.Name}'..."
|
||||||
.Status()
|
);
|
||||||
|
|
||||||
|
var fetchedThreadsCount = 0;
|
||||||
|
await console
|
||||||
|
.CreateStatusTicker()
|
||||||
.StartAsync(
|
.StartAsync(
|
||||||
"Found 0 threads.",
|
"...",
|
||||||
async ctx =>
|
async ctx =>
|
||||||
{
|
{
|
||||||
await foreach (
|
await foreach (
|
||||||
|
|
@ -94,14 +113,15 @@ public class ExportAllCommand : ExportCommandBase
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
channels.Add(thread);
|
channels.Add(thread);
|
||||||
ctx.Status(
|
|
||||||
$"Found {channels.Count(channel => channel.IsThread)} threads: {thread.GetHierarchicalName()}"
|
ctx.Status($"Fetched '{thread.GetHierarchicalName()}'.");
|
||||||
);
|
fetchedThreadsCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await console.Output.WriteLineAsync(
|
await console.Output.WriteLineAsync(
|
||||||
$" Found {channels.Count(channel => channel.IsThread)} threads."
|
$"Fetched {fetchedThreadsCount} thread(s)."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,39 +130,55 @@ public class ExportAllCommand : ExportCommandBase
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await console.Output.WriteLineAsync("Extracting channels...");
|
await console.Output.WriteLineAsync("Extracting channels...");
|
||||||
using var archive = ZipFile.OpenRead(DataPackageFilePath);
|
|
||||||
|
|
||||||
var entry = archive.GetEntry("messages/index.json");
|
var dump = await DataDump.LoadAsync(DataPackageFilePath, cancellationToken);
|
||||||
if (entry is null)
|
var inaccessibleChannels = new List<DataDumpChannel>();
|
||||||
throw new CommandException("Could not find channel index inside the data package.");
|
|
||||||
|
|
||||||
await using var stream = entry.Open();
|
await console
|
||||||
using var document = await JsonDocument.ParseAsync(stream, default, cancellationToken);
|
.CreateStatusTicker()
|
||||||
|
.StartAsync(
|
||||||
|
"...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
foreach (var dumpChannel in dump.Channels)
|
||||||
|
{
|
||||||
|
ctx.Status($"Fetching '{dumpChannel.Name}' ({dumpChannel.Id})...");
|
||||||
|
|
||||||
foreach (var property in document.RootElement.EnumerateObjectOrEmpty())
|
try
|
||||||
{
|
{
|
||||||
var channelId = Snowflake.Parse(property.Name);
|
var channel = await Discord.GetChannelAsync(
|
||||||
var channelName = property.Value.GetString();
|
dumpChannel.Id,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
// Null items refer to deleted channels
|
channels.Add(channel);
|
||||||
if (channelName is null)
|
}
|
||||||
continue;
|
catch (DiscordChatExporterException)
|
||||||
|
{
|
||||||
await console.Output.WriteLineAsync(
|
inaccessibleChannels.Add(dumpChannel);
|
||||||
$"Fetching channel '{channelName}' ({channelId})..."
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
try
|
await console.Output.WriteLineAsync($"Fetched {channels} channel(s).");
|
||||||
{
|
|
||||||
var channel = await Discord.GetChannelAsync(channelId, cancellationToken);
|
// Print inaccessible channels
|
||||||
channels.Add(channel);
|
if (inaccessibleChannels.Any())
|
||||||
}
|
{
|
||||||
catch (DiscordChatExporterException)
|
await console.Output.WriteLineAsync();
|
||||||
|
|
||||||
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||||
{
|
{
|
||||||
await console.Error.WriteLineAsync(
|
await console.Error.WriteLineAsync(
|
||||||
$"Channel '{channelName}' ({channelId}) is inaccessible."
|
"Failed to access the following channel(s):"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var dumpChannel in inaccessibleChannels)
|
||||||
|
await console.Error.WriteLineAsync($"{dumpChannel.Name} ({dumpChannel.Id})");
|
||||||
|
|
||||||
|
await console.Error.WriteLineAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Cli.Commands.Converters;
|
using DiscordChatExporter.Cli.Commands.Converters;
|
||||||
using DiscordChatExporter.Cli.Commands.Shared;
|
using DiscordChatExporter.Cli.Commands.Shared;
|
||||||
|
using DiscordChatExporter.Cli.Utils.Extensions;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
@ -35,30 +36,46 @@ public class ExportGuildCommand : ExportCommandBase
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
var cancellationToken = console.RegisterCancellationHandler();
|
||||||
var channels = new List<Channel>();
|
var channels = new List<Channel>();
|
||||||
|
|
||||||
|
// Regular channels
|
||||||
await console.Output.WriteLineAsync("Fetching channels...");
|
await console.Output.WriteLineAsync("Fetching channels...");
|
||||||
|
|
||||||
// Regular channels
|
var fetchedChannelsCount = 0;
|
||||||
await foreach (var channel in Discord.GetGuildChannelsAsync(GuildId, cancellationToken))
|
await console
|
||||||
{
|
.CreateStatusTicker()
|
||||||
if (channel.IsCategory)
|
.StartAsync(
|
||||||
continue;
|
"...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
await foreach (
|
||||||
|
var channel in Discord.GetGuildChannelsAsync(GuildId, cancellationToken)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (channel.IsCategory)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!IncludeVoiceChannels && channel.IsVoice)
|
if (!IncludeVoiceChannels && channel.IsVoice)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
channels.Add(channel);
|
channels.Add(channel);
|
||||||
}
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync($" Found {channels.Count} channels.");
|
ctx.Status($"Fetched '{channel.GetHierarchicalName()}'.");
|
||||||
|
fetchedChannelsCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await console.Output.WriteLineAsync($"Fetched {fetchedChannelsCount} channel(s).");
|
||||||
|
|
||||||
// Threads
|
// Threads
|
||||||
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
||||||
{
|
{
|
||||||
AnsiConsole.MarkupLine("Fetching threads...");
|
await console.Output.WriteLineAsync("Fetching threads...");
|
||||||
await AnsiConsole
|
|
||||||
.Status()
|
var fetchedThreadsCount = 0;
|
||||||
|
await console
|
||||||
|
.CreateStatusTicker()
|
||||||
.StartAsync(
|
.StartAsync(
|
||||||
"Found 0 threads.",
|
"...",
|
||||||
async ctx =>
|
async ctx =>
|
||||||
{
|
{
|
||||||
await foreach (
|
await foreach (
|
||||||
|
|
@ -72,15 +89,14 @@ public class ExportGuildCommand : ExportCommandBase
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
channels.Add(thread);
|
channels.Add(thread);
|
||||||
ctx.Status(
|
|
||||||
$"Found {channels.Count(channel => channel.IsThread)} threads: {thread.GetHierarchicalName()}"
|
ctx.Status($"Fetched '{thread.GetHierarchicalName()}'.");
|
||||||
);
|
fetchedThreadsCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await console.Output.WriteLineAsync(
|
|
||||||
$" Found {channels.Count(channel => channel.IsThread)} threads."
|
await console.Output.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s).");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await ExportAsync(console, channels);
|
await ExportAsync(console, channels);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ internal static class ConsoleExtensions
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static Status CreateStatusTicker(this IConsole console) =>
|
||||||
|
console.CreateAnsiConsole().Status().AutoRefresh(true);
|
||||||
|
|
||||||
public static Progress CreateProgressTicker(this IConsole console) =>
|
public static Progress CreateProgressTicker(this IConsole console) =>
|
||||||
console
|
console
|
||||||
.CreateAnsiConsole()
|
.CreateAnsiConsole()
|
||||||
|
|
@ -31,16 +34,16 @@ internal static class ConsoleExtensions
|
||||||
);
|
);
|
||||||
|
|
||||||
public static async ValueTask StartTaskAsync(
|
public static async ValueTask StartTaskAsync(
|
||||||
this ProgressContext progressContext,
|
this ProgressContext context,
|
||||||
string description,
|
string description,
|
||||||
Func<ProgressTask, ValueTask> performOperationAsync
|
Func<ProgressTask, ValueTask> performOperationAsync
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Description cannot be empty
|
// Description cannot be empty
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1133
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1133
|
||||||
var actualDescription = !string.IsNullOrWhiteSpace(description) ? description : "?";
|
var actualDescription = !string.IsNullOrWhiteSpace(description) ? description : "...";
|
||||||
|
|
||||||
var progressTask = progressContext.AddTask(
|
var progressTask = context.AddTask(
|
||||||
// Don't recognize random square brackets as style tags
|
// Don't recognize random square brackets as style tags
|
||||||
Markup.Escape(actualDescription),
|
Markup.Escape(actualDescription),
|
||||||
new ProgressTaskSettings { MaxValue = 1 }
|
new ProgressTaskSettings { MaxValue = 1 }
|
||||||
|
|
|
||||||
60
DiscordChatExporter.Core/Discord/Dump/DataDump.cs
Normal file
60
DiscordChatExporter.Core/Discord/Dump/DataDump.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Discord.Dump;
|
||||||
|
|
||||||
|
public partial class DataDump
|
||||||
|
{
|
||||||
|
public IReadOnlyList<DataDumpChannel> Channels { get; }
|
||||||
|
|
||||||
|
public DataDump(IReadOnlyList<DataDumpChannel> channels) => Channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class DataDump
|
||||||
|
{
|
||||||
|
public static DataDump Parse(JsonElement json)
|
||||||
|
{
|
||||||
|
var channels = new List<DataDumpChannel>();
|
||||||
|
|
||||||
|
foreach (var property in json.EnumerateObjectOrEmpty())
|
||||||
|
{
|
||||||
|
var channelId = Snowflake.Parse(property.Name);
|
||||||
|
var channelName = property.Value.GetString();
|
||||||
|
|
||||||
|
// Null items refer to deleted channels
|
||||||
|
if (channelName is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var channel = new DataDumpChannel(channelId, channelName);
|
||||||
|
channels.Add(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataDump(channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<DataDump> LoadAsync(
|
||||||
|
string zipFilePath,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
using var archive = ZipFile.OpenRead(zipFilePath);
|
||||||
|
|
||||||
|
var entry = archive.GetEntry("messages/index.json");
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Could not find the channel index inside the data package."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var stream = entry.Open();
|
||||||
|
using var document = await JsonDocument.ParseAsync(stream, default, cancellationToken);
|
||||||
|
|
||||||
|
return Parse(document.RootElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
DiscordChatExporter.Core/Discord/Dump/DataDumpChannel.cs
Normal file
3
DiscordChatExporter.Core/Discord/Dump/DataDumpChannel.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
namespace DiscordChatExporter.Core.Discord.Dump;
|
||||||
|
|
||||||
|
public record DataDumpChannel(Snowflake Id, string Name);
|
||||||
Loading…
Reference in a new issue