1
Fork 0
mirror of https://gitlab.com/Kwoth/nadekobot.git synced 2024-10-02 20:13:13 +00:00

add: 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:
Kwoth 2024-08-26 23:09:33 +00:00
parent 67b186a1a5
commit 9424d4d5f9
5 changed files with 134 additions and 21 deletions

View file

@ -2,7 +2,7 @@
namespace NadekoBot.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 NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using System.Security.Cryptography;
namespace NadekoBot.Modules.Administration.DangerousCommands;
public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
{
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, INService
_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,11 +209,8 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
public async Task<bool> KeepGuild(ulong guildId)
{
await using var db = _db.GetDbContext();
await using var ctx = db.CreateLinqToDBContext();
var table = await GetKeptGuildsTable();
var table = ctx.CreateTable<KeptGuilds>(tableOptions: TableOptions.CheckExistence);
if (await table.AnyAsyncLinqToDB(x => x.GuildId == guildId))
return false;
@ -149,30 +222,37 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
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 Nadeko's database during the purge.
ex:
- ''
params:
- { }
leaveunkeptservers:
desc: |-
Leaves all servers whose owners didn't run .keep
ex:
- ''
params:
- { }