#nullable disable namespace EllieBot.Modules.Administration.Services; public class PruneService(ILogCommandService logService) : IEService { private readonly ConcurrentDictionary<ulong, CancellationTokenSource> _pruningGuilds = new(); private readonly TimeSpan _twoWeeks = TimeSpan.FromDays(14); public async Task<PruneResult> PruneWhere( ulong runningUserId, IMessageChannel channel, int amount, Func<IMessage, bool> predicate, IProgress<(int deleted, int total)> progress, ulong? after = null ) { ArgumentNullException.ThrowIfNull(channel, nameof(channel)); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount); var originalAmount = amount; var gid = (channel as ITextChannel)?.GuildId ?? channel.Id; using var cancelSource = new CancellationTokenSource(); if (!_pruningGuilds.TryAdd(gid, cancelSource)) return PruneResult.AlreadyRunning; try { var now = DateTime.UtcNow; IMessage[] msgs; IMessage lastMessage = null; while (amount > 0 && !cancelSource.IsCancellationRequested) { var dled = lastMessage is null ? await channel.GetMessagesAsync(50).FlattenAsync() : await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync(); msgs = dled .Where(predicate) .Where(x => after is not ulong a || x.Id > a) .Take(amount) .ToArray(); if (!msgs.Any()) return PruneResult.Success; lastMessage = msgs[^1]; var bulkDeletable = new List<IMessage>(); var singleDeletable = new List<IMessage>(); foreach (var x in msgs) { logService.AddDeleteIgnore(x.Id); if (now - x.CreatedAt < _twoWeeks) bulkDeletable.Add(x); else singleDeletable.Add(x); } if (channel is ITextChannel tc2 && bulkDeletable.Count > 0) { await tc2.DeleteMessagesAsync(bulkDeletable); amount -= msgs.Length; progress.Report((originalAmount - amount, originalAmount)); await Task.Delay(2000, cancelSource.Token); } foreach (var group in singleDeletable.Chunk(5)) { await group.Select(x => x.DeleteAsync()).WhenAll(); amount -= 5; progress.Report((originalAmount - amount, originalAmount)); await Task.Delay(5000, cancelSource.Token); } } } catch { //ignore } finally { _pruningGuilds.TryRemove(gid, out _); } return PruneResult.Success; } public async Task<bool> CancelAsync(ulong guildId) { if (!_pruningGuilds.TryRemove(guildId, out var source)) return false; await source.CancelAsync(); return true; } }