From 4d07886ec8952d95ce595a468313fdcbc50fa485 Mon Sep 17 00:00:00 2001 From: Ske Date: Thu, 26 Dec 2019 20:39:47 +0100 Subject: [PATCH] Add server-specific display names --- PluralKit.Bot/Commands/CommandTree.cs | 11 +++-- PluralKit.Bot/Commands/MemberCommands.cs | 51 ++++++++++++++++++++---- PluralKit.Bot/Commands/SystemCommands.cs | 2 +- PluralKit.Bot/Errors.cs | 2 +- PluralKit.Bot/Services/EmbedService.cs | 13 ++++-- PluralKit.Bot/Services/ProxyService.cs | 6 ++- PluralKit.Core/Models.cs | 6 +-- PluralKit.Core/Stores.cs | 43 ++++++++++++++++++-- PluralKit.Core/db_schema.sql | 10 +++++ 9 files changed, 120 insertions(+), 24 deletions(-) diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 8b138746..3acee756 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -33,6 +33,7 @@ namespace PluralKit.Bot.Commands public static Command MemberDelete = new Command("member delete", "member delete", "Deletes a member"); public static Command MemberAvatar = new Command("member avatar", "member avatar [url|@mention]", "Changes a member's avatar"); public static Command MemberDisplayName = new Command("member displayname", "member displayname [display name]", "Changes a member's display name"); + public static Command MemberServerName = new Command("member servername", "member servername [server name]", "Changes a member's display name in the current server"); public static Command MemberKeepProxy = new Command("member keepproxy", "member keepproxy [on|off]", "Sets whether to include a member's proxy tags when proxying"); public static Command Switch = new Command("switch", "switch [member 2] [member 3...]", "Registers a switch"); public static Command SwitchOut = new Command("switch out", "switch out", "Registers a switch with no members"); @@ -60,8 +61,8 @@ namespace PluralKit.Bot.Commands }; public static Command[] MemberCommands = { - MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberDesc, MemberPronouns, MemberColor, - MemberBirthday, MemberProxy, MemberKeepProxy, MemberDelete, MemberAvatar, + MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns, + MemberColor, MemberBirthday, MemberProxy, MemberKeepProxy, MemberDelete, MemberAvatar, }; public static Command[] SwitchCommands = {Switch, SwitchOut, SwitchMove, SwitchDelete}; @@ -235,7 +236,7 @@ namespace PluralKit.Bot.Commands else if (await ctx.MatchMember() is PKMember target) await HandleMemberCommandTargeted(ctx, target); else if (!ctx.HasNext()) - await PrintCommandExpectedError(ctx, MemberNew, MemberInfo, MemberRename, MemberDisplayName, MemberDesc, MemberPronouns, + await PrintCommandExpectedError(ctx, MemberNew, MemberInfo, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar); else await ctx.Reply($"{Emojis.Error} {ctx.CreateMemberNotFoundError(ctx.PopArgument())}"); @@ -262,12 +263,14 @@ namespace PluralKit.Bot.Commands await ctx.Execute(MemberAvatar, m => m.MemberAvatar(ctx, target)); else if (ctx.Match("displayname", "dn", "dname", "nick", "nickname")) await ctx.Execute(MemberDisplayName, m => m.MemberDisplayName(ctx, target)); + else if (ctx.Match("servername", "sn", "sname", "snick", "snickname", "servernick", "servernickname", "serverdisplayname", "guildname", "guildnick", "guildnickname")) + await ctx.Execute(MemberServerName, m => m.MemberServerName(ctx, target)); else if (ctx.Match("keepproxy", "keeptags", "showtags")) await ctx.Execute(MemberKeepProxy, m => m.MemberKeepProxy(ctx, target)); else if (!ctx.HasNext()) // Bare command await ctx.Execute(MemberInfo, m => m.ViewMember(ctx, target)); else - await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList); + await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberServerName ,MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList); } private async Task HandleSwitchCommand(Context ctx) diff --git a/PluralKit.Bot/Commands/MemberCommands.cs b/PluralKit.Bot/Commands/MemberCommands.cs index 9187df69..57156cd2 100644 --- a/PluralKit.Bot/Commands/MemberCommands.cs +++ b/PluralKit.Bot/Commands/MemberCommands.cs @@ -82,7 +82,14 @@ namespace PluralKit.Bot.Commands await ctx.Reply($"{Emojis.Success} Member renamed."); if (newName.Contains(" ")) await ctx.Reply($"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it."); if (target.DisplayName != null) await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName.SanitizeMentions()}), and will be proxied using that name instead."); - + + if (ctx.Guild != null) + { + var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id); + if (memberGuildConfig.DisplayName != null) + await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName.SanitizeMentions()}) in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name here."); + } + await _proxyCache.InvalidateResultsForSystem(ctx.System); } @@ -322,14 +329,44 @@ namespace PluralKit.Bot.Commands var successStr = $"{Emojis.Success} "; if (newDisplayName != null) - { - successStr += - $"Member display name changed. This member will now be proxied using the name \"{newDisplayName.SanitizeMentions()}\"."; - } + successStr += $"Member display name changed. This member will now be proxied using the name \"{newDisplayName.SanitizeMentions()}\"."; else - { successStr += $"Member display name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\"."; + + if (ctx.Guild != null) + { + var memberGuildConfig = await _data.GetMemberGuildSettings(target, ctx.Guild.Id); + if (memberGuildConfig.DisplayName != null) + successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name, \"{memberGuildConfig.DisplayName.SanitizeMentions()}\", here."; } + + await ctx.Reply(successStr); + + await _proxyCache.InvalidateResultsForSystem(ctx.System); + } + + public async Task MemberServerName(Context ctx, PKMember target) + { + if (ctx.System == null) throw Errors.NoSystemError; + if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError; + + // TODO: allow setting server names for different servers/in DMs by ID + ctx.CheckGuildContext(); + + var newServerName = ctx.RemainderOrNull(); + + var guildSettings = await _data.GetMemberGuildSettings(target, ctx.Guild.Id); + guildSettings.DisplayName = newServerName; + await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildSettings); + + var successStr = $"{Emojis.Success} "; + if (newServerName != null) + successStr += $"Member server name changed. This member will now be proxied using the name \"{newServerName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()})."; + else if (target.DisplayName != null) + successStr += $"Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()})."; + else + successStr += $"Member server name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()})."; + await ctx.Reply(successStr); await _proxyCache.InvalidateResultsForSystem(ctx.System); @@ -359,7 +396,7 @@ namespace PluralKit.Bot.Commands public async Task ViewMember(Context ctx, PKMember target) { var system = await _data.GetSystemById(target.System); - await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target)); + await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild)); } } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/SystemCommands.cs b/PluralKit.Bot/Commands/SystemCommands.cs index 95ce1f51..07b4b511 100644 --- a/PluralKit.Bot/Commands/SystemCommands.cs +++ b/PluralKit.Bot/Commands/SystemCommands.cs @@ -210,7 +210,7 @@ namespace PluralKit.Bot.Commands else newValue = !gs.ProxyEnabled; gs.ProxyEnabled = newValue; - await _data.SetGuildSystemSettings(ctx.System, ctx.Guild.Id, gs); + await _data.SetSystemGuildSettings(ctx.System, ctx.Guild.Id, gs); if (newValue) await ctx.Reply($"Message proxying in this server ({ctx.Guild.Name.EscapeMarkdown()}) is now **enabled** for your system."); diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index 7616b3b0..6355056c 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -80,7 +80,7 @@ namespace PluralKit.Bot { public static PKError DisplayNameTooLong(string displayName, int maxLength) => new PKError( $"Display name too long ({displayName.Length} > {maxLength} characters). Use a shorter display name, or shorten your system tag."); public static PKError ProxyNameTooShort(string name) => new PKError($"The webhook's name, `{name.SanitizeMentions()}`, is shorter than two characters, and thus cannot be proxied. Please change the member name or use a longer system tag."); - public static PKError ProxyNameTooLong(string name) => new PKError($"The webhook's name, {name.SanitizeMentions()}, is too long ({name.Length} > {Limits.MaxProxyNameLength} characters), and thus cannot be proxied. Please change the member name or use a shorter system tag."); + public static PKError ProxyNameTooLong(string name) => new PKError($"The webhook's name, {name.SanitizeMentions()}, is too long ({name.Length} > {Limits.MaxProxyNameLength} characters), and thus cannot be proxied. Please change the member name, display name or server display name, or use a shorter system tag."); public static PKError ProxyTagAlreadyExists(ProxyTag tagToAdd, PKMember member) => new PKError($"That member already has the proxy tag `{tagToAdd.ProxyString.SanitizeMentions()}`. The member currently has these tags: {member.ProxyTagsString().SanitizeMentions()}"); public static PKError ProxyTagDoesNotExist(ProxyTag tagToRemove, PKMember member) => new PKError($"That member does not have the proxy tag `{tagToRemove.ProxyString.SanitizeMentions()}`. The member currently has these tags: {member.ProxyTagsString().SanitizeMentions()}"); diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 5ff52f57..5f55d4aa 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -62,7 +62,7 @@ namespace PluralKit.Bot { .Build(); } - public async Task CreateMemberEmbed(PKSystem system, PKMember member) + public async Task CreateMemberEmbed(PKSystem system, PKMember member, IGuild guild) { var name = member.Name; if (system.Name != null) name = $"{member.Name} ({system.Name})"; @@ -82,6 +82,10 @@ namespace PluralKit.Bot { var messageCount = await _data.GetMemberMessageCount(member); + string guildDisplayName = null; + if (guild != null) + guildDisplayName = (await _data.GetMemberGuildSettings(member, guild.Id)).DisplayName; + var proxyTagsStr = string.Join('\n', member.ProxyTags.Select(t => $"`{t.ProxyString}`")); var eb = new EmbedBuilder() @@ -92,11 +96,12 @@ namespace PluralKit.Bot { if (member.AvatarUrl != null) eb.WithThumbnailUrl(member.AvatarUrl); - if (member.DisplayName != null) eb.AddField("Display Name", member.DisplayName, true); + if (member.DisplayName != null) eb.AddField("Display Name", member.DisplayName.Truncate(1024), true); + if (guild != null && guildDisplayName != null) eb.AddField($"Server Nickname (for {guild.Name})", guildDisplayName.Truncate(1024), true); if (member.Birthday != null) eb.AddField("Birthdate", member.BirthdayString, true); - if (member.Pronouns != null) eb.AddField("Pronouns", member.Pronouns, true); + if (member.Pronouns != null) eb.AddField("Pronouns", member.Pronouns.Truncate(1024), true); if (messageCount > 0) eb.AddField("Message Count", messageCount, true); - if (member.HasProxyTags) eb.AddField("Proxy Tags", string.Join('\n', proxyTagsStr), true); + if (member.HasProxyTags) eb.AddField("Proxy Tags", string.Join('\n', proxyTagsStr).Truncate(1024), true); if (member.Color != null) eb.AddField("Color", $"#{member.Color}", true); if (member.Description != null) eb.AddField("Description", member.Description, false); diff --git a/PluralKit.Bot/Services/ProxyService.cs b/PluralKit.Bot/Services/ProxyService.cs index face01fd..76a18c38 100644 --- a/PluralKit.Bot/Services/ProxyService.cs +++ b/PluralKit.Bot/Services/ProxyService.cs @@ -99,6 +99,10 @@ namespace PluralKit.Bot // Make sure the system hasn't blacklisted the guild either var systemGuildCfg = await _data.GetSystemGuildSettings(match.System, channel.GuildId); if (!systemGuildCfg.ProxyEnabled) return; + + // Also, check if the member has a guild nickname + // TODO: roll this into the cached results as well as system/guild settings, maybe? Add a separate cache or something. + var memberGuildCfg = await _data.GetMemberGuildSettings(match.Member, channel.GuildId); // We know message.Channel can only be ITextChannel as PK doesn't work in DMs/groups // Afterwards we ensure the bot has the right permissions, otherwise bail early @@ -109,7 +113,7 @@ namespace PluralKit.Bot return; // Get variables in order and all - var proxyName = match.Member.ProxyName(match.System.Tag); + var proxyName = match.Member.ProxyName(match.System.Tag, memberGuildCfg.DisplayName); var avatarUrl = match.Member.AvatarUrl ?? match.System.AvatarUrl; // If the name's too long (or short), bail diff --git a/PluralKit.Core/Models.cs b/PluralKit.Core/Models.cs index 181d4ac2..e933cd05 100644 --- a/PluralKit.Core/Models.cs +++ b/PluralKit.Core/Models.cs @@ -110,10 +110,10 @@ namespace PluralKit } [JsonIgnore] public bool HasProxyTags => ProxyTags.Count > 0; - public string ProxyName(string systemTag) + public string ProxyName(string systemTag, string guildDisplayName) { - if (systemTag == null) return DisplayName ?? Name; - return $"{DisplayName ?? Name} {systemTag}"; + if (systemTag == null) return guildDisplayName ?? DisplayName ?? Name; + return $"{guildDisplayName ?? DisplayName ?? Name} {systemTag}"; } public void ToJson(Utf8JsonWriter w) diff --git a/PluralKit.Core/Stores.cs b/PluralKit.Core/Stores.cs index 4329f687..cd863e17 100644 --- a/PluralKit.Core/Stores.cs +++ b/PluralKit.Core/Stores.cs @@ -69,6 +69,11 @@ namespace PluralKit { public bool ProxyEnabled { get; set; } = true; } + public class MemberGuildSettings + { + public string DisplayName { get; set; } + } + public interface IDataStore { /// @@ -116,8 +121,15 @@ namespace PluralKit { /// The system to check in. Task> GetConflictingProxies(PKSystem system, ProxyTag tag); + /// + /// Gets a specific system's guild-specific settings for a given guild. + /// Task GetSystemGuildSettings(PKSystem system, ulong guild); - Task SetGuildSystemSettings(PKSystem system, ulong guild, SystemGuildSettings settings); + + /// + /// Saves a specific system's guild-specific settings. + /// + Task SetSystemGuildSettings(PKSystem system, ulong guild, SystemGuildSettings settings); /// /// Creates a system, auto-generating its corresponding IDs. @@ -225,6 +237,16 @@ namespace PluralKit { /// Task DeleteMember(PKMember member); + /// + /// Gets a specific member's guild-specific settings for a given guild. + /// + Task GetMemberGuildSettings(PKMember member, ulong guild); + + /// + /// Saves a specific member's guild-specific settings. + /// + Task SetMemberGuildSettings(PKMember member, ulong guild, MemberGuildSettings settings); + /// /// Gets a message and its information by its ID. /// @@ -392,8 +414,7 @@ namespace PluralKit { "select * from system_guild where system = @System and guild = @Guild", new {System = system.Id, Guild = guild}) ?? new SystemGuildSettings(); } - - public async Task SetGuildSystemSettings(PKSystem system, ulong guild, SystemGuildSettings settings) + public async Task SetSystemGuildSettings(PKSystem system, ulong guild, SystemGuildSettings settings) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("insert into system_guild (system, guild, proxy_enabled) values (@System, @Guild, @ProxyEnabled) on conflict (system, guild) do update set proxy_enabled = @ProxyEnabled", new @@ -572,6 +593,22 @@ namespace PluralKit { _logger.Information("Deleted member {@Member}", member); } + public async Task GetMemberGuildSettings(PKMember member, ulong guild) + { + using var conn = await _conn.Obtain(); + return await conn.QuerySingleOrDefaultAsync( + "select * from member_guild where member = @Member and guild = @Guild", new { Member = member.Id, Guild = guild}) + ?? new MemberGuildSettings(); + } + + public async Task SetMemberGuildSettings(PKMember member, ulong guild, MemberGuildSettings settings) + { + using var conn = await _conn.Obtain(); + await conn.ExecuteAsync( + "insert into member_guild (member, guild, display_name) values (@Member, @Guild, @DisplayName) on conflict (member, guild) do update set display_name = @Displayname", + new {Member = member.Id, Guild = guild, DisplayName = settings.DisplayName}); + } + public async Task GetMemberMessageCount(PKMember member) { using (var conn = await _conn.Obtain()) diff --git a/PluralKit.Core/db_schema.sql b/PluralKit.Core/db_schema.sql index 95b7b12d..e5d3f241 100644 --- a/PluralKit.Core/db_schema.sql +++ b/PluralKit.Core/db_schema.sql @@ -47,6 +47,16 @@ create table if not exists members created timestamp not null default (current_timestamp at time zone 'utc') ); +create table if not exists member_guild +( + member serial not null references members (id) on delete cascade, + guild bigint not null, + + display_name text default null, + + primary key (member, guild) +); + create table if not exists accounts ( uid bigint primary key,