diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index 3417ee20..b8cdb144 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Dapper; + using DSharpPlus; using DSharpPlus.Entities; @@ -11,12 +13,12 @@ namespace PluralKit.Bot { public class ServerConfig { - private IDataStore _data; + private DbConnectionFactory _db; private LoggerCleanService _cleanService; - public ServerConfig(IDataStore data, LoggerCleanService cleanService) + public ServerConfig(LoggerCleanService cleanService, DbConnectionFactory db) { - _data = data; _cleanService = cleanService; + _db = db; } public async Task SetLogChannel(Context ctx) @@ -28,10 +30,11 @@ namespace PluralKit.Bot channel = await ctx.MatchChannel() ?? throw new PKSyntaxError("You must pass a #channel to set."); if (channel != null && channel.GuildId != ctx.Guild.Id) throw new PKError("That channel is not in this server!"); - var cfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id); - cfg.LogChannel = channel?.Id; - await _data.SaveGuildConfig(cfg); - + await _db.Execute(c => c.ExecuteAsync(QueryBuilder.Upsert("servers", "id") + .Constant("id", "@Id") + .Variable("log_channel", "@LogChannel") + .Build(), new {Id = ctx.Guild.Id, LogChannel = channel?.Id})); + if (channel != null) await ctx.Reply($"{Emojis.Success} Proxy logging channel set to #{channel.Name.SanitizeMentions()}."); else @@ -52,15 +55,24 @@ namespace PluralKit.Bot if (channel.GuildId != ctx.Guild.Id) throw new PKError($"Channel {ctx.Guild.Id} is not in this server."); affectedChannels.Add(channel); } - - var guildCfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id); - if (enable) guildCfg.LogBlacklist.ExceptWith(affectedChannels.Select(c => c.Id)); - else guildCfg.LogBlacklist.UnionWith(affectedChannels.Select(c => c.Id)); - await _data.SaveGuildConfig(guildCfg); + ulong? logChannel = null; + await using (var conn = await _db.Obtain()) + { + var config = await conn.QueryOrInsertGuildConfig(ctx.Guild.Id); + logChannel = config.LogChannel; + var blacklist = config.LogBlacklist.ToHashSet(); + if (enable) + blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); + else + blacklist.UnionWith(affectedChannels.Select(c => c.Id)); + await conn.ExecuteAsync("update servers set log_blacklist = @LogBlacklist where id = @Id", + new {ctx.Guild.Id, LogBlacklist = blacklist}); + } + await ctx.Reply( $"{Emojis.Success} Message logging for the given channels {(enable ? "enabled" : "disabled")}." + - (guildCfg.LogChannel == null ? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;log channel #your-log-channel`." : "")); + (logChannel == null ? $"\n{Emojis.Warn} Please note that no logging channel is set, so there is nowhere to log messages to. You can set a logging channel using `pk;log channel #your-log-channel`." : "")); } public async Task SetBlacklisted(Context ctx, bool onBlacklist) @@ -78,11 +90,18 @@ namespace PluralKit.Bot affectedChannels.Add(channel); } - var guildCfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id); - if (onBlacklist) guildCfg.Blacklist.UnionWith(affectedChannels.Select(c => c.Id)); - else guildCfg.Blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); + await using (var conn = await _db.Obtain()) + { + var guild = await conn.QueryOrInsertGuildConfig(ctx.Guild.Id); + var blacklist = guild.Blacklist.ToHashSet(); + if (onBlacklist) + blacklist.ExceptWith(affectedChannels.Select(c => c.Id)); + else + blacklist.UnionWith(affectedChannels.Select(c => c.Id)); + await conn.ExecuteAsync("update servers set blacklist = @Blacklist where id = @Id", + new {ctx.Guild.Id, Blacklist = blacklist}); + } - await _data.SaveGuildConfig(guildCfg); await ctx.Reply($"{Emojis.Success} Channels {(onBlacklist ? "added to" : "removed from")} the proxy blacklist."); } @@ -90,33 +109,35 @@ namespace PluralKit.Bot { ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server"); - var guildCfg = await _data.GetOrCreateGuildConfig(ctx.Guild.Id); var botList = string.Join(", ", _cleanService.Bots.Select(b => b.Name).OrderBy(x => x.ToLowerInvariant())); - + + bool newValue; if (ctx.Match("enable", "on", "yes")) - { - guildCfg.LogCleanupEnabled = true; - await _data.SaveGuildConfig(guildCfg); - await ctx.Reply($"{Emojis.Success} Log cleanup has been **enabled** for this server. Messages deleted by PluralKit will now be cleaned up from logging channels managed by the following bots:\n- **{botList}**\n\n{Emojis.Note} Make sure PluralKit has the **Manage Messages** permission in the channels in question.\n{Emojis.Note} Also, make sure to blacklist the logging channel itself from the bots in question to prevent conflicts."); - } + newValue = true; else if (ctx.Match("disable", "off", "no")) - { - guildCfg.LogCleanupEnabled = false; - await _data.SaveGuildConfig(guildCfg); - await ctx.Reply($"{Emojis.Success} Log cleanup has been **disabled** for this server."); - } + newValue = false; else { var eb = new DiscordEmbedBuilder() .WithTitle("Log cleanup settings") .AddField("Supported bots", botList); - + + var guildCfg = await _db.Execute(c => c.QueryOrInsertGuildConfig(ctx.Guild.Id)); if (guildCfg.LogCleanupEnabled) eb.WithDescription("Log cleanup is currently **on** for this server. To disable it, type `pk;logclean off`."); else eb.WithDescription("Log cleanup is currently **off** for this server. To enable it, type `pk;logclean on`."); await ctx.Reply(embed: eb.Build()); + return; } + + await _db.Execute(c => c.ExecuteAsync("update servers set log_cleanup_enabled = @Value where id = @Id", + new {ctx.Guild.Id, Value = newValue})); + + if (newValue) + await ctx.Reply($"{Emojis.Success} Log cleanup has been **enabled** for this server. Messages deleted by PluralKit will now be cleaned up from logging channels managed by the following bots:\n- **{botList}**\n\n{Emojis.Note} Make sure PluralKit has the **Manage Messages** permission in the channels in question.\n{Emojis.Note} Also, make sure to blacklist the logging channel itself from the bots in question to prevent conflicts."); + else + await ctx.Reply($"{Emojis.Success} Log cleanup has been **disabled** for this server."); } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/GuildConfig.cs b/PluralKit.Core/Models/GuildConfig.cs new file mode 100644 index 00000000..0f4dc72c --- /dev/null +++ b/PluralKit.Core/Models/GuildConfig.cs @@ -0,0 +1,11 @@ +namespace PluralKit.Core +{ + public class GuildConfig + { + public int Id { get; } + public ulong? LogChannel { get; } + public ulong[] LogBlacklist { get; } + public ulong[] Blacklist { get; } + public bool LogCleanupEnabled { get; } + } +} \ No newline at end of file diff --git a/PluralKit.Core/Models/ModelQueryExt.cs b/PluralKit.Core/Models/ModelQueryExt.cs new file mode 100644 index 00000000..f8dca6a7 --- /dev/null +++ b/PluralKit.Core/Models/ModelQueryExt.cs @@ -0,0 +1,14 @@ +using System; +using System.Data; +using System.Threading.Tasks; + +using Dapper; + +namespace PluralKit.Core +{ + public static class ModelQueryExt + { + public static Task QueryOrInsertGuildConfig(this IDbConnection conn, ulong guild) => + conn.QueryFirstOrDefaultAsync("insert into servers (id) values (@Guild) on conflict do nothing returning *", new {Guild = guild}); + } +} \ No newline at end of file diff --git a/PluralKit.Core/Services/IDataStore.cs b/PluralKit.Core/Services/IDataStore.cs index 21a8714b..f38e5b70 100644 --- a/PluralKit.Core/Services/IDataStore.cs +++ b/PluralKit.Core/Services/IDataStore.cs @@ -48,16 +48,7 @@ namespace PluralKit.Core { public int Member; public Instant Timestamp; } - - public struct GuildConfig - { - public ulong Id { get; set; } - public ulong? LogChannel { get; set; } - public ISet LogBlacklist { get; set; } - public ISet Blacklist { get; set; } - public bool LogCleanupEnabled { get; set; } - } - + public class SystemGuildSettings { public ulong Guild { get; set; } @@ -348,16 +339,5 @@ namespace PluralKit.Core { /// Gets the total amount of messages in the data store. /// Task GetTotalMessages(); - - /// - /// Gets the guild configuration struct for a given guild, creating and saving one if none was found. - /// - /// The guild's configuration struct. - Task GetOrCreateGuildConfig(ulong guild); - - /// - /// Saves the given guild configuration struct to the data store. - /// - Task SaveGuildConfig(GuildConfig cfg); } } \ No newline at end of file diff --git a/PluralKit.Core/Services/PostgresDataStore.cs b/PluralKit.Core/Services/PostgresDataStore.cs index 29fd660b..8eb39d54 100644 --- a/PluralKit.Core/Services/PostgresDataStore.cs +++ b/PluralKit.Core/Services/PostgresDataStore.cs @@ -270,52 +270,6 @@ namespace PluralKit.Core { return await conn.ExecuteScalarAsync("select count(mid) from messages"); } - // Same as GuildConfig, but with ISet as long[] instead. - public struct DatabaseCompatibleGuildConfig - { - public ulong Id { get; set; } - public ulong? LogChannel { get; set; } - public long[] LogBlacklist { get; set; } - public long[] Blacklist { get; set; } - - public bool LogCleanupEnabled { get; set; } - - public GuildConfig Into() => - new GuildConfig - { - Id = Id, - LogChannel = LogChannel, - LogBlacklist = new HashSet(LogBlacklist?.Select(c => (ulong) c) ?? new ulong[] {}), - Blacklist = new HashSet(Blacklist?.Select(c => (ulong) c) ?? new ulong[]{}), - LogCleanupEnabled = LogCleanupEnabled - }; - } - - public async Task GetOrCreateGuildConfig(ulong guild) - { - // When changing this, also see ProxyCache::GetGuildDataCached - using (var conn = await _conn.Obtain()) - { - return (await conn.QuerySingleOrDefaultAsync( - "insert into servers (id) values (@Id) on conflict do nothing; select * from servers where id = @Id", - new {Id = guild})).Into(); - } - } - - public async Task SaveGuildConfig(GuildConfig cfg) - { - using (var conn = await _conn.Obtain()) - await conn.ExecuteAsync("insert into servers (id, log_channel, log_blacklist, blacklist, log_cleanup_enabled) values (@Id, @LogChannel, @LogBlacklist, @Blacklist, @LogCleanupEnabled) on conflict (id) do update set log_channel = @LogChannel, log_blacklist = @LogBlacklist, blacklist = @Blacklist, log_cleanup_enabled = @LogCleanupEnabled", new - { - cfg.Id, - cfg.LogChannel, - cfg.LogCleanupEnabled, - LogBlacklist = cfg.LogBlacklist.Select(c => (long) c).ToList(), - Blacklist = cfg.Blacklist.Select(c => (long) c).ToList() - }); - _logger.Information("Updated guild configuration {@GuildCfg}", cfg); - } - public async Task AddSwitch(PKSystem system, IEnumerable members) { // Use a transaction here since we're doing multiple executed commands in one