From ec403bbe5df76270d2a96b1de61ad7a9e8a1aaf7 Mon Sep 17 00:00:00 2001 From: Toastie Date: Tue, 27 Aug 2024 21:21:39 +1200 Subject: [PATCH] Implemented .leaveunkeptservers which will cause the bot to leave all... Implemented .leaveunkeptservers which will cause the bot to leave all servers unmarked by .keep. Extremely dangerous and irreversible. Meant for use on public bot. --- .../DangerousCommands/CleanupCommands.cs | 24 +++- .../DangerousCommands/CleanupService.cs | 118 +++++++++++++++--- .../_common/ICleanupService.cs | 2 + src/EllieBot/data/aliases.yml | 4 +- .../data/strings/commands/commands.en-US.yml | 7 ++ 5 files changed, 134 insertions(+), 21 deletions(-) diff --git a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupCommands.cs b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupCommands.cs index c49e77b..c9d9bb2 100644 --- a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupCommands.cs +++ b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupCommands.cs @@ -2,7 +2,7 @@ namespace EllieBot.Modules.Administration; -public partial class Administration +public partial class Administration { [Group] public partial class CleanupCommands : CleanupModuleBase @@ -39,5 +39,27 @@ public partial class Administration await Response().Text("This guild's bot data will be saved.").SendAsync(); } + + [Cmd] + [OwnerOnly] + public async Task LeaveUnkeptServers() + { + var keptGuildCount = await _svc.GetKeptGuildCount(); + + var response = await PromptUserConfirmAsync(new EmbedBuilder() + .WithDescription($""" + Do you want the bot to leave all unkept servers? + + There are currently {keptGuildCount} kept servers. + + **This is a highly destructive and irreversible action.** + """)); + + if (!response) + return; + + await _svc.LeaveUnkeptServers(); + await ctx.OkAsync(); + } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs index 7bf7128..be01a0b 100644 --- a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs +++ b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs @@ -2,16 +2,21 @@ using LinqToDB.Data; using LinqToDB.EntityFrameworkCore; using LinqToDB.Mapping; +using LinqToDB.Tools; using EllieBot.Common.ModuleBehaviors; using EllieBot.Db.Models; +using System.Security.Cryptography; namespace EllieBot.Modules.Administration.DangerousCommands; public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService { + private TypedKey _cleanupReportKey = new("cleanup:report"); + private TypedKey _cleanupTriggerKey = new("cleanup:trigger"); + + private TypedKey _keepTriggerKey = new("keep:trigger"); + private readonly IPubSub _pubSub; - private TypedKey _keepReportKey = new("cleanup:report"); - private TypedKey _keepTriggerKey = new("cleanup:trigger"); private readonly DiscordSocketClient _client; private ConcurrentDictionary guildIds = new(); private readonly IBotCredsProvider _creds; @@ -29,11 +34,82 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService _db = db; } + public async Task OnReadyAsync() + { + await _pubSub.Sub(_cleanupTriggerKey, OnCleanupTrigger); + await _pubSub.Sub(_keepTriggerKey, InternalTriggerKeep); + + _client.JoinedGuild += ClientOnJoinedGuild; + + if (_client.ShardId == 0) + await _pubSub.Sub(_cleanupReportKey, OnKeepReport); + } + + private bool keepTriggered = false; + + private async ValueTask InternalTriggerKeep(bool arg) + { + if (keepTriggered) + return; + + keepTriggered = true; + try + { + await Task.Delay(10 + (10 * _client.ShardId)); + + var allGuildIds = _client.Guilds.Select(x => x.Id); + + var table = await GetKeptGuildsTable(); + + var dontDeleteList = await table + .Where(x => allGuildIds.Contains(x.GuildId)) + .Select(x => x.GuildId) + .ToListAsyncLinqToDB(); + + var dontDelete = dontDeleteList.ToHashSet(); + + guildIds = new(); + foreach (var guildId in allGuildIds) + { + if (dontDelete.Contains(guildId)) + continue; + + // 1 leave per 20 seconds per shard + await Task.Delay(RandomNumberGenerator.GetInt32(18_000, 22_000)); + + SocketGuild? guild = null; + try + { + guild = _client.GetGuild(guildId); + + if (guild is null) + { + Log.Warning("Unable to find guild {GuildId}", guildId); + continue; + } + + await guild.LeaveAsync(); + } + catch (Exception ex) + { + Log.Warning("Unable to leave guild {GuildName} [{GuildId}]: {ErrorMessage}", + guild?.Name, + guildId, + ex.Message); + } + } + } + finally + { + keepTriggered = false; + } + } + public async Task DeleteMissingGuildDataAsync() { guildIds = new(); var totalShards = _creds.GetCreds().TotalShards; - await _pubSub.Pub(_keepTriggerKey, true); + await _pubSub.Pub(_cleanupTriggerKey, true); var counter = 0; while (guildIds.Keys.Count < totalShards) { @@ -133,10 +209,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService public async Task KeepGuild(ulong guildId) { - await using var db = _db.GetDbContext(); - await using var ctx = db.CreateLinqToDBContext(); - - var table = ctx.CreateTable(tableOptions: TableOptions.CheckExistence); + var table = await GetKeptGuildsTable(); if (await table.AnyAsyncLinqToDB(x => x.GuildId == guildId)) return false; @@ -149,30 +222,37 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService return true; } + public async Task GetKeptGuildCount() + { + var table = await GetKeptGuildsTable(); + return await table.CountAsync(); + } + + private async Task> GetKeptGuildsTable() + { + await using var db = _db.GetDbContext(); + await using var ctx = db.CreateLinqToDBContext(); + var table = ctx.CreateTable(tableOptions: TableOptions.CheckExistence); + return table; + } + + public async Task LeaveUnkeptServers() + => await _pubSub.Pub(_keepTriggerKey, true); + private ValueTask OnKeepReport(KeepReport report) { guildIds[report.ShardId] = report.GuildIds; return default; } - public async Task OnReadyAsync() - { - await _pubSub.Sub(_keepTriggerKey, OnKeepTrigger); - - _client.JoinedGuild += ClientOnJoinedGuild; - - if (_client.ShardId == 0) - await _pubSub.Sub(_keepReportKey, OnKeepReport); - } - private async Task ClientOnJoinedGuild(SocketGuild arg) { await KeepGuild(arg.Id); } - private ValueTask OnKeepTrigger(bool arg) + private ValueTask OnCleanupTrigger(bool arg) { - _pubSub.Pub(_keepReportKey, + _pubSub.Pub(_cleanupReportKey, new KeepReport() { ShardId = _client.ShardId, diff --git a/src/EllieBot/Modules/Administration/DangerousCommands/_common/ICleanupService.cs b/src/EllieBot/Modules/Administration/DangerousCommands/_common/ICleanupService.cs index 5d4a720..e48bba4 100644 --- a/src/EllieBot/Modules/Administration/DangerousCommands/_common/ICleanupService.cs +++ b/src/EllieBot/Modules/Administration/DangerousCommands/_common/ICleanupService.cs @@ -4,4 +4,6 @@ public interface ICleanupService { Task DeleteMissingGuildDataAsync(); Task KeepGuild(ulong guildId); + Task GetKeptGuildCount(); + Task LeaveUnkeptServers(); } \ No newline at end of file diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index be045d0..66b7401 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -1423,4 +1423,6 @@ coins: afk: - afk keep: - - keep \ No newline at end of file + - keep +leaveunkeptservers: + - leaveunkeptservers \ No newline at end of file diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index 8016e89..912ca40 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -4554,5 +4554,12 @@ keep: The current serve, won't be deleted from Ellie's database during the purge. ex: - '' + params: + - { } +leaveunkeptservers: + desc: |- + Leaves all servers whose owners didn't run .keep + ex: + - '' params: - { } \ No newline at end of file