diff --git a/PluralKit.Bot/Commands/MemberCommands.cs b/PluralKit.Bot/Commands/MemberCommands.cs index e7f38c5c..fa16ee93 100644 --- a/PluralKit.Bot/Commands/MemberCommands.cs +++ b/PluralKit.Bot/Commands/MemberCommands.cs @@ -29,7 +29,7 @@ namespace PluralKit.Bot.Commands // Warn if member name will be unproxyable (with/without tag) if (memberName.Length > Context.SenderSystem.MaxMemberNameLength) { - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} Member name too long ({memberName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to create it anyway? (You can change the name later)"); + var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} Member name too long ({memberName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to create it anyway? (You can change the name later, or set a member display name)"); if (!await Context.PromptYesNo(msg)) throw new PKError("Member creation cancelled."); } @@ -58,9 +58,9 @@ namespace PluralKit.Bot.Commands // Hard name length cap if (newName.Length > Limits.MaxMemberNameLength) throw Errors.MemberNameTooLongError(newName.Length); - // Warn if member name will be unproxyable (with/without tag) - if (newName.Length > Context.SenderSystem.MaxMemberNameLength) { - var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} New member name too long ({newName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to change it anyway?"); + // Warn if member name will be unproxyable (with/without tag), only if member doesn't have a display name + if (ContextEntity.DisplayName == null && newName.Length > Context.SenderSystem.MaxMemberNameLength) { + var msg = await Context.Channel.SendMessageAsync($"{Emojis.Warn} New member name too long ({newName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to change it anyway? (You can set a member display name instead)"); if (!await Context.PromptYesNo(msg)) throw new PKError("Member renaming cancelled."); } @@ -77,6 +77,7 @@ namespace PluralKit.Bot.Commands await Context.Channel.SendMessageAsync($"{Emojis.Success} Member renamed."); if (newName.Contains(" ")) await Context.Channel.SendMessageAsync($"{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 (ContextEntity.DisplayName != null) await Context.Channel.SendMessageAsync($"{Emojis.Note} Note that this member has a display name set (`{ContextEntity.DisplayName}`), and will be proxied using that name instead."); } [Command("description")] @@ -213,6 +214,39 @@ namespace PluralKit.Bot.Commands var embed = url != null ? new EmbedBuilder().WithImageUrl(url).Build() : null; await Context.Channel.SendMessageAsync($"{Emojis.Success} Member avatar {(url == null ? "cleared" : "changed")}.", embed: embed); } + + [Command("displayname")] + [Alias("nick", "nickname", "displayname")] + [Remarks("member displayname ")] + [MustPassOwnMember] + public async Task MemberDisplayName([Remainder] string newDisplayName = null) + { + // Refuse if proxy name will be unproxyable (with/without tag) + if (newDisplayName != null && newDisplayName.Length > Context.SenderSystem.MaxMemberNameLength) + throw Errors.DisplayNameTooLong(newDisplayName, Context.SenderSystem.MaxMemberNameLength); + + ContextEntity.DisplayName = newDisplayName; + await Members.Save(ContextEntity); + + var successStr = $"{Emojis.Success} "; + if (newDisplayName != null) + { + successStr += + $"Member display name changed. This member will now be proxied using the name `{newDisplayName}`."; + } + else + { + successStr += $"Member display name cleared. "; + + // If we're removing display name and the *real* name will be unproxyable, warn. + if (ContextEntity.Name.Length > Context.SenderSystem.MaxMemberNameLength) + successStr += + $" {Emojis.Warn} This member's actual name is too long ({ContextEntity.Name.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), and thus cannot be proxied."; + else + successStr += $"This member will now be proxied using their member name `{ContextEntity.Name}."; + } + await Context.Channel.SendMessageAsync(successStr); + } [Command] [Alias("view", "show", "info")] diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index 6a258061..80531ce0 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -73,5 +73,8 @@ namespace PluralKit.Bot { public static PKError FrontPercentTimeInFuture => new PKError("Cannot get the front percent between now and a time in the future."); public static PKError GuildNotFound(ulong guildId) => new PKError($"Guild with ID {guildId} not found."); + + 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."); } } \ No newline at end of file diff --git a/PluralKit.Bot/Services/ProxyService.cs b/PluralKit.Bot/Services/ProxyService.cs index 076f71b8..c65d6662 100644 --- a/PluralKit.Bot/Services/ProxyService.cs +++ b/PluralKit.Bot/Services/ProxyService.cs @@ -24,8 +24,6 @@ namespace PluralKit.Bot public PKMember Member; public PKSystem System; public string InnerText; - - public string ProxyName => Member.Name + (System.Tag != null ? " " + System.Tag : ""); } class ProxyService: IDisposable { @@ -118,7 +116,8 @@ namespace PluralKit.Bot // Fetch a webhook for this channel, and send the proxied message var webhook = await _webhookCache.GetWebhook(message.Channel as ITextChannel); var avatarUrl = match.Member.AvatarUrl ?? match.System.AvatarUrl; - var hookMessageId = await ExecuteWebhook(webhook, messageContents, match.ProxyName, avatarUrl, message.Attachments.FirstOrDefault()); + var proxyName = match.Member.ProxyName(match.System.Tag); + var hookMessageId = await ExecuteWebhook(webhook, messageContents, proxyName, avatarUrl, message.Attachments.FirstOrDefault()); // Store the message in the database, and log it in the log channel (if applicable) await _messageStorage.Store(message.Author.Id, hookMessageId, message.Channel.Id, message.Id, match.Member); diff --git a/PluralKit.Core/Models.cs b/PluralKit.Core/Models.cs index 347878df..64295058 100644 --- a/PluralKit.Core/Models.cs +++ b/PluralKit.Core/Models.cs @@ -7,6 +7,7 @@ namespace PluralKit { public class PKSystem { + // Additions here should be mirrored in SystemStore::Save [Key] [JsonIgnore] public int Id { get; set; } [JsonProperty("id")] public string Hid { get; set; } [JsonProperty("name")] public string Name { get; set; } @@ -24,12 +25,14 @@ namespace PluralKit public class PKMember { + // Additions here should be mirrored in MemberStore::Save [JsonIgnore] public int Id { get; set; } [JsonProperty("id")] public string Hid { get; set; } [JsonIgnore] public int System { get; set; } [JsonProperty("color")] public string Color { get; set; } [JsonProperty("avatar_url")] public string AvatarUrl { get; set; } [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("display_name")] public string DisplayName { get; set; } [JsonProperty("birthday")] public LocalDate? Birthday { get; set; } [JsonProperty("pronouns")] public string Pronouns { get; set; } [JsonProperty("description")] public string Description { get; set; } @@ -52,6 +55,12 @@ namespace PluralKit [JsonIgnore] public bool HasProxyTags => Prefix != null || Suffix != null; [JsonIgnore] public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}"; + + public string ProxyName(string systemTag) + { + if (systemTag == null) return DisplayName ?? Name; + return $"{DisplayName ?? Name} {systemTag}"; + } } public class PKSwitch diff --git a/PluralKit.Core/Stores.cs b/PluralKit.Core/Stores.cs index 4c63948a..ba4d32c9 100644 --- a/PluralKit.Core/Stores.cs +++ b/PluralKit.Core/Stores.cs @@ -150,7 +150,7 @@ namespace PluralKit { public async Task Save(PKMember member) { using (var conn = await _conn.Obtain()) - await conn.ExecuteAsync("update members set name = @Name, description = @Description, color = @Color, avatar_url = @AvatarUrl, birthday = @Birthday, pronouns = @Pronouns, prefix = @Prefix, suffix = @Suffix where id = @Id", member); + await conn.ExecuteAsync("update members set name = @Name, display_name = @DisplayName, description = @Description, color = @Color, avatar_url = @AvatarUrl, birthday = @Birthday, pronouns = @Pronouns, prefix = @Prefix, suffix = @Suffix where id = @Id", member); _logger.Information("Updated member {@Member}", member); } diff --git a/PluralKit.Core/Utils.cs b/PluralKit.Core/Utils.cs index a9609d15..176a50f9 100644 --- a/PluralKit.Core/Utils.cs +++ b/PluralKit.Core/Utils.cs @@ -242,7 +242,7 @@ namespace PluralKit public static readonly string Warn = "\u26A0"; public static readonly string Success = "\u2705"; public static readonly string Error = "\u274C"; - public static readonly string Note = "\u2757"; + public static readonly string Note = "\U0001f4dd"; public static readonly string ThumbsUp = "\U0001f44d"; public static readonly string RedQuestion = "\u2753"; } diff --git a/PluralKit.Core/db_schema.sql b/PluralKit.Core/db_schema.sql index 97063f50..3fe1848d 100644 --- a/PluralKit.Core/db_schema.sql +++ b/PluralKit.Core/db_schema.sql @@ -13,18 +13,19 @@ create table if not exists systems create table if not exists members ( - id serial primary key, - hid char(5) unique not null, - system serial not null references systems (id) on delete cascade, - color char(6), - avatar_url text, - name text not null, - birthday date, - pronouns text, - description text, - prefix text, - suffix text, - created timestamp not null default (current_timestamp at time zone 'utc') + id serial primary key, + hid char(5) unique not null, + system serial not null references systems (id) on delete cascade, + color char(6), + avatar_url text, + name text not null, + display_name text, + birthday date, + pronouns text, + description text, + prefix text, + suffix text, + created timestamp not null default (current_timestamp at time zone 'utc') ); create table if not exists accounts