mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-04-04 03:23:05 -06:00
Rework CLI commands to be composable: pipe channels to export, remove exportguild/exportall/exportdm
Agent-Logs-Url: https://github.com/Tyrrrz/DiscordChatExporter/sessions/5f305835-64af-456e-b0b4-6163ece1e8cf Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
This commit is contained in:
parent
716ea79f60
commit
efb371f093
|
|
@ -33,16 +33,13 @@ Type the following command in your terminal of choice, then press ENTER to run i
|
||||||
|
|
||||||
## CLI commands
|
## CLI commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| ----------- | ---------------------------------------------------- |
|
| -------- | ---------------------------------------------------- |
|
||||||
| export | Exports a channel |
|
| export | Exports one or more channels |
|
||||||
| exportdm | Exports all direct message channels |
|
| channels | Outputs the list of channels in the given server |
|
||||||
| exportguild | Exports all channels within the specified server |
|
| dm | Outputs the list of direct message channels |
|
||||||
| exportall | Exports all accessible channels |
|
| guilds | Outputs the list of accessible servers |
|
||||||
| channels | Outputs the list of channels in the given server |
|
| guide | Explains how to obtain token, server, and channel ID |
|
||||||
| dm | Outputs the list of direct message channels |
|
|
||||||
| guilds | Outputs the list of accessible servers |
|
|
||||||
| guide | Explains how to obtain token, server, and channel ID |
|
|
||||||
|
|
||||||
To use the commands, you'll need a token. For the instructions on how to get a token, please refer to [this page](Token-and-IDs.md), or run `./DiscordChatExporter.Cli guide`.
|
To use the commands, you'll need a token. For the instructions on how to get a token, please refer to [this page](Token-and-IDs.md), or run `./DiscordChatExporter.Cli guide`.
|
||||||
|
|
||||||
|
|
@ -225,46 +222,38 @@ Documentation on message filter syntax can be found [here](https://github.com/Ty
|
||||||
|
|
||||||
### Export channels from a specific server
|
### Export channels from a specific server
|
||||||
|
|
||||||
To export all channels in a specific server, use the `exportguild` command and provide the server ID through the `-g|--guild` option:
|
To export all channels in a specific server, use the `channels` command to list channels and pipe the result to `export`:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./DiscordChatExporter.Cli exportguild -t "mfa.Ifrn" -g 21814
|
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Tip**: To avoid repeating `--token` (or `-t`) twice, set the `DISCORD_TOKEN` environment variable.
|
||||||
|
|
||||||
#### Including threads
|
#### Including threads
|
||||||
|
|
||||||
By default, threads are not included in the export. You can change this behavior by using `--include-threads` and
|
By default, threads are not included. You can change this behavior by passing `--include-threads` to the `channels` command. It has possible values of `none`, `active`, or `all`, indicating which threads should be included. To include both active and archived threads, use `--include-threads all`.
|
||||||
specifying which threads should be included. It has possible values of `none`, `active`, or `all`, indicating which
|
|
||||||
threads should be included. To include both active and archived threads, use `--include-threads all`.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./DiscordChatExporter.Cli exportguild -t "mfa.Ifrn" -g 21814 --include-threads all
|
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 --include-threads all | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Including voice channels
|
#### Including voice channels
|
||||||
|
|
||||||
By default, voice channels are included in the export. You can change this behavior by using `--include-vc` and
|
By default, voice channels are included. You can change this behavior by passing `--include-vc false` to the `channels` command.
|
||||||
specifying whether to include voice channels in the export. It has possible values of `true` or `false`, to exclude
|
|
||||||
voice channels, use `--include-vc false`.
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./DiscordChatExporter.Cli exportguild -t "mfa.Ifrn" -g 21814 --include-vc false
|
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 --include-vc false | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Export all channels
|
### Export all channels
|
||||||
|
|
||||||
To export all accessible channels, use the `exportall` command:
|
To export all accessible channels, first list all guilds and then pipe each guild's channels to `export`. You can also use `dm` to include direct message channels.
|
||||||
|
|
||||||
|
To export all DMs:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./DiscordChatExporter.Cli exportall -t "mfa.Ifrn"
|
./DiscordChatExporter.Cli dm -t "mfa.Ifrn" | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
```
|
|
||||||
|
|
||||||
#### Excluding DMs
|
|
||||||
|
|
||||||
To exclude DMs, add the `--include-dm false` option.
|
|
||||||
|
|
||||||
```console
|
|
||||||
./DiscordChatExporter.Cli exportall -t "mfa.Ifrn" --include-dm false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### List channels in a server
|
### List channels in a server
|
||||||
|
|
@ -275,6 +264,12 @@ To list the channels available in a specific server, use the `channels` command
|
||||||
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814
|
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When the output is redirected or piped, the `channels` command prints only channel IDs (one per line). This allows you to pipe the output directly to the `export` command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
|
```
|
||||||
|
|
||||||
### List direct message channels
|
### List direct message channels
|
||||||
|
|
||||||
To list all DM channels accessible to the current account, use the `dm` command:
|
To list all DM channels accessible to the current account, use the `dm` command:
|
||||||
|
|
@ -283,6 +278,12 @@ To list all DM channels accessible to the current account, use the `dm` command:
|
||||||
./DiscordChatExporter.Cli dm -t "mfa.Ifrn"
|
./DiscordChatExporter.Cli dm -t "mfa.Ifrn"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When the output is redirected or piped, the `dm` command prints only channel IDs (one per line). This allows you to pipe the output directly to the `export` command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
./DiscordChatExporter.Cli dm -t "mfa.Ifrn" | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
|
||||||
|
```
|
||||||
|
|
||||||
### List servers
|
### List servers
|
||||||
|
|
||||||
To list all servers accessible by the current account, use the `guilds` command:
|
To list all servers accessible by the current account, use the `guilds` command:
|
||||||
|
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Binding;
|
|
||||||
using CliFx.Infrastructure;
|
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
|
||||||
using DiscordChatExporter.Cli.Utils.Extensions;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using DiscordChatExporter.Core.Discord.Dump;
|
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands;
|
|
||||||
|
|
||||||
[Command("exportall", Description = "Exports all accessible channels.")]
|
|
||||||
public partial class ExportAllCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("include-dm", Description = "Include direct message channels.")]
|
|
||||||
public bool IncludeDirectChannels { get; set; } = true;
|
|
||||||
|
|
||||||
[CommandOption("include-guilds", Description = "Include server channels.")]
|
|
||||||
public bool IncludeGuildChannels { get; set; } = true;
|
|
||||||
|
|
||||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
|
||||||
public bool IncludeVoiceChannels { get; set; } = true;
|
|
||||||
|
|
||||||
[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."
|
|
||||||
)]
|
|
||||||
public string? DataPackageFilePath { get; set; }
|
|
||||||
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
await base.ExecuteAsync(console);
|
|
||||||
|
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
|
||||||
var channels = new List<Channel>();
|
|
||||||
|
|
||||||
// Pull from the API
|
|
||||||
if (string.IsNullOrWhiteSpace(DataPackageFilePath))
|
|
||||||
{
|
|
||||||
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
// Regular channels
|
|
||||||
await console.Output.WriteLineAsync(
|
|
||||||
$"Fetching channels for server '{guild.Name}'..."
|
|
||||||
);
|
|
||||||
|
|
||||||
var fetchedChannelsCount = 0;
|
|
||||||
await console
|
|
||||||
.CreateStatusTicker()
|
|
||||||
.StartAsync(
|
|
||||||
"...",
|
|
||||||
async ctx =>
|
|
||||||
{
|
|
||||||
await foreach (
|
|
||||||
var channel in Discord.GetGuildChannelsAsync(
|
|
||||||
guild.Id,
|
|
||||||
cancellationToken
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (channel.IsCategory)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!IncludeVoiceChannels && channel.IsVoice)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
channels.Add(channel);
|
|
||||||
|
|
||||||
ctx.Status(
|
|
||||||
Markup.Escape($"Fetched '{channel.GetHierarchicalName()}'.")
|
|
||||||
);
|
|
||||||
|
|
||||||
fetchedChannelsCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync($"Fetched {fetchedChannelsCount} channel(s).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pull from the data package
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await console.Output.WriteLineAsync("Extracting channels...");
|
|
||||||
|
|
||||||
var dump = await DataDump.LoadAsync(DataPackageFilePath, cancellationToken);
|
|
||||||
var inaccessibleChannels = new List<DataDumpChannel>();
|
|
||||||
|
|
||||||
await console
|
|
||||||
.CreateStatusTicker()
|
|
||||||
.StartAsync(
|
|
||||||
"...",
|
|
||||||
async ctx =>
|
|
||||||
{
|
|
||||||
foreach (var dumpChannel in dump.Channels)
|
|
||||||
{
|
|
||||||
ctx.Status(
|
|
||||||
Markup.Escape(
|
|
||||||
$"Fetching '{dumpChannel.Name}' ({dumpChannel.Id})..."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var channel = await Discord.GetChannelAsync(
|
|
||||||
dumpChannel.Id,
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
|
|
||||||
channels.Add(channel);
|
|
||||||
}
|
|
||||||
catch (DiscordChatExporterException)
|
|
||||||
{
|
|
||||||
inaccessibleChannels.Add(dumpChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync($"Fetched {channels} channel(s).");
|
|
||||||
|
|
||||||
// Print inaccessible channels
|
|
||||||
if (inaccessibleChannels.Any())
|
|
||||||
{
|
|
||||||
await console.Output.WriteLineAsync();
|
|
||||||
|
|
||||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
|
||||||
{
|
|
||||||
await console.Error.WriteLineAsync(
|
|
||||||
"Failed to access the following channel(s):"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var dumpChannel in inaccessibleChannels)
|
|
||||||
await console.Error.WriteLineAsync($"{dumpChannel.Name} ({dumpChannel.Id})");
|
|
||||||
|
|
||||||
await console.Error.WriteLineAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out unwanted channels
|
|
||||||
if (!IncludeDirectChannels)
|
|
||||||
channels.RemoveAll(c => c.IsDirect);
|
|
||||||
if (!IncludeGuildChannels)
|
|
||||||
channels.RemoveAll(c => c.IsGuild);
|
|
||||||
if (!IncludeVoiceChannels)
|
|
||||||
channels.RemoveAll(c => c.IsVoice);
|
|
||||||
|
|
||||||
await ExportAsync(console, channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CliFx;
|
||||||
using CliFx.Binding;
|
using CliFx.Binding;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
|
|
@ -17,9 +18,11 @@ public partial class ExportChannelsCommand : ExportCommandBase
|
||||||
"channel",
|
"channel",
|
||||||
'c',
|
'c',
|
||||||
Description = "Channel ID(s). "
|
Description = "Channel ID(s). "
|
||||||
+ "If provided with category ID(s), all channels inside those categories will be exported."
|
+ "If provided with category ID(s), all channels inside those categories will be exported. "
|
||||||
|
+ "If not provided, channel IDs are read from standard input (one per line), "
|
||||||
|
+ "enabling piping from the 'channels' or 'dm' commands."
|
||||||
)]
|
)]
|
||||||
public required IReadOnlyList<Snowflake> ChannelIds { get; set; }
|
public IReadOnlyList<Snowflake> ChannelIds { get; set; } = [];
|
||||||
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
public override async ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
|
|
@ -27,12 +30,33 @@ public partial class ExportChannelsCommand : ExportCommandBase
|
||||||
|
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
var cancellationToken = console.RegisterCancellationHandler();
|
||||||
|
|
||||||
|
// If no channel IDs were specified, read them from stdin
|
||||||
|
var channelIds = new List<Snowflake>(ChannelIds);
|
||||||
|
if (channelIds.Count == 0 && console.IsInputRedirected)
|
||||||
|
{
|
||||||
|
string? line;
|
||||||
|
while ((line = await console.Input.ReadLineAsync()) is not null)
|
||||||
|
{
|
||||||
|
line = line.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(line))
|
||||||
|
channelIds.Add(Snowflake.Parse(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelIds.Count == 0)
|
||||||
|
{
|
||||||
|
throw new CommandException(
|
||||||
|
"No channel IDs provided. "
|
||||||
|
+ "Specify channel IDs via the '--channel' option or pipe them from the 'channels' or 'dm' commands."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await console.Output.WriteLineAsync("Resolving channel(s)...");
|
await console.Output.WriteLineAsync("Resolving channel(s)...");
|
||||||
|
|
||||||
var channels = new List<Channel>();
|
var channels = new List<Channel>();
|
||||||
var channelsByGuild = new Dictionary<Snowflake, IReadOnlyList<Channel>>();
|
var channelsByGuild = new Dictionary<Snowflake, IReadOnlyList<Channel>>();
|
||||||
|
|
||||||
foreach (var channelId in ChannelIds)
|
foreach (var channelId in channelIds)
|
||||||
{
|
{
|
||||||
var channel = await Discord.GetChannelAsync(channelId, cancellationToken);
|
var channel = await Discord.GetChannelAsync(channelId, cancellationToken);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Binding;
|
|
||||||
using CliFx.Infrastructure;
|
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands;
|
|
||||||
|
|
||||||
[Command("exportdm", Description = "Exports all direct message channels.")]
|
|
||||||
public partial class ExportDirectMessagesCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
await base.ExecuteAsync(console);
|
|
||||||
|
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync("Fetching channels...");
|
|
||||||
var channels = await Discord.GetGuildChannelsAsync(
|
|
||||||
Guild.DirectMessages.Id,
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
|
|
||||||
await ExportAsync(console, channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CliFx.Binding;
|
|
||||||
using CliFx.Infrastructure;
|
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
|
||||||
using DiscordChatExporter.Cli.Utils.Extensions;
|
|
||||||
using DiscordChatExporter.Core.Discord;
|
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands;
|
|
||||||
|
|
||||||
[Command("exportguild", Description = "Exports all channels within the specified server.")]
|
|
||||||
public partial class ExportGuildCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("guild", 'g', Description = "Server ID.")]
|
|
||||||
public required Snowflake GuildId { get; set; }
|
|
||||||
|
|
||||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
|
||||||
public bool IncludeVoiceChannels { get; set; } = true;
|
|
||||||
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
|
||||||
{
|
|
||||||
await base.ExecuteAsync(console);
|
|
||||||
|
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
|
||||||
var channels = new List<Channel>();
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync("Fetching channels...");
|
|
||||||
|
|
||||||
var fetchedChannelsCount = 0;
|
|
||||||
await console
|
|
||||||
.CreateStatusTicker()
|
|
||||||
.StartAsync(
|
|
||||||
"...",
|
|
||||||
async ctx =>
|
|
||||||
{
|
|
||||||
await foreach (
|
|
||||||
var channel in Discord.GetGuildChannelsAsync(GuildId, cancellationToken)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (channel.IsCategory)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!IncludeVoiceChannels && channel.IsVoice)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
channels.Add(channel);
|
|
||||||
|
|
||||||
ctx.Status(Markup.Escape($"Fetched '{channel.GetHierarchicalName()}'."));
|
|
||||||
|
|
||||||
fetchedChannelsCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await console.Output.WriteLineAsync($"Fetched {fetchedChannelsCount} channel(s).");
|
|
||||||
|
|
||||||
await ExportAsync(console, channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -60,54 +60,68 @@ public partial class GetChannelsCommand : DiscordCommandBase
|
||||||
.ToArray()
|
.ToArray()
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
foreach (var channel in channels)
|
// If output is redirected, print only channel IDs (one per line) for easy piping
|
||||||
|
if (console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
// Channel ID
|
foreach (var channel in channels)
|
||||||
await console.Output.WriteAsync(
|
|
||||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
|
||||||
await console.Output.WriteAsync(" | ");
|
|
||||||
|
|
||||||
// Channel name
|
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
|
||||||
await console.Output.WriteLineAsync(channel.GetHierarchicalName());
|
|
||||||
|
|
||||||
var channelThreads = threads.Where(t => t.Parent?.Id == channel.Id).ToArray();
|
|
||||||
var channelThreadIdMaxLength = channelThreads
|
|
||||||
.Select(t => t.Id.ToString().Length)
|
|
||||||
.OrderDescending()
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
foreach (var channelThread in channelThreads)
|
|
||||||
{
|
{
|
||||||
// Indent
|
await console.Output.WriteLineAsync(channel.Id.ToString());
|
||||||
await console.Output.WriteAsync(" * ");
|
|
||||||
|
|
||||||
// Thread ID
|
foreach (var channelThread in threads.Where(t => t.Parent?.Id == channel.Id))
|
||||||
|
await console.Output.WriteLineAsync(channelThread.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var channel in channels)
|
||||||
|
{
|
||||||
|
// Channel ID
|
||||||
await console.Output.WriteAsync(
|
await console.Output.WriteAsync(
|
||||||
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
await console.Output.WriteAsync(" | ");
|
await console.Output.WriteAsync(" | ");
|
||||||
|
|
||||||
// Thread name
|
// Channel name
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
await console.Output.WriteAsync($"Thread / {channelThread.Name}");
|
await console.Output.WriteLineAsync(channel.GetHierarchicalName());
|
||||||
|
|
||||||
// Separator
|
var channelThreads = threads.Where(t => t.Parent?.Id == channel.Id).ToArray();
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
var channelThreadIdMaxLength = channelThreads
|
||||||
await console.Output.WriteAsync(" | ");
|
.Select(t => t.Id.ToString().Length)
|
||||||
|
.OrderDescending()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
// Thread status
|
foreach (var channelThread in channelThreads)
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
{
|
||||||
await console.Output.WriteLineAsync(
|
// Indent
|
||||||
channelThread.IsArchived ? "Archived" : "Active"
|
await console.Output.WriteAsync(" * ");
|
||||||
|
|
||||||
|
// Thread ID
|
||||||
|
await console.Output.WriteAsync(
|
||||||
|
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
|
await console.Output.WriteAsync(" | ");
|
||||||
|
|
||||||
|
// Thread name
|
||||||
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
|
await console.Output.WriteAsync($"Thread / {channelThread.Name}");
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
|
await console.Output.WriteAsync(" | ");
|
||||||
|
|
||||||
|
// Thread status
|
||||||
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
|
await console.Output.WriteLineAsync(
|
||||||
|
channelThread.IsArchived ? "Archived" : "Active"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,20 +30,29 @@ public partial class GetDirectChannelsCommand : DiscordCommandBase
|
||||||
.OrderDescending()
|
.OrderDescending()
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
foreach (var channel in channels)
|
// If output is redirected, print only channel IDs (one per line) for easy piping
|
||||||
|
if (console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
// Channel ID
|
foreach (var channel in channels)
|
||||||
await console.Output.WriteAsync(
|
await console.Output.WriteLineAsync(channel.Id.ToString());
|
||||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
}
|
||||||
);
|
else
|
||||||
|
{
|
||||||
|
foreach (var channel in channels)
|
||||||
|
{
|
||||||
|
// Channel ID
|
||||||
|
await console.Output.WriteAsync(
|
||||||
|
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
||||||
|
);
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
await console.Output.WriteAsync(" | ");
|
await console.Output.WriteAsync(" | ");
|
||||||
|
|
||||||
// Channel name
|
// Channel name
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
await console.Output.WriteLineAsync(channel.GetHierarchicalName());
|
await console.Output.WriteLineAsync(channel.GetHierarchicalName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue