mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-06-10 00:02:37 -06:00
Introduce a message-deletion feature across CLI and GUI. Adds a new CLI command (deletemessages) with channel/before/after options and console progress. Extends DiscordClient with GetCurrentUserAsync and DeleteMessageAsync (including rate-limit handling) to perform deletions and surface authorization outcomes. GUI additions include DeleteSetup dialog, its ViewModel, view, and wiring: App registration, View/ViewModel managers, DashboardViewModel command, and a Delete button in the dashboard; deletion runs per-channel (parallel, with progress) and reports success/failure summaries.
176 lines
5.7 KiB
C#
176 lines
5.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using CliFx.Attributes;
|
|
using CliFx.Infrastructure;
|
|
using DiscordChatExporter.Cli.Commands.Base;
|
|
using DiscordChatExporter.Core.Discord;
|
|
using DiscordChatExporter.Core.Utils.Extensions;
|
|
|
|
namespace DiscordChatExporter.Cli.Commands;
|
|
|
|
[Command("deletemessages", Description = "Deletes messages from a channel.")]
|
|
public class DeleteMessagesCommand : DiscordCommandBase
|
|
{
|
|
[CommandOption(
|
|
"channel",
|
|
'c',
|
|
Description = "Channel ID. Note: You can only delete your own messages in DMs."
|
|
)]
|
|
public required Snowflake ChannelId { get; init; }
|
|
|
|
[CommandOption(
|
|
"before",
|
|
Description = "Limit to messages sent before this date (formatted using the current culture)."
|
|
)]
|
|
public DateTimeOffset? Before { get; init; }
|
|
|
|
[CommandOption(
|
|
"after",
|
|
Description = "Limit to messages sent after this date (formatted using the current culture)."
|
|
)]
|
|
public DateTimeOffset? After { get; init; }
|
|
|
|
public override async ValueTask ExecuteAsync(IConsole console)
|
|
{
|
|
await base.ExecuteAsync(console);
|
|
|
|
var cancellationToken = console.RegisterCancellationHandler();
|
|
|
|
// Get current user
|
|
await console.Output.WriteLineAsync("Getting current user...");
|
|
var currentUser = await Discord.GetCurrentUserAsync(cancellationToken);
|
|
await console.Output.WriteLineAsync($"Authenticated as: {currentUser.FullName}");
|
|
await console.Output.WriteLineAsync();
|
|
|
|
// Resolve the channel
|
|
await console.Output.WriteLineAsync("Resolving channel...");
|
|
var channel = await Discord.GetChannelAsync(ChannelId, cancellationToken);
|
|
|
|
// Warning message
|
|
using (console.WithForegroundColor(ConsoleColor.Yellow))
|
|
{
|
|
await console.Output.WriteLineAsync(
|
|
"WARNING: This will delete your messages from the channel."
|
|
);
|
|
await console.Output.WriteLineAsync("Messages from other users will be skipped.");
|
|
}
|
|
|
|
await console.Output.WriteLineAsync();
|
|
await console.Output.WriteLineAsync($"Channel: {channel.Name}");
|
|
|
|
if (After is not null)
|
|
await console.Output.WriteLineAsync($"After: {After:g}");
|
|
|
|
if (Before is not null)
|
|
await console.Output.WriteLineAsync($"Before: {Before:g}");
|
|
|
|
await console.Output.WriteLineAsync();
|
|
|
|
// Count user's messages
|
|
await console.Output.WriteLineAsync("Counting your messages...");
|
|
|
|
var beforeSnowflake = Before?.Pipe(Snowflake.FromDate);
|
|
var afterSnowflake = After?.Pipe(Snowflake.FromDate);
|
|
|
|
var userMessageIds = new List<Snowflake>();
|
|
await foreach (
|
|
var message in Discord.GetMessagesAsync(
|
|
ChannelId,
|
|
afterSnowflake,
|
|
beforeSnowflake,
|
|
null,
|
|
cancellationToken
|
|
)
|
|
)
|
|
{
|
|
if (message.Author.Id == currentUser.Id)
|
|
{
|
|
userMessageIds.Add(message.Id);
|
|
}
|
|
}
|
|
|
|
var totalUserMessages = userMessageIds.Count;
|
|
await console.Output.WriteLineAsync($"Found {totalUserMessages} of your messages");
|
|
await console.Output.WriteLineAsync();
|
|
|
|
if (totalUserMessages == 0)
|
|
{
|
|
using (console.WithForegroundColor(ConsoleColor.Yellow))
|
|
{
|
|
await console.Output.WriteLineAsync("No messages to delete.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Delete messages
|
|
await console.Output.WriteLineAsync("Deleting messages...");
|
|
|
|
var successCount = 0;
|
|
var failedCount = 0;
|
|
|
|
foreach (var messageId in userMessageIds)
|
|
{
|
|
try
|
|
{
|
|
var deleted = await Discord.DeleteMessageAsync(
|
|
ChannelId,
|
|
messageId,
|
|
cancellationToken
|
|
);
|
|
|
|
if (deleted)
|
|
{
|
|
successCount++;
|
|
using (console.WithForegroundColor(ConsoleColor.Green))
|
|
{
|
|
await console.Output.WriteAsync("\r");
|
|
await console.Output.WriteAsync(
|
|
$"✓ Deleted {successCount} / {totalUserMessages} messages "
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
failedCount++;
|
|
}
|
|
|
|
// Discord's rate limit headers are handled automatically by DeleteMessageAsync
|
|
}
|
|
catch
|
|
{
|
|
failedCount++;
|
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
|
{
|
|
await console.Output.WriteAsync("\r");
|
|
await console.Output.WriteAsync(
|
|
$"✗ Error | Deleted: {successCount} / {totalUserMessages}, Failed: {failedCount} "
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear the progress line
|
|
await console.Output.WriteLineAsync();
|
|
await console.Output.WriteLineAsync();
|
|
|
|
// Report results
|
|
await console.Output.WriteLineAsync("Deletion complete!");
|
|
|
|
using (console.WithForegroundColor(ConsoleColor.Green))
|
|
{
|
|
await console.Output.WriteLineAsync(
|
|
$"Successfully deleted: {successCount} / {totalUserMessages} message(s)"
|
|
);
|
|
}
|
|
|
|
if (failedCount > 0)
|
|
{
|
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
|
{
|
|
await console.Output.WriteLineAsync($"Failed to delete: {failedCount} message(s)");
|
|
}
|
|
}
|
|
}
|
|
}
|