Restructure commands: list channels/guilds subcommands with positional parameters

Agent-Logs-Url: https://github.com/Tyrrrz/DiscordChatExporter/sessions/27f23ff6-5b39-46d3-a7dc-387749ee63fa

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-03 15:05:43 +00:00 committed by GitHub
parent 16ecb2802a
commit 0e3b455655
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 140 additions and 120 deletions

View file

@ -34,11 +34,11 @@ 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 one or more channels | | export | Exports one or more channels |
| channels | Outputs the list of channels in the given server | | list channels | Outputs the list of channels in the given server(s) |
| dm | Outputs the list of direct message channels | | list channels dm | Outputs the list of direct message channels |
| guilds | Outputs the list of accessible servers | | list guilds | Outputs the list of accessible servers |
| guide | Explains how to obtain token, server, and channel ID | | 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`.
@ -57,10 +57,10 @@ For example, to figure out how to use the `export` command, run:
## Export a specific channel ## Export a specific channel
You can quickly export with DCE's default settings by using just `-t token` and `-c channelid`. You can quickly export with DCE's default settings by using just `-t token` and the channel ID as a positional argument.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555
``` ```
#### Changing the format #### Changing the format
@ -69,7 +69,7 @@ You can change the export format to `HtmlDark`, `HtmlLight`, `PlainText` `Json`
format is `HtmlDark`. format is `HtmlDark`.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -f Json ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -f Json
``` ```
#### Changing the output filename #### Changing the output filename
@ -77,7 +77,7 @@ format is `HtmlDark`.
You can change the filename by using `-o name.ext`. e.g. for the `HTML` format: You can change the filename by using `-o name.ext`. e.g. for the `HTML` format:
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -o myserver.html ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -o myserver.html
``` ```
#### Changing the output directory #### Changing the output directory
@ -87,7 +87,7 @@ extension.
If any of the folders in the path have a space in its name, escape them with quotes ("). If any of the folders in the path have a space in its name, escape them with quotes (").
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -o "C:\Discord Exports" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -o "C:\Discord Exports"
``` ```
#### Changing the filename and output directory #### Changing the filename and output directory
@ -97,7 +97,7 @@ Note that the filename must have an extension, otherwise it will be considered a
If any of the folders in the path have a space in its name, escape them with quotes ("). If any of the folders in the path have a space in its name, escape them with quotes (").
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -o "C:\Discord Exports\myserver.html" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -o "C:\Discord Exports\myserver.html"
``` ```
#### Generating the filename and output directory dynamically #### Generating the filename and output directory dynamically
@ -105,7 +105,7 @@ If any of the folders in the path have a space in its name, escape them with quo
You can use template tokens to generate the output file path based on the server and channel metadata. You can use template tokens to generate the output file path based on the server and channel metadata.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -o "C:\Discord Exports\%G\%T\%C.html" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -o "C:\Discord Exports\%G\%T\%C.html"
``` ```
Assuming you are exporting a channel named `"my-channel"` in the `"Text channels"` category from a server Assuming you are exporting a channel named `"my-channel"` in the `"Text channels"` category from a server
@ -133,13 +133,13 @@ You can use partitioning to split files after a given number of messages or file
For example, a channel with 36 messages set to be partitioned every 10 messages will output 4 files. For example, a channel with 36 messages set to be partitioned every 10 messages will output 4 files.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -p 10 ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -p 10
``` ```
A 45 MB channel set to be partitioned every 20 MB will output 3 files. A 45 MB channel set to be partitioned every 20 MB will output 3 files.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 -p 20mb ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 -p 20mb
``` ```
#### Downloading assets #### Downloading assets
@ -150,7 +150,7 @@ downloaded when using the plain text (TXT) export format.
A folder containing the assets will be created along with the exported chat. They must be kept together. A folder containing the assets will be created along with the exported chat. They must be kept together.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --media ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --media
``` ```
#### Reusing assets #### Reusing assets
@ -159,7 +159,7 @@ Previously downloaded assets can be reused to skip redundant downloads as long a
same folder. Using this option can speed up future exports. This option requires the `--media` option. same folder. Using this option can speed up future exports. This option requires the `--media` option.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --media --reuse-media ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --media --reuse-media
``` ```
#### Changing the media directory #### Changing the media directory
@ -168,7 +168,7 @@ By default, the media directory is created alongside the exported chat. You can
providing a path that ends with a slash. All of the exported media will be stored in this directory. providing a path that ends with a slash. All of the exported media will be stored in this directory.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --media --media-dir "C:\Discord Media" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --media --media-dir "C:\Discord Media"
``` ```
#### Changing the date format #### Changing the date format
@ -177,7 +177,7 @@ You can customize how dates are formatted in the exported files by using `--loca
locales. The default locale is `en-US`. locales. The default locale is `en-US`.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --locale "de-DE" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --locale "de-DE"
``` ```
#### Date ranges #### Date ranges
@ -186,14 +186,14 @@ locales. The default locale is `en-US`.
Use `--before` to export messages sent before the provided date. E.g. messages sent before September 18th, 2019: Use `--before` to export messages sent before the provided date. E.g. messages sent before September 18th, 2019:
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --before 2019-09-18 ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --before 2019-09-18
``` ```
**Messages sent after a date** **Messages sent after a date**
Use `--after` to export messages sent after the provided date. E.g. messages sent after September 17th, 2019 11:34 PM: Use `--after` to export messages sent after the provided date. E.g. messages sent after September 17th, 2019 11:34 PM:
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --after "2019-09-17 23:34" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --after "2019-09-17 23:34"
``` ```
**Messages sent in a date range** **Messages sent in a date range**
@ -201,7 +201,7 @@ Use `--before` and `--after` to export messages sent during the provided date ra
September 17th, 2019 11:34 PM and September 18th: September 17th, 2019 11:34 PM and September 18th:
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --after "2019-09-17 23:34" --before "2019-09-18" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --after "2019-09-17 23:34" --before "2019-09-18"
``` ```
You can try different formats like `17-SEP-2019 11:34 PM` or even refine your ranges down to You can try different formats like `17-SEP-2019 11:34 PM` or even refine your ranges down to
@ -215,79 +215,83 @@ formats [here](https://docs.microsoft.com/en-us/dotnet/standard/base-types/custo
Use `--filter` to filter what messages are included in the export. Use `--filter` to filter what messages are included in the export.
```console ```console
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --filter "from:Tyrrrz has:image" ./DiscordChatExporter.Cli export -t "mfa.Ifrn" 53555 --filter "from:Tyrrrz has:image"
``` ```
Documentation on message filter syntax can be found [here](https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs/Message-filters.md). Documentation on message filter syntax can be found [here](https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs/Message-filters.md).
### Export channels from a specific server ### Export channels from a specific server
To export all channels in a specific server, use the `channels` command to list channels and pipe the result to `export`: To export all channels in a specific server, use `list channels` to list channels and pipe the result to `export`:
```console ```console
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 21814 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
``` ```
> **Tip**: To avoid repeating `--token` (or `-t`) twice, set the `DISCORD_TOKEN` environment variable: `export DISCORD_TOKEN="mfa.Ifrn"` (Linux/macOS) or `set DISCORD_TOKEN=mfa.Ifrn` (Windows). Then you can omit `-t` from both commands. > **Tip**: To avoid repeating `--token` (or `-t`) twice, set the `DISCORD_TOKEN` environment variable: `export DISCORD_TOKEN="mfa.Ifrn"` (Linux/macOS) or `set DISCORD_TOKEN=mfa.Ifrn` (Windows). Then you can omit `-t` from both commands.
#### Including threads You can also list channels for multiple guilds at once:
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`.
```console ```console
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 --include-threads all | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 21814 35930 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
```
#### Including threads
By default, threads are not included. You can change this behavior by passing `--include-threads` to the `list 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`.
```console
./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 21814 --include-threads all | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
``` ```
#### Including voice channels #### Including voice channels
By default, voice channels are included. You can change this behavior by passing `--include-vc false` to the `channels` command. By default, voice channels are included. You can change this behavior by passing `--include-vc false` to the `list channels` command.
```console ```console
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 --include-vc false | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 21814 --include-vc false | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
``` ```
### Export all channels ### Export all channels
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: To export all DMs:
```console ```console
./DiscordChatExporter.Cli dm -t "mfa.Ifrn" | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels dm -t "mfa.Ifrn" | ./DiscordChatExporter.Cli export -t "mfa.Ifrn"
``` ```
### List channels in a server ### List channels in a server
To list the channels available in a specific server, use the `channels` command and provide the server ID through the `-g|--guild` option: To list the channels available in a specific server, use the `list channels` command and provide the server ID as an argument:
```console ```console
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 ./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 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: When the output is redirected or piped, the `list channels` command prints only channel IDs (one per line). This allows you to pipe the output directly to the `export` command:
```console ```console
./DiscordChatExporter.Cli channels -t "mfa.Ifrn" -g 21814 | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels -t "mfa.Ifrn" 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 `list channels dm` command:
```console ```console
./DiscordChatExporter.Cli dm -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels 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: When the output is redirected or piped, the `list channels dm` command prints only channel IDs (one per line). This allows you to pipe the output directly to the `export` command:
```console ```console
./DiscordChatExporter.Cli dm -t "mfa.Ifrn" | ./DiscordChatExporter.Cli export -t "mfa.Ifrn" ./DiscordChatExporter.Cli list channels 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 `list guilds` command:
```console ```console
./DiscordChatExporter.Cli guilds -t "mfa.Ifrn" > C:\path\to\output.txt ./DiscordChatExporter.Cli list guilds -t "mfa.Ifrn" > C:\path\to\output.txt
``` ```

View file

@ -13,14 +13,13 @@ namespace DiscordChatExporter.Cli.Commands;
[Command("export", Description = "Exports one or multiple channels.")] [Command("export", Description = "Exports one or multiple channels.")]
public partial class ExportChannelsCommand : ExportCommandBase public partial class ExportChannelsCommand : ExportCommandBase
{ {
// TODO: change this to plural (breaking change) [CommandParameter(
[CommandOption( 0,
"channel", Name = "channel-ids",
'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), " + "If not provided, channel IDs are read from standard input (one per line), "
+ "enabling piping from the 'channels' or 'dm' commands." + "enabling piping from the 'list channels' or 'list channels dm' commands."
)] )]
public IReadOnlyList<Snowflake> ChannelIds { get; set; } = []; public IReadOnlyList<Snowflake> ChannelIds { get; set; } = [];
@ -60,7 +59,7 @@ public partial class ExportChannelsCommand : ExportCommandBase
{ {
throw new CommandException( throw new CommandException(
"No channel IDs provided. " "No channel IDs provided. "
+ "Specify channel IDs via the '--channel' option or pipe them from the 'channels' or 'dm' commands." + "Specify channel IDs as arguments or pipe them from the 'list channels' or 'list channels dm' commands."
); );
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Binding; using CliFx.Binding;
@ -7,15 +8,16 @@ 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.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("channels", Description = "Get the list of channels in a server.")] [Command("list channels", Description = "Gets the list of channels in one or more servers.")]
public partial class GetChannelsCommand : DiscordCommandBase public partial class GetChannelsCommand : DiscordCommandBase
{ {
[CommandOption("guild", 'g', Description = "Server ID.")] [CommandParameter(0, Name = "guild-ids", Description = "Server ID(s).")]
public required Snowflake GuildId { get; set; } public required IReadOnlyList<Snowflake> GuildIds { get; set; }
[CommandOption("include-vc", Description = "Include voice channels.")] [CommandOption("include-vc", Description = "Include voice channels.")]
public bool IncludeVoiceChannels { get; set; } = true; public bool IncludeVoiceChannels { get; set; } = true;
@ -33,23 +35,20 @@ public partial class GetChannelsCommand : DiscordCommandBase
var cancellationToken = console.RegisterCancellationHandler(); var cancellationToken = console.RegisterCancellationHandler();
var channels = (await Discord.GetGuildChannelsAsync(GuildId, cancellationToken)) foreach (var guildId in GuildIds)
{
var channels = (await Discord.GetGuildChannelsAsync(guildId, cancellationToken))
.Where(c => !c.IsCategory) .Where(c => !c.IsCategory)
.Where(c => IncludeVoiceChannels || !c.IsVoice) .Where(c => IncludeVoiceChannels || !c.IsVoice)
.OrderBy(c => c.Parent?.Position) .OrderBy(c => c.Parent?.Position)
.ThenBy(c => c.Name) .ThenBy(c => c.Name)
.ToArray(); .ToArray();
var channelIdMaxLength = channels
.Select(c => c.Id.ToString().Length)
.OrderDescending()
.FirstOrDefault();
var threads = var threads =
ThreadInclusionMode != ThreadInclusionMode.None ThreadInclusionMode != ThreadInclusionMode.None
? ( ? (
await Discord.GetGuildThreadsAsync( await Discord.GetGuildThreadsAsync(
GuildId, guildId,
ThreadInclusionMode == ThreadInclusionMode.All, ThreadInclusionMode == ThreadInclusionMode.All,
null, null,
null, null,
@ -72,6 +71,20 @@ public partial class GetChannelsCommand : DiscordCommandBase
} }
else else
{ {
// Show guild header when listing multiple guilds
if (GuildIds.Count > 1)
{
var guild = await Discord.GetGuildAsync(guildId, cancellationToken);
using (console.WithForegroundColor(ConsoleColor.Cyan))
await console.Output.WriteLineAsync($"{guild.Id} | {guild.Name}");
}
var channelIdMaxLength = channels
.Select(c => c.Id.ToString().Length)
.OrderDescending()
.FirstOrDefault();
foreach (var channel in channels) foreach (var channel in channels)
{ {
// Channel ID // Channel ID
@ -122,6 +135,10 @@ public partial class GetChannelsCommand : DiscordCommandBase
); );
} }
} }
if (GuildIds.Count > 1)
await console.Output.WriteLineAsync();
}
} }
} }
} }

View file

@ -9,7 +9,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("dm", Description = "Gets the list of all direct message channels.")] [Command("list channels dm", Description = "Gets the list of direct message channels.")]
public partial class GetDirectChannelsCommand : DiscordCommandBase public partial class GetDirectChannelsCommand : DiscordCommandBase
{ {
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)

View file

@ -9,7 +9,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("guilds", Description = "Gets the list of accessible servers.")] [Command("list guilds", Description = "Gets the list of accessible servers.")]
public partial class GetGuildsCommand : DiscordCommandBase public partial class GetGuildsCommand : DiscordCommandBase
{ {
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)