mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-04-24 15:04:03 +00:00
Use CSharpier
This commit is contained in:
@@ -38,9 +38,9 @@ public abstract class DiscordCommandBase : ICommand
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkYellow))
|
||||
{
|
||||
console.Error.WriteLine(
|
||||
"Warning: Option --bot is deprecated and should not be used. " +
|
||||
"The type of the provided token is now inferred automatically. " +
|
||||
"Please update your workflows as this option may be completely removed in a future version."
|
||||
"Warning: Option --bot is deprecated and should not be used. "
|
||||
+ "The type of the provided token is now inferred automatically. "
|
||||
+ "Please update your workflows as this option may be completely removed in a future version."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,4 @@ public abstract class DiscordCommandBase : ICommand
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,10 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
[CommandOption(
|
||||
"output",
|
||||
'o',
|
||||
Description =
|
||||
"Output file or directory path. " +
|
||||
"Directory path must end with a slash to avoid ambiguity. " +
|
||||
"If a directory is specified, file names will be generated automatically. " +
|
||||
"Supports template tokens, see the documentation for more info."
|
||||
Description = "Output file or directory path. "
|
||||
+ "Directory path must end with a slash to avoid ambiguity. "
|
||||
+ "If a directory is specified, file names will be generated automatically. "
|
||||
+ "Supports template tokens, see the documentation for more info."
|
||||
)]
|
||||
public string OutputPath
|
||||
{
|
||||
@@ -42,11 +41,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
init => _outputPath = Path.GetFullPath(value);
|
||||
}
|
||||
|
||||
[CommandOption(
|
||||
"format",
|
||||
'f',
|
||||
Description = "Export format."
|
||||
)]
|
||||
[CommandOption("format", 'f', Description = "Export format.")]
|
||||
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark;
|
||||
|
||||
[CommandOption(
|
||||
@@ -64,17 +59,15 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
[CommandOption(
|
||||
"partition",
|
||||
'p',
|
||||
Description =
|
||||
"Split the output into partitions, each limited to the specified " +
|
||||
"number of messages (e.g. '100') or file size (e.g. '10mb')."
|
||||
Description = "Split the output into partitions, each limited to the specified "
|
||||
+ "number of messages (e.g. '100') or file size (e.g. '10mb')."
|
||||
)]
|
||||
public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null;
|
||||
|
||||
[CommandOption(
|
||||
"filter",
|
||||
Description =
|
||||
"Only include messages that satisfy this filter. " +
|
||||
"See the documentation for more info."
|
||||
Description = "Only include messages that satisfy this filter. "
|
||||
+ "See the documentation for more info."
|
||||
)]
|
||||
public MessageFilter MessageFilter { get; init; } = MessageFilter.Null;
|
||||
|
||||
@@ -106,9 +99,8 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
|
||||
[CommandOption(
|
||||
"media-dir",
|
||||
Description =
|
||||
"Download assets to this directory. " +
|
||||
"If not specified, the asset directory path will be derived from the output path."
|
||||
Description = "Download assets to this directory. "
|
||||
+ "If not specified, the asset directory path will be derived from the output path."
|
||||
)]
|
||||
public string? AssetsDirPath
|
||||
{
|
||||
@@ -118,10 +110,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
init => _assetsDirPath = value is not null ? Path.GetFullPath(value) : null;
|
||||
}
|
||||
|
||||
[CommandOption(
|
||||
"dateformat",
|
||||
Description = "Format used when writing dates."
|
||||
)]
|
||||
[CommandOption("dateformat", Description = "Format used when writing dates.")]
|
||||
public string DateFormat { get; init; } = "MM/dd/yyyy h:mm tt";
|
||||
|
||||
[CommandOption(
|
||||
@@ -142,17 +131,13 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/425
|
||||
if (ShouldReuseAssets && !ShouldDownloadAssets)
|
||||
{
|
||||
throw new CommandException(
|
||||
"Option --reuse-media cannot be used without --media."
|
||||
);
|
||||
throw new CommandException("Option --reuse-media cannot be used without --media.");
|
||||
}
|
||||
|
||||
// Assets directory can only be specified if the download assets option is set
|
||||
if (!string.IsNullOrWhiteSpace(AssetsDirPath) && !ShouldDownloadAssets)
|
||||
{
|
||||
throw new CommandException(
|
||||
"Option --media-dir cannot be used without --media."
|
||||
);
|
||||
throw new CommandException("Option --media-dir cannot be used without --media.");
|
||||
}
|
||||
|
||||
// Make sure the user does not try to export multiple channels into one file.
|
||||
@@ -161,17 +146,20 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/917
|
||||
var isValidOutputPath =
|
||||
// Anything is valid when exporting a single channel
|
||||
channels.Count <= 1 ||
|
||||
channels.Count <= 1
|
||||
||
|
||||
// When using template tokens, assume the user knows what they're doing
|
||||
OutputPath.Contains('%') ||
|
||||
OutputPath.Contains('%')
|
||||
||
|
||||
// Otherwise, require an existing directory or an unambiguous directory path
|
||||
Directory.Exists(OutputPath) || PathEx.IsDirectoryPath(OutputPath);
|
||||
Directory.Exists(OutputPath)
|
||||
|| PathEx.IsDirectoryPath(OutputPath);
|
||||
|
||||
if (!isValidOutputPath)
|
||||
{
|
||||
throw new CommandException(
|
||||
"Attempted to export multiple channels, but the output path is neither a directory nor a template. " +
|
||||
"If the provided output path is meant to be treated as a directory, make sure it ends with a slash."
|
||||
"Attempted to export multiple channels, but the output path is neither a directory nor a template. "
|
||||
+ "If the provided output path is meant to be treated as a directory, make sure it ends with a slash."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,56 +168,61 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
var errorsByChannel = new ConcurrentDictionary<Channel, string>();
|
||||
|
||||
await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)...");
|
||||
await console.CreateProgressTicker().StartAsync(async progressContext =>
|
||||
{
|
||||
await Parallel.ForEachAsync(
|
||||
channels,
|
||||
new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
|
||||
CancellationToken = cancellationToken
|
||||
},
|
||||
async (channel, innerCancellationToken) =>
|
||||
{
|
||||
try
|
||||
await console
|
||||
.CreateProgressTicker()
|
||||
.StartAsync(async progressContext =>
|
||||
{
|
||||
await Parallel.ForEachAsync(
|
||||
channels,
|
||||
new ParallelOptions
|
||||
{
|
||||
await progressContext.StartTaskAsync(
|
||||
$"{channel.Category} / {channel.Name}",
|
||||
async progress =>
|
||||
{
|
||||
var guild = await Discord.GetGuildAsync(channel.GuildId, innerCancellationToken);
|
||||
|
||||
var request = new ExportRequest(
|
||||
guild,
|
||||
channel,
|
||||
OutputPath,
|
||||
AssetsDirPath,
|
||||
ExportFormat,
|
||||
After,
|
||||
Before,
|
||||
PartitionLimit,
|
||||
MessageFilter,
|
||||
ShouldFormatMarkdown,
|
||||
ShouldDownloadAssets,
|
||||
ShouldReuseAssets,
|
||||
DateFormat
|
||||
);
|
||||
|
||||
await Exporter.ExportChannelAsync(
|
||||
request,
|
||||
progress.ToPercentageBased(),
|
||||
innerCancellationToken
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
|
||||
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
|
||||
CancellationToken = cancellationToken
|
||||
},
|
||||
async (channel, innerCancellationToken) =>
|
||||
{
|
||||
errorsByChannel[channel] = ex.Message;
|
||||
try
|
||||
{
|
||||
await progressContext.StartTaskAsync(
|
||||
$"{channel.Category} / {channel.Name}",
|
||||
async progress =>
|
||||
{
|
||||
var guild = await Discord.GetGuildAsync(
|
||||
channel.GuildId,
|
||||
innerCancellationToken
|
||||
);
|
||||
|
||||
var request = new ExportRequest(
|
||||
guild,
|
||||
channel,
|
||||
OutputPath,
|
||||
AssetsDirPath,
|
||||
ExportFormat,
|
||||
After,
|
||||
Before,
|
||||
PartitionLimit,
|
||||
MessageFilter,
|
||||
ShouldFormatMarkdown,
|
||||
ShouldDownloadAssets,
|
||||
ShouldReuseAssets,
|
||||
DateFormat
|
||||
);
|
||||
|
||||
await Exporter.ExportChannelAsync(
|
||||
request,
|
||||
progress.ToPercentageBased(),
|
||||
innerCancellationToken
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
|
||||
{
|
||||
errorsByChannel[channel] = ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
// Print the result
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
@@ -285,8 +278,8 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
if (channel.Kind == ChannelKind.GuildCategory)
|
||||
{
|
||||
var guildChannels =
|
||||
channelsByGuild.GetValueOrDefault(channel.GuildId) ??
|
||||
await Discord.GetGuildChannelsAsync(channel.GuildId, cancellationToken);
|
||||
channelsByGuild.GetValueOrDefault(channel.GuildId)
|
||||
?? await Discord.GetGuildChannelsAsync(channel.GuildId, cancellationToken);
|
||||
|
||||
foreach (var guildChannel in guildChannels)
|
||||
{
|
||||
@@ -311,18 +304,36 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
// Support Ukraine callout
|
||||
if (!IsUkraineSupportMessageDisabled)
|
||||
{
|
||||
console.Output.WriteLine("┌────────────────────────────────────────────────────────────────────┐");
|
||||
console.Output.WriteLine("│ Thank you for supporting Ukraine <3 │");
|
||||
console.Output.WriteLine("│ │");
|
||||
console.Output.WriteLine("│ As Russia wages a genocidal war against my country, │");
|
||||
console.Output.WriteLine("│ I'm grateful to everyone who continues to │");
|
||||
console.Output.WriteLine("│ stand with Ukraine in our fight for freedom. │");
|
||||
console.Output.WriteLine("│ │");
|
||||
console.Output.WriteLine("│ Learn more: https://tyrrrz.me/ukraine │");
|
||||
console.Output.WriteLine("└────────────────────────────────────────────────────────────────────┘");
|
||||
console.Output.WriteLine(
|
||||
"┌────────────────────────────────────────────────────────────────────┐"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ Thank you for supporting Ukraine <3 │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ As Russia wages a genocidal war against my country, │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ I'm grateful to everyone who continues to │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ stand with Ukraine in our fight for freedom. │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ Learn more: https://tyrrrz.me/ukraine │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"└────────────────────────────────────────────────────────────────────┘"
|
||||
);
|
||||
console.Output.WriteLine("");
|
||||
}
|
||||
|
||||
await base.ExecuteAsync(console);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,4 @@ internal class TruthyBooleanBindingConverter : BindingConverter<bool>
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,41 +16,25 @@ namespace DiscordChatExporter.Cli.Commands;
|
||||
[Command("exportall", Description = "Exports all accessible channels.")]
|
||||
public class ExportAllCommand : ExportCommandBase
|
||||
{
|
||||
[CommandOption(
|
||||
"include-dm",
|
||||
Description = "Include direct message channels."
|
||||
)]
|
||||
[CommandOption("include-dm", Description = "Include direct message channels.")]
|
||||
public bool IncludeDirectChannels { get; init; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-guilds",
|
||||
Description = "Include guild channels."
|
||||
)]
|
||||
[CommandOption("include-guilds", Description = "Include guild channels.")]
|
||||
public bool IncludeGuildChannels { get; init; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-vc",
|
||||
Description = "Include voice channels."
|
||||
)]
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-threads",
|
||||
Description = "Include threads."
|
||||
)]
|
||||
[CommandOption("include-threads", Description = "Include threads.")]
|
||||
public bool IncludeThreads { get; init; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"include-archived-threads",
|
||||
Description = "Include archived threads."
|
||||
)]
|
||||
[CommandOption("include-archived-threads", Description = "Include archived threads.")]
|
||||
public bool IncludeArchivedThreads { get; init; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"data-package",
|
||||
Description =
|
||||
"Path to the personal data package (ZIP file) requested from Discord. " +
|
||||
"If provided, only channels referenced in the dump will be exported."
|
||||
Description = "Path to the personal data package (ZIP file) requested from Discord. "
|
||||
+ "If provided, only channels referenced in the dump will be exported."
|
||||
)]
|
||||
public string? DataPackageFilePath { get; init; }
|
||||
|
||||
@@ -77,7 +61,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
||||
{
|
||||
// Regular channels
|
||||
await foreach (var channel in Discord.GetGuildChannelsAsync(guild.Id, cancellationToken))
|
||||
await foreach (
|
||||
var channel in Discord.GetGuildChannelsAsync(guild.Id, cancellationToken)
|
||||
)
|
||||
{
|
||||
if (channel.Kind == ChannelKind.GuildCategory)
|
||||
continue;
|
||||
@@ -91,7 +77,13 @@ public class ExportAllCommand : ExportCommandBase
|
||||
// Threads
|
||||
if (IncludeThreads)
|
||||
{
|
||||
await foreach (var thread in Discord.GetGuildThreadsAsync(guild.Id, IncludeArchivedThreads, cancellationToken))
|
||||
await foreach (
|
||||
var thread in Discord.GetGuildThreadsAsync(
|
||||
guild.Id,
|
||||
IncludeArchivedThreads,
|
||||
cancellationToken
|
||||
)
|
||||
)
|
||||
{
|
||||
channels.Add(thread);
|
||||
}
|
||||
@@ -120,7 +112,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
if (channelName is null)
|
||||
continue;
|
||||
|
||||
await console.Output.WriteLineAsync($"Fetching channel '{channelName}' ({channelId})...");
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Fetching channel '{channelName}' ({channelId})..."
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -129,7 +123,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
}
|
||||
catch (DiscordChatExporterException)
|
||||
{
|
||||
await console.Error.WriteLineAsync($"Channel '{channelName}' ({channelId}) is inaccessible.");
|
||||
await console.Error.WriteLineAsync(
|
||||
$"Channel '{channelName}' ({channelId}) is inaccessible."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,4 +144,4 @@ public class ExportAllCommand : ExportCommandBase
|
||||
|
||||
await ExportAsync(console, channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,8 @@ public class ExportChannelsCommand : ExportCommandBase
|
||||
[CommandOption(
|
||||
"channel",
|
||||
'c',
|
||||
Description =
|
||||
"Channel ID(s). " +
|
||||
"If provided with category ID(s), all channels inside those categories will be exported."
|
||||
Description = "Channel ID(s). "
|
||||
+ "If provided with category ID(s), all channels inside those categories will be exported."
|
||||
)]
|
||||
public required IReadOnlyList<Snowflake> ChannelIds { get; init; }
|
||||
|
||||
@@ -25,4 +24,4 @@ public class ExportChannelsCommand : ExportCommandBase
|
||||
await base.ExecuteAsync(console);
|
||||
await ExportAsync(console, ChannelIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,11 @@ public class ExportDirectMessagesCommand : ExportCommandBase
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
|
||||
await console.Output.WriteLineAsync("Fetching channels...");
|
||||
var channels = await Discord.GetGuildChannelsAsync(Guild.DirectMessages.Id, cancellationToken);
|
||||
var channels = await Discord.GetGuildChannelsAsync(
|
||||
Guild.DirectMessages.Id,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
await ExportAsync(console, channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,29 +12,16 @@ namespace DiscordChatExporter.Cli.Commands;
|
||||
[Command("exportguild", Description = "Exports all channels within the specified guild.")]
|
||||
public class ExportGuildCommand : ExportCommandBase
|
||||
{
|
||||
[CommandOption(
|
||||
"guild",
|
||||
'g',
|
||||
Description = "Guild ID."
|
||||
)]
|
||||
[CommandOption("guild", 'g', Description = "Guild ID.")]
|
||||
public required Snowflake GuildId { get; init; }
|
||||
|
||||
[CommandOption(
|
||||
"include-vc",
|
||||
Description = "Include voice channels."
|
||||
)]
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-threads",
|
||||
Description = "Include threads."
|
||||
)]
|
||||
[CommandOption("include-threads", Description = "Include threads.")]
|
||||
public bool IncludeThreads { get; init; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"include-archived-threads",
|
||||
Description = "Include archived threads."
|
||||
)]
|
||||
[CommandOption("include-archived-threads", Description = "Include archived threads.")]
|
||||
public bool IncludeArchivedThreads { get; init; } = false;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
@@ -69,7 +56,13 @@ public class ExportGuildCommand : ExportCommandBase
|
||||
// Threads
|
||||
if (IncludeThreads)
|
||||
{
|
||||
await foreach (var thread in Discord.GetGuildThreadsAsync(GuildId, IncludeArchivedThreads, cancellationToken))
|
||||
await foreach (
|
||||
var thread in Discord.GetGuildThreadsAsync(
|
||||
GuildId,
|
||||
IncludeArchivedThreads,
|
||||
cancellationToken
|
||||
)
|
||||
)
|
||||
{
|
||||
channels.Add(thread);
|
||||
}
|
||||
@@ -77,4 +70,4 @@ public class ExportGuildCommand : ExportCommandBase
|
||||
|
||||
await ExportAsync(console, channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,29 +14,16 @@ namespace DiscordChatExporter.Cli.Commands;
|
||||
[Command("channels", Description = "Get the list of channels in a guild.")]
|
||||
public class GetChannelsCommand : DiscordCommandBase
|
||||
{
|
||||
[CommandOption(
|
||||
"guild",
|
||||
'g',
|
||||
Description = "Guild ID."
|
||||
)]
|
||||
[CommandOption("guild", 'g', Description = "Guild ID.")]
|
||||
public required Snowflake GuildId { get; init; }
|
||||
|
||||
[CommandOption(
|
||||
"include-vc",
|
||||
Description = "Include voice channels."
|
||||
)]
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-threads",
|
||||
Description = "Include threads."
|
||||
)]
|
||||
[CommandOption("include-threads", Description = "Include threads.")]
|
||||
public bool IncludeThreads { get; init; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"include-archived-threads",
|
||||
Description = "Include archived threads."
|
||||
)]
|
||||
[CommandOption("include-archived-threads", Description = "Include archived threads.")]
|
||||
public bool IncludeArchivedThreads { get; init; } = false;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
@@ -66,7 +53,13 @@ public class GetChannelsCommand : DiscordCommandBase
|
||||
.FirstOrDefault();
|
||||
|
||||
var threads = IncludeThreads
|
||||
? (await Discord.GetGuildThreadsAsync(GuildId, IncludeArchivedThreads, cancellationToken))
|
||||
? (
|
||||
await Discord.GetGuildThreadsAsync(
|
||||
GuildId,
|
||||
IncludeArchivedThreads,
|
||||
cancellationToken
|
||||
)
|
||||
)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToArray()
|
||||
: Array.Empty<Channel>();
|
||||
@@ -116,8 +109,10 @@ public class GetChannelsCommand : DiscordCommandBase
|
||||
|
||||
// Thread status
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
await console.Output.WriteLineAsync(channelThread.IsArchived ? "Archived" : "Active");
|
||||
await console.Output.WriteLineAsync(
|
||||
channelThread.IsArchived ? "Archived" : "Active"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ public class GetDirectChannelsCommand : DiscordCommandBase
|
||||
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
|
||||
var channels = (await Discord.GetGuildChannelsAsync(Guild.DirectMessages.Id, cancellationToken))
|
||||
var channels = (
|
||||
await Discord.GetGuildChannelsAsync(Guild.DirectMessages.Id, cancellationToken)
|
||||
)
|
||||
.Where(c => c.Kind != ChannelKind.GuildCategory)
|
||||
.OrderByDescending(c => c.LastMessageId)
|
||||
.ThenBy(c => c.Name)
|
||||
@@ -45,4 +47,4 @@ public class GetDirectChannelsCommand : DiscordCommandBase
|
||||
await console.Output.WriteLineAsync($"{channel.Category} / {channel.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ public class GetGuildsCommand : DiscordCommandBase
|
||||
foreach (var guild in guilds)
|
||||
{
|
||||
// Guild ID
|
||||
await console.Output.WriteAsync(
|
||||
guild.Id.ToString().PadRight(guildIdMaxLength, ' ')
|
||||
);
|
||||
await console.Output.WriteAsync(guild.Id.ToString().PadRight(guildIdMaxLength, ' '));
|
||||
|
||||
// Separator
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||
@@ -45,4 +43,4 @@ public class GetGuildsCommand : DiscordCommandBase
|
||||
await console.Output.WriteLineAsync(guild.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,18 @@ public class GuideCommand : ICommand
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
console.Output.WriteLine("To get user token:");
|
||||
|
||||
console.Output.WriteLine(" * Automating user accounts is technically against TOS — USE AT YOUR OWN RISK!");
|
||||
console.Output.WriteLine(
|
||||
" * Automating user accounts is technically against TOS — USE AT YOUR OWN RISK!"
|
||||
);
|
||||
console.Output.WriteLine(" 1. Open Discord in your web browser and login");
|
||||
console.Output.WriteLine(" 2. Open any server or direct message channel");
|
||||
console.Output.WriteLine(" 3. Press Ctrl+Shift+I to show developer tools");
|
||||
console.Output.WriteLine(" 4. Navigate to the Network tab");
|
||||
console.Output.WriteLine(" 5. Press Ctrl+R to reload");
|
||||
console.Output.WriteLine(" 6. Switch between random channels to trigger network requests");
|
||||
console.Output.WriteLine(" 7. Search for a request containing \"messages?limit=50\" or similar");
|
||||
console.Output.WriteLine(
|
||||
" 7. Search for a request containing \"messages?limit=50\" or similar"
|
||||
);
|
||||
console.Output.WriteLine(" 8. Select the Headers tab on the right");
|
||||
console.Output.WriteLine(" 9. Scroll down to the Request Headers section");
|
||||
console.Output.WriteLine(" 10. Copy the value of the \"authorization\" header");
|
||||
@@ -36,7 +40,9 @@ public class GuideCommand : ICommand
|
||||
console.Output.WriteLine(" 2. Open your application's settings");
|
||||
console.Output.WriteLine(" 3. Navigate to the Bot section on the left");
|
||||
console.Output.WriteLine(" 4. Under Token click Copy");
|
||||
console.Output.WriteLine(" * Your bot needs to have Message Content Intent enabled to read messages");
|
||||
console.Output.WriteLine(
|
||||
" * Your bot needs to have Message Content Intent enabled to read messages"
|
||||
);
|
||||
console.Output.WriteLine();
|
||||
|
||||
// Guild or channel ID
|
||||
@@ -47,15 +53,21 @@ public class GuideCommand : ICommand
|
||||
console.Output.WriteLine(" 2. Open Settings");
|
||||
console.Output.WriteLine(" 3. Go to Advanced section");
|
||||
console.Output.WriteLine(" 4. Enable Developer Mode");
|
||||
console.Output.WriteLine(" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID");
|
||||
console.Output.WriteLine(
|
||||
" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID"
|
||||
);
|
||||
console.Output.WriteLine();
|
||||
|
||||
// Docs link
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
console.Output.WriteLine("If you have questions or issues, please refer to the documentation:");
|
||||
console.Output.WriteLine(
|
||||
"If you have questions or issues, please refer to the documentation:"
|
||||
);
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
||||
console.Output.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs");
|
||||
console.Output.WriteLine(
|
||||
"https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs"
|
||||
);
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" Version="2.3.4" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using CliFx;
|
||||
|
||||
return await new CliApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
return await new CliApplicationBuilder().AddCommandsFromThisAssembly().Build().RunAsync(args);
|
||||
|
||||
@@ -8,34 +8,38 @@ namespace DiscordChatExporter.Cli.Utils.Extensions;
|
||||
internal static class ConsoleExtensions
|
||||
{
|
||||
public static IAnsiConsole CreateAnsiConsole(this IConsole console) =>
|
||||
AnsiConsole.Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = AnsiSupport.Detect,
|
||||
ColorSystem = ColorSystemSupport.Detect,
|
||||
Out = new AnsiConsoleOutput(console.Output)
|
||||
});
|
||||
|
||||
public static Progress CreateProgressTicker(this IConsole console) => console
|
||||
.CreateAnsiConsole()
|
||||
.Progress()
|
||||
.AutoClear(false)
|
||||
.AutoRefresh(true)
|
||||
.HideCompleted(false)
|
||||
.Columns(
|
||||
new TaskDescriptionColumn {Alignment = Justify.Left},
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn()
|
||||
AnsiConsole.Create(
|
||||
new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = AnsiSupport.Detect,
|
||||
ColorSystem = ColorSystemSupport.Detect,
|
||||
Out = new AnsiConsoleOutput(console.Output)
|
||||
}
|
||||
);
|
||||
|
||||
public static Progress CreateProgressTicker(this IConsole console) =>
|
||||
console
|
||||
.CreateAnsiConsole()
|
||||
.Progress()
|
||||
.AutoClear(false)
|
||||
.AutoRefresh(true)
|
||||
.HideCompleted(false)
|
||||
.Columns(
|
||||
new TaskDescriptionColumn { Alignment = Justify.Left },
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn()
|
||||
);
|
||||
|
||||
public static async ValueTask StartTaskAsync(
|
||||
this ProgressContext progressContext,
|
||||
string description,
|
||||
Func<ProgressTask, ValueTask> performOperationAsync)
|
||||
Func<ProgressTask, ValueTask> performOperationAsync
|
||||
)
|
||||
{
|
||||
var progressTask = progressContext.AddTask(
|
||||
// Don't recognize random square brackets as style tags
|
||||
Markup.Escape(description),
|
||||
new ProgressTaskSettings {MaxValue = 1}
|
||||
new ProgressTaskSettings { MaxValue = 1 }
|
||||
);
|
||||
|
||||
try
|
||||
@@ -48,4 +52,4 @@ internal static class ConsoleExtensions
|
||||
progressTask.StopTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user