From b04768633c54f61fbfe4553000269c28481158ae Mon Sep 17 00:00:00 2001 From: Toastie Date: Wed, 4 Sep 2024 22:33:34 +1200 Subject: [PATCH] more work on the greet cleanup --- README.md | 2 + src/EllieBot/Db/Models/GuildConfig.cs | 36 +- .../DangerousCommands/CleanupService.cs | 3 +- .../Administration/GreetBye/GreetService.cs | 469 ++++++------------ src/EllieBot/_common/Linq2DbExpressions.cs | 1 + 5 files changed, 185 insertions(+), 326 deletions(-) diff --git a/README.md b/README.md index 28fe07b..ca14f09 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # EllieBot +## ? This branch is a heavy work in progress and is not stable at all. + [![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page) ## Guides diff --git a/src/EllieBot/Db/Models/GuildConfig.cs b/src/EllieBot/Db/Models/GuildConfig.cs index a88d3c0..20f42b3 100644 --- a/src/EllieBot/Db/Models/GuildConfig.cs +++ b/src/EllieBot/Db/Models/GuildConfig.cs @@ -13,21 +13,23 @@ public class GuildConfig : DbEntity public string AutoAssignRoleIds { get; set; } - //greet stuff - public int AutoDeleteGreetMessagesTimer { get; set; } = 30; - public int AutoDeleteByeMessagesTimer { get; set; } = 30; - - public ulong GreetMessageChannelId { get; set; } - public ulong ByeMessageChannelId { get; set; } - - public bool SendDmGreetMessage { get; set; } - public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; - - public bool SendChannelGreetMessage { get; set; } - public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; - - public bool SendChannelByeMessage { get; set; } - public string ChannelByeMessageText { get; set; } = "%user% has left!"; + // //greet stuff + // public int AutoDeleteGreetMessagesTimer { get; set; } = 30; + // public int AutoDeleteByeMessagesTimer { get; set; } = 30; + // + // public ulong GreetMessageChannelId { get; set; } + // public ulong ByeMessageChannelId { get; set; } + // + // public bool SendDmGreetMessage { get; set; } + // public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + // + // public bool SendChannelGreetMessage { get; set; } + // public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + // + // public bool SendChannelByeMessage { get; set; } + // public string ChannelByeMessageText { get; set; } = "%user% has left!"; + // public bool SendBoostMessage { get; set; } + // pulic int BoostMessageDeleteAfter { get; set; } //self assignable roles public bool ExclusiveSelfAssignedRoles { get; set; } @@ -98,10 +100,6 @@ public class GuildConfig : DbEntity #region Boost Message - public bool SendBoostMessage { get; set; } - public string BoostMessage { get; set; } = "%user% just boosted this server!"; - public ulong BoostMessageChannelId { get; set; } - public int BoostMessageDeleteAfter { get; set; } public bool StickyRoles { get; set; } #endregion diff --git a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs index dd269bc..ee99b49 100644 --- a/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs +++ b/src/EllieBot/Modules/Administration/DangerousCommands/CleanupService.cs @@ -72,9 +72,8 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, IEService dontDelete = dontDeleteList.ToHashSet(); } - Log.Information("Leaving {RemainingCount} guilds every {Delay} seconds, {DontDeleteCount} will remain", + Log.Information("Leaving {RemainingCount} guilds every, 1 seconds. {DontDeleteCount} will remain", allGuildIds.Length - dontDelete.Count, - shardId, dontDelete.Count); foreach (var guildId in allGuildIds) { diff --git a/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs b/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs index 277c242..59fc1b7 100644 --- a/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs +++ b/src/EllieBot/Modules/Administration/GreetBye/GreetService.cs @@ -1,5 +1,7 @@ +using LinqToDB; +using LinqToDB.EntityFrameworkCore; +using LinqToDB.Tools; using EllieBot.Common.ModuleBehaviors; -using EllieBot.Db.Models; using System.Threading.Channels; namespace EllieBot.Services; @@ -11,7 +13,11 @@ public class GreetService : IEService, IReadyExecutor private readonly DbService _db; - private readonly ConcurrentDictionary _guildConfigsCache; + private ConcurrentHashSet _greetDmEnabledGuilds = new(); + private ConcurrentHashSet _boostEnabledGuilds = new(); + private ConcurrentHashSet _greetEnabledGuilds = new(); + private ConcurrentHashSet _byeEnabledGuilds = new(); + private readonly DiscordSocketClient _client; private readonly GreetGrouper _greets = new(); @@ -22,37 +28,52 @@ public class GreetService : IEService, IReadyExecutor public GreetService( DiscordSocketClient client, - IBot bot, DbService db, BotConfigService bss, IMessageSenderService sender, - IReplacementService repSvc) + IReplacementService repSvc + ) { _db = db; _client = client; _bss = bss; _repSvc = repSvc; _sender = sender; - - _guildConfigsCache = new(bot.AllGuildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create)); - - _client.UserJoined += OnUserJoined; - _client.UserLeft += OnUserLeft; - - bot.JoinedGuild += OnBotJoinedGuild; - _client.LeftGuild += OnClientLeftGuild; - - _client.GuildMemberUpdated += ClientOnGuildMemberUpdated; } public async Task OnReadyAsync() { - while (true) + // cache all enabled guilds + await using (var uow = _db.GetDbContext()) + { + var guilds = _client.Guilds.Select(x => x.Id).ToList(); + var enabled = await uow.GetTable() + .Where(x => x.GuildId.In(guilds)) + .Where(x => x.SendChannelGreetMessage + || x.SendBoostMessage + || x.SendChannelByeMessage + || x.SendDmGreetMessage) + .ToListAsync(); + + _boostEnabledGuilds = new(enabled.Where(x => x.SendBoostMessage).Select(x => x.GuildId)); + _byeEnabledGuilds = new(enabled.Where(x => x.SendChannelByeMessage).Select(x => x.GuildId)); + _greetDmEnabledGuilds = new(enabled.Where(x => x.SendDmGreetMessage).Select(x => x.GuildId)); + _greetEnabledGuilds = new(enabled.Where(x => x.SendChannelGreetMessage).Select(x => x.GuildId)); + } + + _client.UserJoined += OnUserJoined; + _client.UserLeft += OnUserLeft; + + _client.LeftGuild += OnClientLeftGuild; + + _client.GuildMemberUpdated += ClientOnGuildMemberUpdated; + + var timer = new PeriodicTimer(TimeSpan.FromSeconds(2)); + while (await timer.WaitForNextTickAsync()) { var (conf, user, compl) = await _greetDmQueue.Reader.ReadAsync(); var res = await GreetDmUserInternal(conf, user); compl.TrySetResult(res); - await Task.Delay(2000); } } @@ -65,25 +86,28 @@ public class GreetService : IEService, IReadyExecutor && newUser.PremiumSince is { } newDate && newDate > oldDate)) { - var conf = GetOrAddSettingsForGuild(newUser.Guild.Id); - if (!conf.SendBoostMessage) - return Task.CompletedTask; + _ = Task.Run(async () => + { + var conf = await GetGreetSettingsAsync(newUser.Guild.Id); - _ = Task.Run(TriggerBoostMessage(conf, newUser)); + if (conf is null || !conf.SendBoostMessage) + return; + + await TriggerBoostMessage(conf, newUser); + }); } return Task.CompletedTask; } - private Func TriggerBoostMessage(GreetSettings conf, SocketGuildUser user) - => async () => - { - var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId); - if (channel is null) - return; + private async Task TriggerBoostMessage(GreetSettings conf, SocketGuildUser user) + { + var channel = user.Guild.GetTextChannel(conf.BoostMessageChannelId); + if (channel is null) + return; - await SendBoostMessage(conf, user, channel); - }; + await SendBoostMessage(conf, user, channel); + } private async Task SendBoostMessage(GreetSettings conf, IGuildUser user, ITextChannel channel) { @@ -110,16 +134,17 @@ public class GreetService : IEService, IReadyExecutor return false; } - private Task OnClientLeftGuild(SocketGuild arg) + private async Task OnClientLeftGuild(SocketGuild arg) { - _guildConfigsCache.TryRemove(arg.Id, out _); - return Task.CompletedTask; - } + _boostEnabledGuilds.TryRemove(arg.Id); + _byeEnabledGuilds.TryRemove(arg.Id); + _greetDmEnabledGuilds.TryRemove(arg.Id); + _greetEnabledGuilds.TryRemove(arg.Id); - private Task OnBotJoinedGuild(GuildConfig gc) - { - _guildConfigsCache[gc.GuildId] = GreetSettings.Create(gc); - return Task.CompletedTask; + await using var uow = _db.GetDbContext(); + await uow.GetTable() + .Where(x => x.GuildId == arg.Id) + .DeleteAsync(); } private Task OnUserLeft(SocketGuild guild, SocketUser user) @@ -128,10 +153,11 @@ public class GreetService : IEService, IReadyExecutor { try { - var conf = GetOrAddSettingsForGuild(guild.Id); + var conf = await GetGreetSettingsAsync(guild.Id); - if (!conf.SendChannelByeMessage) + if (conf is null) return; + var channel = guild.TextChannels.FirstOrDefault(c => c.Id == conf.ByeMessageChannelId); if (channel is null) //maybe warn the server owner that the channel is missing @@ -156,7 +182,9 @@ public class GreetService : IEService, IReadyExecutor } } else + { await ByeUsers(conf, channel, new[] { user }); + } } catch { @@ -166,31 +194,14 @@ public class GreetService : IEService, IReadyExecutor return Task.CompletedTask; } - public string? GetDmGreetMsg(ulong id) + public async Task GetGreetSettingsAsync(ulong gid, GreetType type) { - using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(id, set => set).DmGreetMessageText; - } + await using var uow = _db.GetDbContext(); + var res = await uow.GetTable() + .Where(x => x.GuildId == gid && x.GreetType == type) + .FirstOrDefaultAsync(); - public string? GetGreetMsg(ulong gid) - { - using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set).ChannelGreetMessageText; - } - - public string? GetBoostMessage(ulong gid) - { - using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set).BoostMessage; - } - - public GreetSettings GetGreetSettings(ulong gid) - { - if (_guildConfigsCache.TryGetValue(gid, out var gs)) - return gs; - - using var uow = _db.GetDbContext(); - return GreetSettings.Create(uow.GuildConfigsForId(gid, set => set)); + return res; } private Task ByeUsers(GreetSettings conf, ITextChannel channel, IUser user) @@ -221,7 +232,7 @@ public class GreetService : IEService, IReadyExecutor Log.Warning(ex, "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}", channel.GuildId); - await SetBye(channel.GuildId, channel.Id, false); + await SetGreet(channel.GuildId, channel.Id, GreetType.Bye, false); } catch (Exception ex) { @@ -250,14 +261,14 @@ public class GreetService : IEService, IReadyExecutor if (conf.AutoDeleteGreetMessagesTimer > 0) toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer); } - catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions - || ex.DiscordCode == DiscordErrorCode.MissingPermissions - || ex.DiscordCode == DiscordErrorCode.UnknownChannel) + catch (HttpException ex) when (ex.DiscordCode is DiscordErrorCode.InsufficientPermissions + or DiscordErrorCode.MissingPermissions + or DiscordErrorCode.UnknownChannel) { Log.Warning(ex, "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", channel.GuildId); - await SetGreet(channel.GuildId, channel.Id, false); + await SetGreet(channel.GuildId, channel.Id, GreetType.Greet, false); } catch (Exception ex) { @@ -285,11 +296,6 @@ public class GreetService : IEService, IReadyExecutor { try { - // var rep = new ReplacementBuilder() - // .WithUser(user) - // .WithServer(_client, (SocketGuild)user.Guild) - // .Build(); - var repCtx = new ReplacementContext(client: _client, guild: user.Guild, users: user); var smartText = SmartText.CreateFrom(conf.DmGreetMessageText); smartText = await _repSvc.ReplaceAsync(smartText, repCtx); @@ -341,9 +347,9 @@ public class GreetService : IEService, IReadyExecutor { // if there is less than 10 embeds, add an embed with footer only seta.Embeds = seta.Embeds.Append(new SmartEmbedArrayElementText() - { - Footer = CreateFooterSource(user) - }) + { + Footer = CreateFooterSource(user) + }) .ToArray(); } } @@ -372,7 +378,9 @@ public class GreetService : IEService, IReadyExecutor { try { - var conf = GetOrAddSettingsForGuild(user.GuildId); + var conf = await GetGreetSettingsAsync(user.GuildId); + if (conf is null) + return; if (conf.SendChannelGreetMessage) { @@ -413,256 +421,107 @@ public class GreetService : IEService, IReadyExecutor return Task.CompletedTask; } - public string? GetByeMessage(ulong gid) - { - using var uow = _db.GetDbContext(); - return uow.GuildConfigsForId(gid, set => set).ChannelByeMessageText; - } + // public GreetSettings GetOrAddSettingsForGuild(ulong guildId) + // { + // if (_greetDmEnabledGuilds.TryGetValue(guildId, out var settings)) + // return settings; + // + // using (var uow = _db.GetDbContext()) + // { + // var gc = uow.GuildConfigsForId(guildId, set => set); + // settings = GreetSettings.Create(gc); + // } + // + // _greetDmEnabledGuilds.TryAdd(guildId, settings); + // return settings; + // } - public GreetSettings GetOrAddSettingsForGuild(ulong guildId) - { - if (_guildConfigsCache.TryGetValue(guildId, out var settings)) - return settings; - - using (var uow = _db.GetDbContext()) - { - var gc = uow.GuildConfigsForId(guildId, set => set); - settings = GreetSettings.Create(gc); - } - - _guildConfigsCache.TryAdd(guildId, settings); - return settings; - } - - public async Task SetGreet(ulong guildId, ulong channelId, bool? value = null) + public async Task SetGreet( + ulong guildId, + ulong? channelId, + GreetType greetType, + bool? value = null) { await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - var enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; - conf.GreetMessageChannelId = channelId; + var q = uow.GetTable(); - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; + if (value is { } v) + { + await q + .InsertOrUpdateAsync(() => new() + { + IsEnabled = v, + ChannelId = channelId, + }, + (old) => new() + { + IsEnabled = v, + ChannelId = channelId, + }, + () => new() + { + GuildId = guildId, + GreetType = greetType, + }); + } + else + { + await q + .Where(x => x.GuildId == guildId && x.GreetType == greetType) + .UpdateAsync((old) => new() + { + IsEnabled = !old.IsEnabled + }); + } - await uow.SaveChangesAsync(); - return enabled; + return true; } - public bool SetGreetMessage(ulong guildId, ref string message) + public async Task SetGreetTypeMessage(ulong guildId, GreetType greetType, string message) { message = message.SanitizeMentions(); if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message)); - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.ChannelGreetMessageText = message; - var greetMsgEnabled = conf.SendChannelGreetMessage; + await using (var uow = _db.GetDbContext()) + { + await uow.GetTable() + .InsertOrUpdateAsync(() => new() + { + MessageText = message + }, + x => new() + { + MessageText = message + }, + () => new() + { + GuildId = guildId, + GreetType = greetType + }); + } - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache.AddOrUpdate(guildId, toAdd, (_, _) => toAdd); + var conf = await GetGreetSettingsAsync(guildId, type); - uow.SaveChanges(); - return greetMsgEnabled; + return conf?.IsEnabled ?? false; } - public async Task SetGreetDm(ulong guildId, bool? value = null) + public async Task Test( + ulong guildId, + GreetType type, + IMessageChannel channel, + IGuildUser user) { - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - var enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - await uow.SaveChangesAsync(); - return enabled; + var conf = await GetGreetSettingsAsync(guildId, type); + return SendMessage(conf, user, channel); } - public bool SetGreetDmMessage(ulong guildId, ref string? message) + public async Task SendMessage(GreetSettings conf, IMessageChannel channel, IGuildUser user) { - message = message?.SanitizeMentions(); - - if (string.IsNullOrWhiteSpace(message)) - throw new ArgumentNullException(nameof(message)); - - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.DmGreetMessageText = message; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - uow.SaveChanges(); - return conf.SendDmGreetMessage; + if (conf.GreetType == GreetType.GreetDm) + { + await GreetDmUser(conf, user); + } } - - public async Task SetBye(ulong guildId, ulong channelId, bool? value = null) - { - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - var enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; - conf.ByeMessageChannelId = channelId; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - await uow.SaveChangesAsync(); - return enabled; - } - - public bool SetByeMessage(ulong guildId, ref string? message) - { - message = message?.SanitizeMentions(); - - if (string.IsNullOrWhiteSpace(message)) - throw new ArgumentNullException(nameof(message)); - - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.ChannelByeMessageText = message; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - uow.SaveChanges(); - return conf.SendChannelByeMessage; - } - - public async Task SetByeDel(ulong guildId, int timer) - { - if (timer is < 0 or > 600) - return; - - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.AutoDeleteByeMessagesTimer = timer; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - await uow.SaveChangesAsync(); - } - - public async Task SetGreetDel(ulong guildId, int timer) - { - if (timer is < 0 or > 600) - return; - - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.AutoDeleteGreetMessagesTimer = timer; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - await uow.SaveChangesAsync(); - } - - public bool SetBoostMessage(ulong guildId, ref string message) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.BoostMessage = message; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - uow.SaveChanges(); - return conf.SendBoostMessage; - } - - public async Task SetBoostDel(ulong guildId, int timer) - { - if (timer is < 0 or > 600) - throw new ArgumentOutOfRangeException(nameof(timer)); - - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - conf.BoostMessageDeleteAfter = timer; - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - - await uow.SaveChangesAsync(); - } - - public async Task ToggleBoost(ulong guildId, ulong channelId, bool? forceState = null) - { - await using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - - if (forceState is not bool fs) - conf.SendBoostMessage = !conf.SendBoostMessage; - else - conf.SendBoostMessage = fs; - - conf.BoostMessageChannelId = channelId; - await uow.SaveChangesAsync(); - - var toAdd = GreetSettings.Create(conf); - _guildConfigsCache[guildId] = toAdd; - return conf.SendBoostMessage; - } - - #region Get Enabled Status - - public bool GetGreetDmEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendDmGreetMessage; - } - - public bool GetGreetEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendChannelGreetMessage; - } - - public bool GetByeEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendChannelByeMessage; - } - - public bool GetBoostEnabled(ulong guildId) - { - using var uow = _db.GetDbContext(); - var conf = uow.GuildConfigsForId(guildId, set => set); - return conf.SendBoostMessage; - } - - #endregion - - #region Test Messages - - public Task ByeTest(ITextChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return ByeUsers(conf, channel, user); - } - - public Task GreetTest(ITextChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return GreetUsers(conf, channel, user); - } - - public Task GreetDmTest(IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return GreetDmUser(conf, user); - } - - public Task BoostTest(ITextChannel channel, IGuildUser user) - { - var conf = GetOrAddSettingsForGuild(user.GuildId); - return SendBoostMessage(conf, user, channel); - } - - #endregion } \ No newline at end of file diff --git a/src/EllieBot/_common/Linq2DbExpressions.cs b/src/EllieBot/_common/Linq2DbExpressions.cs index 53c90e6..5a1e145 100644 --- a/src/EllieBot/_common/Linq2DbExpressions.cs +++ b/src/EllieBot/_common/Linq2DbExpressions.cs @@ -1,5 +1,6 @@ #nullable disable using LinqToDB; +using LinqToDB.EntityFrameworkCore; using System.Linq.Expressions; namespace EllieBot.Common;