From 393ee16c1b64b911f8ab414ab7c882547576b85d Mon Sep 17 00:00:00 2001 From: Ske Date: Mon, 28 Oct 2019 20:15:27 +0100 Subject: [PATCH] Add support for multiple proxy tags Tangentially closes #103. --- PluralKit.API/Controllers/MemberController.cs | 23 ++--- PluralKit.Bot/Commands/CommandTree.cs | 2 +- PluralKit.Bot/Commands/MemberCommands.cs | 87 ++++++++++++++----- PluralKit.Bot/Commands/SystemCommands.cs | 4 +- PluralKit.Bot/Errors.cs | 6 ++ PluralKit.Bot/Services/EmbedService.cs | 4 +- PluralKit.Bot/Services/ProxyService.cs | 16 ++-- PluralKit.Bot/Utils.cs | 2 + PluralKit.Core/DataFiles.cs | 16 ++-- PluralKit.Core/Models.cs | 55 ++++++++++-- PluralKit.Core/Stores.cs | 2 +- PluralKit.Core/Utils.cs | 3 + PluralKit.Core/db_schema.sql | 12 ++- docs/1-user-guide.md | 13 +++ docs/2-command-list.md | 4 +- docs/3-api-documentation.md | 10 ++- 16 files changed, 190 insertions(+), 69 deletions(-) diff --git a/PluralKit.API/Controllers/MemberController.cs b/PluralKit.API/Controllers/MemberController.cs index f5a82fba..36784db4 100644 --- a/PluralKit.API/Controllers/MemberController.cs +++ b/PluralKit.API/Controllers/MemberController.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PluralKit.Core; @@ -10,13 +11,11 @@ namespace PluralKit.API.Controllers public class MemberController: ControllerBase { private IDataStore _data; - private DbConnectionFactory _conn; private TokenAuthService _auth; - public MemberController(IDataStore data, DbConnectionFactory conn, TokenAuthService auth) + public MemberController(IDataStore data, TokenAuthService auth) { _data = data; - _conn = conn; _auth = auth; } @@ -36,7 +35,7 @@ namespace PluralKit.API.Controllers var system = _auth.CurrentSystem; if (newMember.Name == null) - return BadRequest("Member name cannot be null."); + return BadRequest("Member name cannot be null."); // Enforce per-system member limit var memberCount = await _data.GetSystemMemberCount(system); @@ -56,9 +55,7 @@ namespace PluralKit.API.Controllers // Sanity bounds checks if (newMember.AvatarUrl != null && newMember.AvatarUrl.Length > 1000) return BadRequest(); - if (newMember.Prefix != null && newMember.Prefix.Length > 1000) - return BadRequest(); - if (newMember.Suffix != null && newMember.Suffix.Length > 1000) + if (newMember.ProxyTags?.Any(tag => tag.Prefix.Length > 1000 || tag.Suffix.Length > 1000) ?? false) return BadRequest(); var member = await _data.CreateMember(system, newMember.Name); @@ -70,8 +67,7 @@ namespace PluralKit.API.Controllers member.Birthday = newMember.Birthday; member.Pronouns = newMember.Pronouns; member.Description = newMember.Description; - member.Prefix = newMember.Prefix; - member.Suffix = newMember.Suffix; + member.ProxyTags = newMember.ProxyTags; await _data.SaveMember(member); return Ok(member); @@ -100,11 +96,7 @@ namespace PluralKit.API.Controllers return BadRequest($"Member descriptions too long ({newMember.Description.Length} > {Limits.MaxDescriptionLength}."); // Sanity bounds checks - if (newMember.AvatarUrl != null && newMember.AvatarUrl.Length > 1000) - return BadRequest(); - if (newMember.Prefix != null && newMember.Prefix.Length > 1000) - return BadRequest(); - if (newMember.Suffix != null && newMember.Suffix.Length > 1000) + if (newMember.ProxyTags?.Any(tag => tag.Prefix.Length > 1000 || tag.Suffix.Length > 1000) ?? false) return BadRequest(); member.Name = newMember.Name; @@ -114,8 +106,7 @@ namespace PluralKit.API.Controllers member.Birthday = newMember.Birthday; member.Pronouns = newMember.Pronouns; member.Description = newMember.Description; - member.Prefix = newMember.Prefix; - member.Suffix = newMember.Suffix; + member.ProxyTags = newMember.ProxyTags; await _data.SaveMember(member); return Ok(member); diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 86497425..b04a4176 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -28,7 +28,7 @@ namespace PluralKit.Bot.Commands public static Command MemberPronouns = new Command("member pronouns", "member pronouns [pronouns]", "uwu"); public static Command MemberColor = new Command("member color", "member color [color]", "uwu"); public static Command MemberBirthday = new Command("member birthday", "member birthday [birthday]", "uwu"); - public static Command MemberProxy = new Command("member proxy", "member proxy [example proxy]", "uwu"); + public static Command MemberProxy = new Command("member proxy", "member proxy [add|remove] [example proxy]", "uwu"); public static Command MemberDelete = new Command("member delete", "member delete", "uwu"); public static Command MemberAvatar = new Command("member avatar", "member avatar [url|@mention]", "uwu"); public static Command MemberDisplayName = new Command("member displayname", "member displayname [display name]", "uwu"); diff --git a/PluralKit.Bot/Commands/MemberCommands.cs b/PluralKit.Bot/Commands/MemberCommands.cs index 09a5da8c..cb4c7dba 100644 --- a/PluralKit.Bot/Commands/MemberCommands.cs +++ b/PluralKit.Bot/Commands/MemberCommands.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -153,32 +154,74 @@ namespace PluralKit.Bot.Commands { if (ctx.System == null) throw Errors.NoSystemError; if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError; - - // Handling the clear case in an if here to keep the body dedented - var exampleProxy = ctx.RemainderOrNull(); - if (exampleProxy == null) + + ProxyTag ParseProxyTags(string exampleProxy) { - // Just reset and send OK message - target.Prefix = null; - target.Suffix = null; - await _data.SaveMember(target); - await ctx.Reply($"{Emojis.Success} Member proxy tags cleared."); - - await _proxyCache.InvalidateResultsForSystem(ctx.System); - return; + // // Make sure there's one and only one instance of "text" in the example proxy given + var prefixAndSuffix = exampleProxy.Split("text"); + if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText; + if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText; + return new ProxyTag(prefixAndSuffix[0], prefixAndSuffix[1]); } - // Make sure there's one and only one instance of "text" in the example proxy given - var prefixAndSuffix = exampleProxy.Split("text"); - if (prefixAndSuffix.Length < 2) throw Errors.ProxyMustHaveText; - if (prefixAndSuffix.Length > 2) throw Errors.ProxyMultipleText; + // "Sub"command: no arguments clearing + if (!ctx.HasNext()) + { + // If we already have multiple tags, this would clear everything, so prompt that + var msg = await ctx.Reply( + $"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?"); + if (!await ctx.PromptYesNo(msg)) + throw Errors.GenericCancelled(); + + target.ProxyTags = new ProxyTag[] { }; + + await _data.SaveMember(target); + await ctx.Reply($"{Emojis.Success} Proxy tags cleared."); + } + // Subcommand: "add" + else if (ctx.Match("add")) + { + var tagToAdd = ParseProxyTags(ctx.RemainderOrNull()); + if (target.ProxyTags.Contains(tagToAdd)) + throw Errors.ProxyTagAlreadyExists(tagToAdd, target); + + // It's not guaranteed the list's mutable, so we force it to be + target.ProxyTags = target.ProxyTags.ToList(); + target.ProxyTags.Add(tagToAdd); + + await _data.SaveMember(target); + await ctx.Reply($"{Emojis.Success} Added proxy tags `{tagToAdd.ProxyString.SanitizeMentions()}`."); + } + // Subcommand: "remove" + else if (ctx.Match("remove")) + { + var tagToRemove = ParseProxyTags(ctx.RemainderOrNull()); + if (!target.ProxyTags.Contains(tagToRemove)) + throw Errors.ProxyTagDoesNotExist(tagToRemove, target); + + // It's not guaranteed the list's mutable, so we force it to be + target.ProxyTags = target.ProxyTags.ToList(); + target.ProxyTags.Remove(tagToRemove); + + await _data.SaveMember(target); + await ctx.Reply($"{Emojis.Success} Removed proxy tags `{tagToRemove.ProxyString.SanitizeMentions()}`."); + } + // Subcommand: bare proxy tag given + else + { + var requestedTag = ParseProxyTags(ctx.RemainderOrNull()); + + // This is mostly a legacy command, so it's gonna error out if there's + // already more than one proxy tag. + if (target.ProxyTags.Count > 1) + throw Errors.LegacyAlreadyHasProxyTag(requestedTag, target); + + target.ProxyTags = new[] {requestedTag}; + + await _data.SaveMember(target); + await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString.SanitizeMentions()}`."); + } - // If the prefix/suffix is empty, use "null" instead (for DB) - target.Prefix = prefixAndSuffix[0].Length > 0 ? prefixAndSuffix[0] : null; - target.Suffix = prefixAndSuffix[1].Length > 0 ? prefixAndSuffix[1] : null; - await _data.SaveMember(target); - await ctx.Reply($"{Emojis.Success} Member proxy tags changed to `{target.ProxyString.SanitizeMentions()}`. Try proxying now!"); - await _proxyCache.InvalidateResultsForSystem(ctx.System); } diff --git a/PluralKit.Bot/Commands/SystemCommands.cs b/PluralKit.Bot/Commands/SystemCommands.cs index c49f0b78..ce94e314 100644 --- a/PluralKit.Bot/Commands/SystemCommands.cs +++ b/PluralKit.Bot/Commands/SystemCommands.cs @@ -134,7 +134,7 @@ namespace PluralKit.Bot.Commands 25, embedTitle, (eb, ms) => eb.Description = string.Join("\n", ms.Select((m) => { - if (m.HasProxyTags) return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}** *({m.ProxyString.SanitizeMentions()})*"; + if (m.HasProxyTags) return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}** *({m.ProxyTagsString().SanitizeMentions()})*"; return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}**"; })) ); @@ -154,7 +154,7 @@ namespace PluralKit.Bot.Commands var profile = $"**ID**: {m.Hid}"; if (m.Pronouns != null) profile += $"\n**Pronouns**: {m.Pronouns}"; if (m.Birthday != null) profile += $"\n**Birthdate**: {m.BirthdayString}"; - if (m.Prefix != null || m.Suffix != null) profile += $"\n**Proxy tags**: {m.ProxyString}"; + if (m.ProxyTags.Count > 0) profile += $"\n**Proxy tags:** {m.ProxyTagsString()}"; if (m.Description != null) profile += $"\n\n{m.Description}"; eb.AddField(m.Name, profile.Truncate(1024)); } diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index 74760569..66f44bf9 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -80,5 +80,11 @@ namespace PluralKit.Bot { $"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 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()}"); + public static PKError LegacyAlreadyHasProxyTag(ProxyTag requested, PKMember member) => new PKError($"This member already has more than one proxy tag set: {member.ProxyTagsString().SanitizeMentions()}\nConsider using the `pk;member {member.Hid} proxy add {requested.ProxyString.SanitizeMentions()}` command instead."); + + public static PKError GenericCancelled() => new PKError("Operation cancelled."); } } \ No newline at end of file diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index f7ef5aec..d9c9694a 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -82,6 +82,8 @@ namespace PluralKit.Bot { var messageCount = await _data.GetMemberMessageCount(member); + var proxyTagsStr = string.Join('\n', member.ProxyTags.Select(t => $"`{t.ProxyString}`")); + var eb = new EmbedBuilder() // TODO: add URL of website when that's up .WithAuthor(name, member.AvatarUrl) @@ -94,7 +96,7 @@ namespace PluralKit.Bot { if (member.Birthday != null) eb.AddField("Birthdate", member.BirthdayString, true); if (member.Pronouns != null) eb.AddField("Pronouns", member.Pronouns, true); if (messageCount > 0) eb.AddField("Message Count", messageCount, true); - if (member.HasProxyTags) eb.AddField("Proxy Tags", $"{member.Prefix.EscapeMarkdown()}text{member.Suffix.EscapeMarkdown()}", true); + if (member.HasProxyTags) eb.AddField("Proxy Tags", string.Join('\n', proxyTagsStr), 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 470e8a6b..757a84e7 100644 --- a/PluralKit.Bot/Services/ProxyService.cs +++ b/PluralKit.Bot/Services/ProxyService.cs @@ -44,7 +44,7 @@ namespace PluralKit.Bot _httpClient = new HttpClient(); } - private ProxyMatch GetProxyTagMatch(string message, IEnumerable potentials) + private ProxyMatch GetProxyTagMatch(string message, IEnumerable potentialMembers) { // If the message starts with a @mention, and then proceeds to have proxy tags, // extract the mention and place it inside the inner message @@ -57,19 +57,19 @@ namespace PluralKit.Bot message = message.Substring(matchStartPosition); } - // Sort by specificity (ProxyString length desc = prefix+suffix length desc = inner message asc = more specific proxy first!) - var ordered = potentials.OrderByDescending(p => p.Member.ProxyString.Length); - foreach (var potential in ordered) + // Flatten and sort by specificity (ProxyString length desc = prefix+suffix length desc = inner message asc = more specific proxy first!) + var ordered = potentialMembers.SelectMany(m => m.Member.ProxyTags.Select(tag => (tag, m))).OrderByDescending(p => p.Item1.ProxyString); + foreach (var (tag, match) in ordered) { - if (potential.Member.Prefix == null && potential.Member.Suffix == null) continue; + if (tag.Prefix == null && tag.Suffix == null) continue; - var prefix = potential.Member.Prefix ?? ""; - var suffix = potential.Member.Suffix ?? ""; + var prefix = tag.Prefix ?? ""; + var suffix = tag.Suffix ?? ""; if (message.Length >= prefix.Length + suffix.Length && message.StartsWith(prefix) && message.EndsWith(suffix)) { var inner = message.Substring(prefix.Length, message.Length - prefix.Length - suffix.Length); if (leadingMention != null) inner = $"{leadingMention} {inner}"; - return new ProxyMatch { Member = potential.Member, System = potential.System, InnerText = inner }; + return new ProxyMatch { Member = match.Member, System = match.System, InnerText = inner }; } } diff --git a/PluralKit.Bot/Utils.cs b/PluralKit.Bot/Utils.cs index 91cf5b93..d0d33a68 100644 --- a/PluralKit.Bot/Utils.cs +++ b/PluralKit.Bot/Utils.cs @@ -100,6 +100,8 @@ namespace PluralKit.Bot if (input != null) return pattern.Replace(input, @"\$&"); else return input; } + + public static string ProxyTagsString(this PKMember member) => string.Join(", ", member.ProxyTags.Select(t => $"`{t.ProxyString.EscapeMarkdown()}`")); public static async Task PermissionsIn(this IChannel channel) { diff --git a/PluralKit.Core/DataFiles.cs b/PluralKit.Core/DataFiles.cs index ac119742..97960f97 100644 --- a/PluralKit.Core/DataFiles.cs +++ b/PluralKit.Core/DataFiles.cs @@ -37,8 +37,7 @@ namespace PluralKit.Bot Pronouns = m.Pronouns, Color = m.Color, AvatarUrl = m.AvatarUrl, - Prefix = m.Prefix, - Suffix = m.Suffix, + ProxyTags = m.ProxyTags, Created = Formats.TimestampExportFormat.Format(m.Created), MessageCount = messageCounts.Where(x => x.Member == m.Id).Select(x => x.MessageCount).FirstOrDefault() })); @@ -150,8 +149,7 @@ namespace PluralKit.Bot if (dataMember.AvatarUrl != null) member.AvatarUrl = dataMember.AvatarUrl; if (dataMember.Prefix != null || dataMember.Suffix != null) { - member.Prefix = dataMember.Prefix; - member.Suffix = dataMember.Suffix; + member.ProxyTags = new List { new ProxyTag(dataMember.Prefix, dataMember.Suffix) }; } if (dataMember.Birthday != null) @@ -223,8 +221,14 @@ namespace PluralKit.Bot [JsonProperty("pronouns")] public string Pronouns; [JsonProperty("color")] public string Color; [JsonProperty("avatar_url")] public string AvatarUrl; - [JsonProperty("prefix")] public string Prefix; - [JsonProperty("suffix")] public string Suffix; + + // For legacy single-tag imports + [JsonProperty("prefix")] [JsonIgnore] public string Prefix; + [JsonProperty("suffix")] [JsonIgnore] public string Suffix; + + // ^ is superseded by v + [JsonProperty("proxy_tags")] public ICollection ProxyTags; + [JsonProperty("message_count")] public int MessageCount; [JsonProperty("created")] public string Created; diff --git a/PluralKit.Core/Models.cs b/PluralKit.Core/Models.cs index 88f394c7..8269380d 100644 --- a/PluralKit.Core/Models.cs +++ b/PluralKit.Core/Models.cs @@ -1,12 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + using Dapper.Contrib.Extensions; using Newtonsoft.Json; using NodaTime; using NodaTime.Text; -using PluralKit.Core; - namespace PluralKit { + public struct ProxyTag + { + public ProxyTag(string prefix, string suffix) + { + // Normalize empty strings to null for DB + Prefix = prefix.Length == 0 ? null : prefix; + Suffix = suffix.Length == 0 ? null : suffix; + } + + [JsonProperty("prefix")] public string Prefix { get; set; } + [JsonProperty("suffix")] public string Suffix { get; set; } + + [JsonIgnore] public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}"; + + public bool Equals(ProxyTag other) => Prefix == other.Prefix && Suffix == other.Suffix; + + public override bool Equals(object obj) => obj is ProxyTag other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + return ((Prefix != null ? Prefix.GetHashCode() : 0) * 397) ^ + (Suffix != null ? Suffix.GetHashCode() : 0); + } + } + } + public class PKSystem { // Additions here should be mirrored in SystemStore::Save @@ -35,9 +65,22 @@ namespace PluralKit [JsonProperty("birthday")] public LocalDate? Birthday { get; set; } [JsonProperty("pronouns")] public string Pronouns { get; set; } [JsonProperty("description")] public string Description { get; set; } - [JsonProperty("prefix")] public string Prefix { get; set; } - [JsonProperty("suffix")] public string Suffix { get; set; } + [JsonProperty("proxy_tags")] public ICollection ProxyTags { get; set; } [JsonProperty("created")] public Instant Created { get; set; } + + // These are deprecated as fuck, and are kinda hacky + // Don't use, unless you're the API's serialization library + [JsonProperty("prefix")] [Obsolete("Use PKMember.ProxyTags")] public string Prefix + { + get => ProxyTags.FirstOrDefault().Prefix; + set => ProxyTags = new[] {new ProxyTag(Prefix, value)}; + } + + [JsonProperty("suffix")] [Obsolete("Use PKMember.ProxyTags")] public string Suffix + { + get => ProxyTags.FirstOrDefault().Prefix; + set => ProxyTags = new[] {new ProxyTag(Prefix, value)}; + } /// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" is hidden [JsonIgnore] public string BirthdayString @@ -52,9 +95,7 @@ namespace PluralKit } } - [JsonIgnore] public bool HasProxyTags => Prefix != null || Suffix != null; - [JsonIgnore] public string ProxyString => $"{Prefix ?? ""}text{Suffix ?? ""}"; - + [JsonIgnore] public bool HasProxyTags => ProxyTags.Count > 0; public string ProxyName(string systemTag) { if (systemTag == null) return DisplayName ?? Name; diff --git a/PluralKit.Core/Stores.cs b/PluralKit.Core/Stores.cs index f91ae756..fe864edc 100644 --- a/PluralKit.Core/Stores.cs +++ b/PluralKit.Core/Stores.cs @@ -495,7 +495,7 @@ namespace PluralKit { public async Task SaveMember(PKMember member) { using (var conn = await _conn.Obtain()) - 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); + await conn.ExecuteAsync("update members set name = @Name, display_name = @DisplayName, description = @Description, color = @Color, avatar_url = @AvatarUrl, birthday = @Birthday, pronouns = @Pronouns, proxy_tags = @ProxyTags where id = @Id", member); _logger.Information("Updated member {@Member}", member); } diff --git a/PluralKit.Core/Utils.cs b/PluralKit.Core/Utils.cs index cdc3677b..8569e6d3 100644 --- a/PluralKit.Core/Utils.cs +++ b/PluralKit.Core/Utils.cs @@ -310,6 +310,9 @@ namespace PluralKit // So we add a custom type handler that literally just passes the type through to Npgsql SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); + + // Add global type mapper for ProxyTag compound type in Postgres + NpgsqlConnection.GlobalTypeMapper.MapComposite("proxy_tag"); } public static ILogger InitLogger(CoreConfig config, string component) diff --git a/PluralKit.Core/db_schema.sql b/PluralKit.Core/db_schema.sql index d0108cca..11b55046 100644 --- a/PluralKit.Core/db_schema.sql +++ b/PluralKit.Core/db_schema.sql @@ -1,3 +1,12 @@ +-- Create proxy_tag compound type if it doesn't exist +do $$ begin + create type proxy_tag as ( + prefix text, + suffix text + ); +exception when duplicate_object then null; +end $$; + create table if not exists systems ( id serial primary key, @@ -23,8 +32,7 @@ create table if not exists members birthday date, pronouns text, description text, - prefix text, - suffix text, + proxy_tags proxy_tag[] not null default array[], -- Rationale on making this an array rather than a separate table - we never need to query them individually, only access them as part of a selected Member struct created timestamp not null default (current_timestamp at time zone 'utc') ); diff --git a/docs/1-user-guide.md b/docs/1-user-guide.md index 06cad30c..ecc7c9ce 100644 --- a/docs/1-user-guide.md +++ b/docs/1-user-guide.md @@ -255,6 +255,19 @@ You can now type a message enclosed in your proxy tags, and it'll be deleted by **NB:** If you want `` as proxy tags, there is currently a bug where custom server emojis will (wrongly) be interpreted as proxying with that member (see [issue #37](https://github.com/xSke/PluralKit/issues/37)). The current workaround is to use different proxy tags. +### Using multiple distinct proxy tag pairs +If you'd like to proxy a member in multiple ways (for example, a name or a nickname, uppercase and lowercase variants, etc), you can add multiple tag pairs. +When proxying, you may then use any of the tags to proxy for that specific member. + +To add a proxy tag to a member, use the `pk;member proxy add` command: + pk;member John proxy add {text} + pk;member Craig proxy add C:text + +To remove a proxy tag from a member, use the `pk;member proxy remove` command: + pk;member John proxy remove {text} + pk;member Craig proxy remove C:text + + ### Querying message information If you want information about a proxied message (eg. for moderation reasons), you can query the message for its sender account, system, member, etc. diff --git a/docs/2-command-list.md b/docs/2-command-list.md index d3ea5f2d..6d431b22 100644 --- a/docs/2-command-list.md +++ b/docs/2-command-list.md @@ -32,7 +32,9 @@ Words in \ are *required parameters*. Words in [square brackets] - `pk;member displayname ` - Changes the display name of a member. - `pk;member description [description]` - Changes the description of a member. - `pk;member avatar [avatar url]` - Changes the avatar of a member. -- `pk;member proxy [tags]` - Changes the proxy tags of a member. +- `pk;member proxy [tags]` - Changes the proxy tags of a member. use below add/remove commands for members with multiple tag pairs. +- `pk;member proxy add [tags]` - Adds a proxy tag pair to a member. +- `pk;member proxy remove [tags]` - Removes a proxy tag from a member. - `pk;member pronouns [pronouns]` - Changes the pronouns of a member. - `pk;member color [color]` - Changes the color of a member. - `pk;member birthdate [birthdate]` - Changes the birthday of a member. diff --git a/docs/3-api-documentation.md b/docs/3-api-documentation.md index e4c77220..b7c606ad 100644 --- a/docs/3-api-documentation.md +++ b/docs/3-api-documentation.md @@ -44,10 +44,16 @@ The following three models (usually represented in JSON format) represent the va |color|color?|Yes|6-char hex (eg. `ff7000`), sans `#`.| |avatar_url|url?|Yes|Not validated server-side.| |birthday|date?|Yes|ISO-8601 (`YYYY-MM-DD`) format, year of `0001` means hidden year.| -|prefix|string?|Yes|| -|suffix|string?|Yes|| +|prefix|string?|Yes|Deprecated. Use `proxy_tags` instead.| +|suffix|string?|Yes|Deprecated. Use `proxy_tags` instead.| +|proxy_tags|ProxyTag[]|Yes (entire array)|An array of ProxyTag (see below) objects, each representing a single prefix/suffix pair.| |created|datetime|No|| +#### ProxyTag object +|Key|Type| +|prefix|string?| +|suffix|string?| + ### Switch model |Key|Type|Notes|