From f8ab9260742b1a546f1a43d5e7eed7745c1e1b2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:23:45 +0000 Subject: [PATCH] Add 'list unwrap' command; remove category unwrapping from export; add [!IMPORTANT] callouts to pipeline doc sections Agent-Logs-Url: https://github.com/Tyrrrz/DiscordChatExporter/sessions/d2a03a38-0ed4-45c7-b8e7-615ffb35c971 Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- .docs/Using-the-CLI.md | 33 ++++++--- .../Commands/ExportChannelsCommand.cs | 27 +------ .../Commands/UnwrapChannelsCommand.cs | 71 +++++++++++++++++++ 3 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 DiscordChatExporter.Cli/Commands/UnwrapChannelsCommand.cs diff --git a/.docs/Using-the-CLI.md b/.docs/Using-the-CLI.md index 02545268..8626cf93 100644 --- a/.docs/Using-the-CLI.md +++ b/.docs/Using-the-CLI.md @@ -33,13 +33,14 @@ Type the following command in your terminal of choice, then press ENTER to run i ## CLI commands -| Command | Description | -| ----------------- | ---------------------------------------------------- | -| export | Exports one or more channels | -| list channels | Outputs the list of channels in the given server(s) | -| list channels dm | Outputs the list of direct message channels | -| list servers | Outputs the list of accessible servers | -| guide | Explains how to obtain token, server, and channel ID | +| Command | Description | +| ----------------- | -------------------------------------------------------------------- | +| export | Exports one or more channels | +| list channels | Outputs the list of channels in the given server(s) | +| list channels dm | Outputs the list of direct message channels | +| list servers | Outputs the list of accessible servers | +| list unwrap | Resolves categories in a channel list to their child channels | +| 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 `./dce guide`. @@ -244,6 +245,9 @@ Documentation on message filter syntax can be found [here](https://github.com/Ty ### Export channels from a specific server +> [!IMPORTANT] +> The following examples assume `DISCORD_TOKEN` is already set. See [CLI commands](#cli-commands) for instructions. + To export all channels in a specific server, use `list channels` to list channels and pipe the result to `export`. **Linux/macOS:** @@ -282,6 +286,9 @@ By default, voice channels are included. You can change this behavior by passing ### Export all DMs +> [!IMPORTANT] +> The following examples assume `DISCORD_TOKEN` is already set. See [CLI commands](#cli-commands) for instructions. + To export all DMs: **Linux/macOS:** @@ -304,7 +311,7 @@ To list the channels available in a specific server, use the `list channels` com ./dce list channels 21814 -t "mfa.Ifrn" ``` -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: +The `list channels` command outputs a JSON array of channel objects. You can pipe this directly to the `export` command: ```console ./dce list channels 21814 | ./dce export @@ -318,12 +325,20 @@ To list all DM channels accessible to the current account, use the `list channel ./dce list channels dm -t "mfa.Ifrn" ``` -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: +The `list channels dm` command outputs a JSON array of channel objects. You can pipe this directly to the `export` command: ```console ./dce list channels dm | ./dce export ``` +### Unwrap categories + +To resolve category channels in a list to their child channels, use the `list unwrap` command: + +```console +./dce list channels 21814 | ./dce list unwrap | ./dce export +``` + ### List servers To list all servers accessible by the current account, use the `list servers` command: diff --git a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs index a6ce1a1f..d2dc2435 100644 --- a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs @@ -8,7 +8,6 @@ using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Utils.Extensions; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord.Data; -using DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Cli.Commands; @@ -19,8 +18,7 @@ public partial class ExportChannelsCommand : ExportCommandBase 0, Name = "channel-ids", Description = "Channel ID(s). " - + "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 or as a JSON array), " + "enabling piping from the 'list channels' or 'list channels dm' commands." )] public IReadOnlyList ChannelIds { get; set; } = []; @@ -66,32 +64,11 @@ public partial class ExportChannelsCommand : ExportCommandBase await console.Output.WriteLineAsync("Resolving channel(s)..."); var channels = new List(); - var channelsByGuild = new Dictionary>(); foreach (var channelId in channelIds) { var channel = await Discord.GetChannelAsync(channelId, cancellationToken); - - // Unwrap categories - if (channel.IsCategory) - { - var guildChannels = - channelsByGuild.GetValueOrDefault(channel.GuildId) - ?? await Discord.GetGuildChannelsAsync(channel.GuildId, cancellationToken); - - foreach (var guildChannel in guildChannels) - { - if (guildChannel.Parent?.Id == channel.Id) - channels.Add(guildChannel); - } - - // Cache the guild channels to avoid redundant work - channelsByGuild[channel.GuildId] = guildChannels; - } - else - { - channels.Add(channel); - } + channels.Add(channel); } await ExportAsync(console, channels); diff --git a/DiscordChatExporter.Cli/Commands/UnwrapChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/UnwrapChannelsCommand.cs new file mode 100644 index 00000000..54db1091 --- /dev/null +++ b/DiscordChatExporter.Cli/Commands/UnwrapChannelsCommand.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using CliFx.Binding; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands.Base; +using DiscordChatExporter.Cli.Utils.Extensions; +using DiscordChatExporter.Cli.Utils.Json; +using DiscordChatExporter.Core.Discord; +using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Utils.Extensions; + +namespace DiscordChatExporter.Cli.Commands; + +[Command( + "list unwrap", + Description = "Resolves categories in a channel list to their child channels." +)] +public partial class UnwrapChannelsCommand : DiscordCommandBase +{ + public override async ValueTask ExecuteAsync(IConsole console) + { + await base.ExecuteAsync(console); + + var cancellationToken = console.RegisterCancellationHandler(); + + // Read all JSON from stdin (produced by 'list channels' or 'list channels dm') + var sb = new StringBuilder(); + await foreach (var line in console.Input.ReadLinesAsync(cancellationToken)) + sb.Append(line); + + var channels = + JsonSerializer.Deserialize( + sb.ToString().Trim(), + CliJsonSerializerContext.Instance.ChannelArray + ) ?? []; + + var result = new List(); + var channelsByGuild = new Dictionary>(); + + foreach (var channel in channels) + { + if (channel.IsCategory) + { + var guildChannels = + channelsByGuild.GetValueOrDefault(channel.GuildId) + ?? await Discord.GetGuildChannelsAsync(channel.GuildId, cancellationToken); + + foreach (var guildChannel in guildChannels) + { + if (guildChannel.Parent?.Id == channel.Id) + result.Add(guildChannel); + } + + channelsByGuild[channel.GuildId] = guildChannels; + } + else + { + result.Add(channel); + } + } + + await console.Output.WriteLineAsync( + JsonSerializer.Serialize( + result.ToArray(), + CliJsonSerializerContext.Instance.ChannelArray + ) + ); + } +}