diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index 26491d0c..15185ca3 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -9,7 +9,6 @@ using NadekoBot.Services; using NadekoBot.Attributes; using NadekoBot.Services.Database.Models; using NadekoBot.Services.Administration; -using Discord.WebSocket; namespace NadekoBot.Modules.Administration { @@ -311,67 +310,6 @@ namespace NadekoBot.Modules.Administration await ReplyConfirmLocalized("set_channel_name").ConfigureAwait(false); } - - //delets her own messages, no perm required - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - public async Task Prune() - { - var user = await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false); - - var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()) - .Where(x => x.Author.Id == user.Id && DateTime.UtcNow - x.CreatedAt < twoWeeks); - await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); - Context.Message.DeleteAfter(3); - } - - - private TimeSpan twoWeeks => TimeSpan.FromDays(14); - // prune x - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(ChannelPermission.ManageMessages)] - [RequireBotPermission(GuildPermission.ManageMessages)] - [Priority(0)] - public async Task Prune(int count) - { - if (count < 1) - return; - await Context.Message.DeleteAsync().ConfigureAwait(false); - int limit = (count < 100) ? count + 1 : 100; - var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false)) - .Where(x => DateTime.UtcNow - x.CreatedAt < twoWeeks); - if (enumerable.FirstOrDefault()?.Id == Context.Message.Id) - enumerable = enumerable.Skip(1).ToArray(); - else - enumerable = enumerable.Take(count); - await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); - } - - //prune @user [x] - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(ChannelPermission.ManageMessages)] - [RequireBotPermission(GuildPermission.ManageMessages)] - [Priority(1)] - public async Task Prune(IGuildUser user, int count = 100) - { - if (count < 1) - return; - - if (count > 100) - count = 100; - - if (user.Id == Context.User.Id) - count += 1; - var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()) - .Where(m => m.Author.Id == user.Id && DateTime.UtcNow - m.CreatedAt < twoWeeks) - .Take(count); - await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false); - - Context.Message.DeleteAfter(3); - } - [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.MentionEveryone)] diff --git a/src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs new file mode 100644 index 00000000..96b668c5 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/PruneCommands.cs @@ -0,0 +1,70 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services.Administration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class PruneCommands : ModuleBase + { + private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14); + private readonly PruneService _prune; + + public PruneCommands(PruneService prune) + { + _prune = prune; + } + + //delets her own messages, no perm required + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Prune() + { + var user = await Context.Guild.GetCurrentUserAsync().ConfigureAwait(false); + + await _prune.PruneWhere((ITextChannel)Context.Channel, 100, (x) => x.Author.Id == user.Id).ConfigureAwait(false); + Context.Message.DeleteAfter(3); + } + // prune x + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(ChannelPermission.ManageMessages)] + [RequireBotPermission(GuildPermission.ManageMessages)] + [Priority(0)] + public async Task Prune(int count) + { + if (count < 1) + return; + if (count > 1000) + count = 1000; + await Context.Message.DeleteAsync().ConfigureAwait(false); + await _prune.PruneWhere((ITextChannel)Context.Channel, count, x => true).ConfigureAwait(false); + } + + //prune @user [x] + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(ChannelPermission.ManageMessages)] + [RequireBotPermission(GuildPermission.ManageMessages)] + [Priority(1)] + public async Task Prune(IGuildUser user, int count = 100) + { + if (count < 1) + return; + + if (count > 1000) + count = 1000; + await _prune.PruneWhere((ITextChannel)Context.Channel, count, m => m.Author.Id == user.Id && DateTime.UtcNow - m.CreatedAt < twoWeeks); + } + } + } +} diff --git a/src/NadekoBot/Modules/NadekoModule.cs b/src/NadekoBot/Modules/NadekoModule.cs index cfc96dce..dae471d4 100644 --- a/src/NadekoBot/Modules/NadekoModule.cs +++ b/src/NadekoBot/Modules/NadekoModule.cs @@ -121,8 +121,10 @@ namespace NadekoBot.Modules return Task.CompletedTask; } - userInputTask.SetResult(arg.Content); - userMsg.DeleteAfter(1); + if (userInputTask.TrySetResult(arg.Content)) + { + userMsg.DeleteAfter(1); + } return Task.CompletedTask; }); return Task.CompletedTask; diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index ac99d01e..87d54e11 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -92,7 +92,7 @@ namespace NadekoBot CommandService = new CommandService(new CommandServiceConfig() { CaseSensitiveCommands = false, - DefaultRunMode = RunMode.Sync, + DefaultRunMode = RunMode.Async, }); //foundation services @@ -127,6 +127,7 @@ namespace NadekoBot var commandMapService = new CommandMapService(AllGuildConfigs); var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency); var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService); + var pruneService = new PruneService(); #endregion #region permissions @@ -197,6 +198,7 @@ namespace NadekoBot .Add(converterService) .Add(verboseErrorsService) .Add(patreonRewardsService) + .Add(pruneService) .Add(searchesService) .Add(streamNotificationService) .Add(animeSearchService) diff --git a/src/NadekoBot/Services/Administration/PruneService.cs b/src/NadekoBot/Services/Administration/PruneService.cs new file mode 100644 index 00000000..894206b8 --- /dev/null +++ b/src/NadekoBot/Services/Administration/PruneService.cs @@ -0,0 +1,68 @@ +using Discord; +using NadekoBot.Extensions; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Administration +{ + public class PruneService + { + //channelids where prunes are currently occuring + private ConcurrentHashSet _pruningChannels = new ConcurrentHashSet(); + private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14); + + public async Task PruneWhere(ITextChannel channel, int amount, Func predicate) + { + channel.ThrowIfNull(nameof(channel)); + if (amount <= 0) + throw new ArgumentOutOfRangeException(nameof(amount)); + + if (!_pruningChannels.Add(channel.Id)) + return; + + try + { + IMessage[] msgs; + IMessage lastMessage = null; + msgs = (await channel.GetMessagesAsync(amount > 100 ? 100 : amount).Flatten()).Where(predicate).ToArray(); + while (amount > 0 && msgs.Any()) + { + lastMessage = msgs[msgs.Length - 1]; + + var bulkDeletable = new List(); + var singleDeletable = new List(); + foreach (var x in msgs) + { + if (DateTime.UtcNow - x.CreatedAt < twoWeeks) + bulkDeletable.Add(x); + else + singleDeletable.Add(x); + } + + if (bulkDeletable.Count > 0) + await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable)).ConfigureAwait(false); + + var i = 0; + foreach (var group in singleDeletable.GroupBy(x => ++i / (singleDeletable.Count / 5))) + await Task.WhenAll(Task.Delay(1000), Task.WhenAll(group.Select(x => x.DeleteAsync()))).ConfigureAwait(false); + + amount -= 100; + if(amount > 0) + msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, amount > 100 ? 100 : amount).Flatten()).Where(predicate).ToArray(); + } + } + catch (Exception ex) + { + + } + finally + { + _pruningChannels.TryRemove(channel.Id); + } + } + } +}