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.
This commit is contained in:
Toastie 2024-08-27 21:21:39 +12:00
parent 37438f33a2
commit ec403bbe5d
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
5 changed files with 134 additions and 21 deletions

View file

@ -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();
}
}
}

View file

@ -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<KeepReport> _cleanupReportKey = new("cleanup:report");
private TypedKey<bool> _cleanupTriggerKey = new("cleanup:trigger");
private TypedKey<bool> _keepTriggerKey = new("keep:trigger");
private readonly IPubSub _pubSub;
private TypedKey<KeepReport> _keepReportKey = new("cleanup:report");
private TypedKey<bool> _keepTriggerKey = new("cleanup:trigger");
private readonly DiscordSocketClient _client;
private ConcurrentDictionary<int, ulong[]> 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<KeepResult?> 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<bool> KeepGuild(ulong guildId)
{
await using var db = _db.GetDbContext();
await using var ctx = db.CreateLinqToDBContext();
var table = ctx.CreateTable<KeptGuilds>(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<int> GetKeptGuildCount()
{
var table = await GetKeptGuildsTable();
return await table.CountAsync();
}
private async Task<ITable<KeptGuilds>> GetKeptGuildsTable()
{
await using var db = _db.GetDbContext();
await using var ctx = db.CreateLinqToDBContext();
var table = ctx.CreateTable<KeptGuilds>(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,

View file

@ -4,4 +4,6 @@ public interface ICleanupService
{
Task<KeepResult?> DeleteMissingGuildDataAsync();
Task<bool> KeepGuild(ulong guildId);
Task<int> GetKeptGuildCount();
Task LeaveUnkeptServers();
}

View file

@ -1423,4 +1423,6 @@ coins:
afk:
- afk
keep:
- keep
- keep
leaveunkeptservers:
- leaveunkeptservers

View file

@ -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:
- { }