diff --git a/PluralKit.API/Controllers/v1/JsonModelExt.cs b/PluralKit.API/Controllers/v1/JsonModelExt.cs index 1ba2fd15..f4c7b34f 100644 --- a/PluralKit.API/Controllers/v1/JsonModelExt.cs +++ b/PluralKit.API/Controllers/v1/JsonModelExt.cs @@ -44,14 +44,13 @@ namespace PluralKit.API { var o = new JObject(); o.Add("id", member.Hid); - o.Add("name", member.Name); - o.Add("color", member.MemberPrivacy.CanAccess(ctx) ? member.Color : null); - o.Add("display_name", member.DisplayName); - o.Add("birthday", member.MemberPrivacy.CanAccess(ctx) && member.Birthday.HasValue ? DateTimeFormats.DateExportFormat.Format(member.Birthday.Value) : null); - o.Add("pronouns", member.MemberPrivacy.CanAccess(ctx) ? member.Pronouns : null); + o.Add("name", member.NamePrivacy.CanAccess(ctx) ? member.Name : member.DisplayName ?? member.Name); + o.Add("color", member.ColorPrivacy.CanAccess(ctx) ? member.Color : null); + o.Add("display_name", member.NamePrivacy.CanAccess(ctx) ? member.DisplayName : null); + o.Add("birthday", member.BirthdayPrivacy.CanAccess(ctx) && member.Birthday.HasValue ? DateTimeFormats.DateExportFormat.Format(member.Birthday.Value) : null); + o.Add("pronouns", member.PronounPrivacy.CanAccess(ctx) ? member.Pronouns : null); o.Add("avatar_url", member.AvatarUrl); - o.Add("description", member.MemberPrivacy.CanAccess(ctx) ? member.Description : null); - o.Add("privacy", ctx == LookupContext.ByOwner ? (member.MemberPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("description", member.DescriptionPrivacy.CanAccess(ctx) ? member.Description : null); var tagArray = new JArray(); foreach (var tag in member.ProxyTags) @@ -59,7 +58,22 @@ namespace PluralKit.API o.Add("proxy_tags", tagArray); o.Add("keep_proxy", member.KeepProxy); - o.Add("created", DateTimeFormats.TimestampExportFormat.Format(member.Created)); + + o.Add("privacy", ctx == LookupContext.ByOwner ? (member.MemberVisibility == PrivacyLevel.Private ? "private" : "public") : null); + + o.Add("visibility", ctx == LookupContext.ByOwner ? (member.MemberVisibility == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("name_privacy", ctx == LookupContext.ByOwner ? (member.NamePrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("description_privacy", ctx == LookupContext.ByOwner ? (member.DescriptionPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("birthday_privacy", ctx == LookupContext.ByOwner ? (member.BirthdayPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("pronoun_privacy", ctx == LookupContext.ByOwner ? (member.PronounPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("color_privacy", ctx == LookupContext.ByOwner ? (member.ColorPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + o.Add("metadata_privacy", ctx == LookupContext.ByOwner ? (member.MetadataPrivacy == PrivacyLevel.Private ? "private" : "public") : null); + + if(member.MetadataPrivacy.CanAccess(ctx)) + o.Add("created", DateTimeFormats.TimestampExportFormat.Format(member.Created)); + else + o.Add("created", null); + if (member.ProxyTags.Count > 0) { @@ -101,8 +115,28 @@ namespace PluralKit.API .OfType().Select(o => new ProxyTag(o.Value("prefix"), o.Value("suffix"))) .ToList(); } - - if (o.ContainsKey("privacy")) member.MemberPrivacy = o.Value("privacy").ParsePrivacy("member"); + if(o.ContainsKey("privacy")) //TODO: Deprecate this completely in api v2 + { + var plevel = o.Value("privacy").ParsePrivacy("member"); + + member.MemberVisibility = plevel; + member.NamePrivacy = plevel; + member.DescriptionPrivacy = plevel; + member.BirthdayPrivacy = plevel; + member.PronounPrivacy = plevel; + member.ColorPrivacy = plevel; + member.MetadataPrivacy = plevel; + } + else + { + if (o.ContainsKey("visibility")) member.MemberVisibility = o.Value("visibility").ParsePrivacy("member"); + if (o.ContainsKey("name_privacy")) member.NamePrivacy = o.Value("name_privacy").ParsePrivacy("member"); + if (o.ContainsKey("description_privacy")) member.DescriptionPrivacy = o.Value("description_privacy").ParsePrivacy("member"); + if (o.ContainsKey("birthday_privacy")) member.BirthdayPrivacy = o.Value("birthday_privacy").ParsePrivacy("member"); + if (o.ContainsKey("pronoun_privacy")) member.PronounPrivacy = o.Value("pronoun_privacy").ParsePrivacy("member"); + if (o.ContainsKey("color_privacy")) member.ColorPrivacy = o.Value("color_privacy").ParsePrivacy("member"); + if (o.ContainsKey("metadata_privacy")) member.MetadataPrivacy = o.Value("metadata_privacy").ParsePrivacy("member"); + } } private static string BoundsCheckField(this string input, int maxLength, string nameInError) diff --git a/PluralKit.API/Controllers/v1/SystemController.cs b/PluralKit.API/Controllers/v1/SystemController.cs index 8129b874..84d26899 100644 --- a/PluralKit.API/Controllers/v1/SystemController.cs +++ b/PluralKit.API/Controllers/v1/SystemController.cs @@ -78,7 +78,7 @@ namespace PluralKit.API var members = _data.GetSystemMembers(system); return Ok(await members - .Where(m => m.MemberPrivacy.CanAccess(User.ContextFor(system))) + .Where(m => m.MemberVisibility.CanAccess(User.ContextFor(system))) .Select(m => m.ToJson(User.ContextFor(system))) .ToListAsync()); } diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index c52b6249..909ca4f3 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -24,8 +24,8 @@ namespace PluralKit.Bot public static Command SystemFronter = new Command("system fronter", "system [system] fronter", "Shows a system's fronter(s)"); public static Command SystemFrontHistory = new Command("system fronthistory", "system [system] fronthistory", "Shows a system's front history"); public static Command SystemFrontPercent = new Command("system frontpercent", "system [system] frontpercent [timespan]", "Shows a system's front breakdown"); - public static Command SystemPrivacy = new Command("system privacy", "system privacy ", "Changes your system's privacy settings"); public static Command SystemPing = new Command("system ping", "system ping ", "Changes your system's ping preferences"); + public static Command SystemPrivacy = new Command("system privacy", "system privacy ", "Changes your system's privacy settings"); public static Command Autoproxy = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for this server"); public static Command MemberInfo = new Command("member", "member ", "Looks up information about a member"); public static Command MemberNew = new Command("member new", "member new ", "Creates a new member"); @@ -42,7 +42,7 @@ namespace PluralKit.Bot 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 MemberRandom = new Command("random", "random", "Looks up a random member from your system"); - public static Command MemberPrivacy = new Command("member privacy", "member privacy [on|off]", "Sets whether a member is private or public"); + public static Command MemberPrivacy = new Command("member privacy", "member privacy ", "Changes a members's privacy settings"); 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"); public static Command SwitchMove = new Command("switch move", "switch move ", "Moves the latest switch in time"); @@ -72,7 +72,7 @@ namespace PluralKit.Bot public static Command[] MemberCommands = { MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns, - MemberColor, MemberBirthday, MemberProxy, MemberKeepProxy, MemberDelete, MemberAvatar, MemberServerAvatar, + MemberColor, MemberBirthday, MemberProxy, MemberKeepProxy, MemberDelete, MemberAvatar, MemberServerAvatar, MemberPrivacy, MemberRandom }; @@ -297,9 +297,9 @@ namespace PluralKit.Bot await ctx.Execute(MemberKeepProxy, m => m.KeepProxy(ctx, target)); else if (ctx.Match("privacy")) await ctx.Execute(MemberPrivacy, m => m.Privacy(ctx, target, null)); - else if (ctx.Match("private", "hidden")) + else if (ctx.Match("private", "hidden", "hide")) await ctx.Execute(MemberPrivacy, m => m.Privacy(ctx, target, PrivacyLevel.Private)); - else if (ctx.Match("public", "shown")) + else if (ctx.Match("public", "shown", "show")) await ctx.Execute(MemberPrivacy, m => m.Privacy(ctx, target, PrivacyLevel.Public)); else if (!ctx.HasNext()) // Bare command await ctx.Execute(MemberInfo, m => m.ViewMember(ctx, target)); diff --git a/PluralKit.Bot/Commands/Member.cs b/PluralKit.Bot/Commands/Member.cs index 0d56ec96..4d62607a 100644 --- a/PluralKit.Bot/Commands/Member.cs +++ b/PluralKit.Bot/Commands/Member.cs @@ -59,7 +59,7 @@ namespace PluralKit.Bot //Maybe move this somewhere else in the file structure since it doesn't need to get created at every command // TODO: don't buffer these, find something else to do ig - var members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberPrivacy == PrivacyLevel.Public).ToListAsync(); + var members = await _data.GetSystemMembers(ctx.System).Where(m => m.MemberVisibility == PrivacyLevel.Public).ToListAsync(); if (members == null || !members.Any()) throw Errors.NoMembersError; var randInt = randGen.Next(members.Count); diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index a79f2f96..8e8fc642 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -1,5 +1,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; +using System; + using Dapper; @@ -53,12 +55,6 @@ namespace PluralKit.Bot } } - private void CheckReadMemberPermission(Context ctx, PKMember target) - { - if (!target.MemberPrivacy.CanAccess(ctx.LookupContextFor(target.System))) - throw Errors.LookupNotAllowed; - } - private void CheckEditMemberPermission(Context ctx, PKMember target) { if (target.System != ctx.System?.Id) throw Errors.NotOwnMemberError; @@ -78,7 +74,8 @@ namespace PluralKit.Bot } else if (!ctx.HasNext()) { - CheckReadMemberPermission(ctx, target); + if (!target.DescriptionPrivacy.CanAccess(ctx.LookupContextFor(target.System))) + throw Errors.LookupNotAllowed; if (target.Description == null) if (ctx.System?.Id == target.System) await ctx.Reply($"This member does not have a description set. To set one, type `pk;member {target.Hid} description `."); @@ -119,7 +116,8 @@ namespace PluralKit.Bot } else if (!ctx.HasNext()) { - CheckReadMemberPermission(ctx, target); + if (!target.PronounPrivacy.CanAccess(ctx.LookupContextFor(target.System))) + throw Errors.LookupNotAllowed; if (target.Pronouns == null) if (ctx.System?.Id == target.System) await ctx.Reply($"This member does not have pronouns set. To set some, type `pk;member {target.Hid} pronouns `."); @@ -155,7 +153,8 @@ namespace PluralKit.Bot } else if (!ctx.HasNext()) { - CheckReadMemberPermission(ctx, target); + if (!target.ColorPrivacy.CanAccess(ctx.LookupContextFor(target.System))) + throw Errors.LookupNotAllowed; if (target.Color == null) if (ctx.System?.Id == target.System) @@ -199,7 +198,8 @@ namespace PluralKit.Bot } else if (!ctx.HasNext()) { - CheckReadMemberPermission(ctx, target); + if (!target.BirthdayPrivacy.CanAccess(ctx.LookupContextFor(target.System))) + throw Errors.LookupNotAllowed; if (target.Birthday == null) await ctx.Reply("This member does not have a birthdate set." @@ -365,29 +365,137 @@ namespace PluralKit.Bot if (ctx.System == null) throw Errors.NoSystemError; if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError; - PrivacyLevel newValue; - if (ctx.Match("private", "hide", "hidden", "on", "enable", "yes")) newValue = PrivacyLevel.Private; - else if (ctx.Match("public", "show", "shown", "displayed", "off", "disable", "no")) newValue = PrivacyLevel.Public; - else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"private\" or \"public\"."); - // If we're getting a value from command (eg. "pk;m private" == always private, "pk;m public == always public"), use that instead of parsing - else if (newValueFromCommand != null) newValue = newValueFromCommand.Value; - else + // Display privacy settings + if (!ctx.HasNext() && newValueFromCommand == null) { - if (target.MemberPrivacy == PrivacyLevel.Public) - await ctx.Reply("This member's privacy is currently set to **public**. This member will show up in member lists and will return all information when queried by other accounts."); - else - await ctx.Reply("This member's privacy is currently set to **private**. This member will not show up in member lists and will return limited information when queried by other accounts."); + string PrivacyLevelString(PrivacyLevel level) => level switch + { + PrivacyLevel.Private => "**Private** (visible only when queried by you)", + PrivacyLevel.Public => "**Public** (visible to everyone)", + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) + }; + var eb = new DiscordEmbedBuilder() + .WithTitle($"Current privacy settings for {target.Name}") + .AddField("Name (replaces name with display name if member has one)",PrivacyLevelString(target.NamePrivacy)) + .AddField("Description", PrivacyLevelString(target.DescriptionPrivacy)) + .AddField("Birthday", PrivacyLevelString(target.BirthdayPrivacy)) + .AddField("Pronouns", PrivacyLevelString(target.PronounPrivacy)) + .AddField("Color", PrivacyLevelString(target.ColorPrivacy)) + .AddField("Meta (message count, last front, last message)", PrivacyLevelString(target.MetadataPrivacy)) + .AddField("Visibility", PrivacyLevelString(target.MemberVisibility)) + .WithDescription("To edit privacy settings, use the command:\n`pk;member privacy `\n\n- `subject` is one of `name`, `description`, `birthday`, `pronouns`, `color`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`."); + await ctx.Reply(embed: eb.Build()); return; } - target.MemberPrivacy = newValue; - await _data.SaveMember(target); + // Set Privacy Settings + PrivacyLevel PopPrivacyLevel(string subject, out string levelStr, out string levelExplanation) + { + if (ctx.Match("public", "show", "shown", "visible")) + { + levelStr = "public"; + levelExplanation = "be shown on the member card"; + return PrivacyLevel.Public; + } - if (newValue == PrivacyLevel.Private) - await ctx.Reply($"{Emojis.Success} Member privacy set to **private**. This member will no longer show up in member lists and will return limited information when queried by other accounts."); + if (ctx.Match("private", "hide", "hidden")) + { + levelStr = "private"; + levelExplanation = "*not* be shown on the member card"; + if(subject == "name") levelExplanation += " unless no display name is set"; + return PrivacyLevel.Private; + } + + if (!ctx.HasNext()) + throw new PKSyntaxError($"You must pass a privacy level for `{subject}` (`public` or `private`)"); + throw new PKSyntaxError($"Invalid privacy level `{ctx.PopArgument().SanitizeMentions()}` (must be `public` or `private`)."); + } + + string levelStr, levelExplanation, subjectStr; + var subjectList = "`name`, `description`, `birthday`, `pronouns`, `color`, `metadata`, `visibility`, or `all`"; + if(ctx.Match("name")) + { + subjectStr = "name"; + target.NamePrivacy = PopPrivacyLevel("name", out levelStr, out levelExplanation); + } + else if(ctx.Match("description", "desc", "text", "info")) + { + subjectStr = "description"; + target.DescriptionPrivacy = PopPrivacyLevel("description", out levelStr, out levelExplanation); + } + else if(ctx.Match("birthday", "birth", "bday")) + { + subjectStr = "birthday"; + target.BirthdayPrivacy = PopPrivacyLevel("birthday", out levelStr, out levelExplanation); + } + else if(ctx.Match("pronouns", "pronoun")) + { + subjectStr = "pronouns"; + target.PronounPrivacy = PopPrivacyLevel("pronouns", out levelStr, out levelExplanation); + } + else if(ctx.Match("color","colour")) + { + subjectStr = "color"; + target.ColorPrivacy = PopPrivacyLevel("color", out levelStr, out levelExplanation); + } + else if(ctx.Match("meta","metadata")) + { + subjectStr = "metadata (date created, message count, last fronted, and last message)"; + target.MetadataPrivacy = PopPrivacyLevel("metadata", out levelStr, out levelExplanation); + } + else if(ctx.Match("visibility","hidden","shown","visible")) + { + subjectStr = "visibility"; + target.MemberVisibility = PopPrivacyLevel("visibility", out levelStr, out levelExplanation); + } + else if(ctx.Match("all") || newValueFromCommand != null){ + subjectStr = "all"; + PrivacyLevel level; + if(newValueFromCommand != null) + { + if(newValueFromCommand == PrivacyLevel.Public) + { + level = PrivacyLevel.Public; + levelStr = "public"; + levelExplanation = "be shown on the member card"; + } + else + { + level = PrivacyLevel.Private; + levelStr = "private"; + levelExplanation = "*not* be shown on the member card"; + } + } + else + level = PopPrivacyLevel("all", out levelStr, out levelExplanation); + target.MemberVisibility = level; + target.NamePrivacy = level; + target.DescriptionPrivacy = level; + target.BirthdayPrivacy = level; + target.PronounPrivacy = level; + target.ColorPrivacy = level; + target.MetadataPrivacy = level; + } else - await ctx.Reply($"{Emojis.Success} Member privacy set to **public**. This member will now show up in member lists and will return all information when queried by other accounts."); + throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument().SanitizeMentions()}` (must be {subjectList})."); + + + await _data.SaveMember(target); + //Handle "all" subject + if(subjectStr == "all"){ + if(levelStr == "private") + await ctx.Reply($"All {target.Name.SanitizeMentions()}'s privacy settings have been set to **{levelStr}**. Other accounts will now see nothing on the member card."); + else + await ctx.Reply($"All {target.Name.SanitizeMentions()}'s privacy settings have been set to **{levelStr}**. Other accounts will now see everything on the member card."); + } + //Handle other subjects + else + await ctx.Reply($"{target.Name.SanitizeMentions()}'s {subjectStr} has been set to **{levelStr}**. Other accounts will now {levelExplanation}."); + + + + } public async Task Delete(Context ctx, PKMember target) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 477a9122..935e156f 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -262,7 +262,7 @@ namespace PluralKit.Bot .AddField("Member list", PrivacyLevelString(ctx.System.MemberListPrivacy)) .AddField("Current fronter(s)", PrivacyLevelString(ctx.System.FrontPrivacy)) .AddField("Front/switch history", PrivacyLevelString(ctx.System.FrontHistoryPrivacy)) - .WithDescription("To edit privacy settings, use the command:\n`pk;system privacy `\n\n- `subject` is one of `description`, `list`, `front` or `fronthistory`\n- `level` is either `public` or `private`."); + .WithDescription("To edit privacy settings, use the command:\n`pk;system privacy `\n\n- `subject` is one of `description`, `list`, `front`, `fronthistory`, or `all` \n- `level` is either `public` or `private`."); await ctx.Reply(embed: eb.Build()); return; } @@ -289,7 +289,7 @@ namespace PluralKit.Bot } string levelStr, levelExplanation, subjectStr; - var subjectList = "`description`, `members`, `front` or `fronthistory`"; + var subjectList = "`description`, `members`, `front`, `fronthistory`, or `all`"; if (ctx.Match("description", "desc", "text", "info")) { subjectStr = "description"; @@ -310,10 +310,27 @@ namespace PluralKit.Bot subjectStr = "front history"; ctx.System.FrontHistoryPrivacy = PopPrivacyLevel("fronthistory", out levelStr, out levelExplanation); } + else if (ctx.Match("all")){ + subjectStr = "all"; + PrivacyLevel level = PopPrivacyLevel("all", out levelStr, out levelExplanation); + ctx.System.DescriptionPrivacy = level; + ctx.System.MemberListPrivacy = level; + ctx.System.FrontPrivacy = level; + ctx.System.FrontHistoryPrivacy = level; + + } else throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument().SanitizeMentions()}` (must be {subjectList})."); await _data.SaveSystem(ctx.System); + if(subjectStr == "all"){ + if(levelStr == "private") + await ctx.Reply($"All of your systems privacy settings have been set to **{levelStr}**. Other accounts will now see nothing on the member card."); + else + await ctx.Reply($"All of your systems privacy have been set to **{levelStr}**. Other accounts will now see everything on the member card."); + } + //Handle other subjects + else await ctx.Reply($"System {subjectStr} privacy has been set to **{levelStr}**. Other accounts will now {levelExplanation} your system {subjectStr}."); } diff --git a/PluralKit.Bot/Commands/SystemList.cs b/PluralKit.Bot/Commands/SystemList.cs index 3b633353..2a4ca989 100644 --- a/PluralKit.Bot/Commands/SystemList.cs +++ b/PluralKit.Bot/Commands/SystemList.cs @@ -33,7 +33,7 @@ namespace PluralKit.Bot (eb, ms) => { eb.WithFooter($"{opts.CreateFilterString()}. {members.Count} results."); - renderer.RenderPage(eb, ctx.System.Zone, ms); + renderer.RenderPage(eb, ctx.System.Zone, ms, ctx.LookupContextFor(target)); return Task.CompletedTask; }); } diff --git a/PluralKit.Bot/Lists/IListRenderer.cs b/PluralKit.Bot/Lists/IListRenderer.cs index 23368882..24bf545d 100644 --- a/PluralKit.Bot/Lists/IListRenderer.cs +++ b/PluralKit.Bot/Lists/IListRenderer.cs @@ -11,6 +11,6 @@ namespace PluralKit.Bot public interface IListRenderer { int MembersPerPage { get; } - void RenderPage(DiscordEmbedBuilder eb, DateTimeZone zone, IEnumerable members); + void RenderPage(DiscordEmbedBuilder eb, DateTimeZone zone, IEnumerable members, LookupContext ctx); } } \ No newline at end of file diff --git a/PluralKit.Bot/Lists/LongRenderer.cs b/PluralKit.Bot/Lists/LongRenderer.cs index b22684db..5b3b528e 100644 --- a/PluralKit.Bot/Lists/LongRenderer.cs +++ b/PluralKit.Bot/Lists/LongRenderer.cs @@ -20,25 +20,26 @@ namespace PluralKit.Bot _fields = fields; } - public void RenderPage(DiscordEmbedBuilder eb, DateTimeZone zone, IEnumerable members) + public void RenderPage(DiscordEmbedBuilder eb, DateTimeZone zone, IEnumerable members, LookupContext ctx) { string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(zone)); foreach (var m in members) { var profile = $"**ID**: {m.Hid}"; - if (_fields.ShowDisplayName && m.DisplayName != null) profile += $"\n**Display name**: {m.DisplayName}"; - if (_fields.ShowPronouns && m.Pronouns != null) profile += $"\n**Pronouns**: {m.Pronouns}"; - if (_fields.ShowBirthday && m.Birthday != null) profile += $"\n**Birthdate**: {m.BirthdayString}"; + if (_fields.ShowDisplayName && m.DisplayName != null && m.NamePrivacy.CanAccess(ctx)) profile += $"\n**Display name**: {m.DisplayName}"; + if (_fields.ShowPronouns && m.Pronouns != null && m.PronounPrivacy.CanAccess(ctx)) profile += $"\n**Pronouns**: {m.Pronouns}"; + if (_fields.ShowBirthday && m.Birthday != null && m.BirthdayPrivacy.CanAccess(ctx)) profile += $"\n**Birthdate**: {m.BirthdayString}"; if (_fields.ShowProxyTags && m.ProxyTags.Count > 0) profile += $"\n**Proxy tags:** {m.ProxyTagsString()}"; - if (_fields.ShowMessageCount && m.MessageCount > 0) profile += $"\n**Message count:** {m.MessageCount}"; - if (_fields.ShowLastMessage && m.LastMessage != null) profile += $"\n**Last message:** {FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))}"; - if (_fields.ShowLastSwitch && m.LastSwitchTime != null) profile += $"\n**Last switched in:** {FormatTimestamp(m.LastSwitchTime.Value)}"; - if (_fields.ShowDescription && m.Description != null) profile += $"\n\n{m.Description}"; - if (_fields.ShowPrivacy && m.MemberPrivacy == PrivacyLevel.Private) - profile += "\n*(this member is private)*"; + if (_fields.ShowMessageCount && m.MessageCount > 0 && m.MetadataPrivacy.CanAccess(ctx)) profile += $"\n**Message count:** {m.MessageCount}"; + if (_fields.ShowLastMessage && m.LastMessage != null && m.MetadataPrivacy.CanAccess(ctx)) profile += $"\n**Last message:** {FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))}"; + if (_fields.ShowLastSwitch && m.LastSwitchTime != null && m.MetadataPrivacy.CanAccess(ctx)) profile += $"\n**Last switched in:** {FormatTimestamp(m.LastSwitchTime.Value)}"; + if (_fields.ShowDescription && m.Description != null && m.DescriptionPrivacy.CanAccess(ctx)) profile += $"\n\n{m.Description}"; + if (_fields.ShowPrivacy && m.MemberVisibility == PrivacyLevel.Private) + profile += "\n*(this member is hidden)*"; - eb.AddField(m.Name, profile.Truncate(1024)); + var memberName = m.NamePrivacy.CanAccess(ctx) ? m.Name : (m.DisplayName ?? m.Name); + eb.AddField(memberName, profile.Truncate(1024)); } } diff --git a/PluralKit.Bot/Lists/ShortRenderer.cs b/PluralKit.Bot/Lists/ShortRenderer.cs index d2404c32..b2d1084b 100644 --- a/PluralKit.Bot/Lists/ShortRenderer.cs +++ b/PluralKit.Bot/Lists/ShortRenderer.cs @@ -13,7 +13,7 @@ namespace PluralKit.Bot { public int MembersPerPage => 25; - public void RenderPage(DiscordEmbedBuilder eb, DateTimeZone timezone, IEnumerable members) + public void RenderPage(DiscordEmbedBuilder eb, DateTimeZone timezone, IEnumerable members, LookupContext ctx) { string RenderLine(ListedMember m) { @@ -22,8 +22,8 @@ namespace PluralKit.Bot var proxyTagsString = m.ProxyTagsString().SanitizeMentions(); if (proxyTagsString.Length > 100) // arbitrary threshold for now, tweak? proxyTagsString = "tags too long, see member card"; - - return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}** *({proxyTagsString})*"; + var memberName = m.NamePrivacy.CanAccess(ctx) ? m.Name : (m.DisplayName ?? m.Name); + return $"[`{m.Hid}`] **{memberName.SanitizeMentions()}** *({proxyTagsString})*"; } return $"[`{m.Hid}`] **{m.Name.SanitizeMentions()}**"; diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 2a2e1ccd..fdddb0cc 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -26,6 +26,8 @@ namespace PluralKit.Bot { _db = db; } + + public async Task CreateSystemEmbed(DiscordClient client, PKSystem system, LookupContext ctx) { var accounts = await _data.GetSystemAccounts(system); @@ -68,8 +70,9 @@ namespace PluralKit.Bot { public DiscordEmbed CreateLoggedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, DiscordUser sender, string content, DiscordChannel channel) { // TODO: pronouns in ?-reacted response using this card var timestamp = DiscordUtils.SnowflakeToInstant(messageId); + var name = member.NamePrivacy == PrivacyLevel.Public ? member.Name : member.DisplayName ?? member.Name; return new DiscordEmbedBuilder() - .WithAuthor($"#{channel.Name}: {member.Name}", iconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarUrl)) + .WithAuthor($"#{channel.Name}: {name}", iconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarUrl)) .WithThumbnailUrl(member.AvatarUrl) .WithDescription(content?.NormalizeLineEndSpacing()) .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} | Sender: {sender.Username}#{sender.Discriminator} ({sender.Id}) | Message ID: {messageId} | Original Message ID: {originalMsgId}") @@ -79,7 +82,10 @@ namespace PluralKit.Bot { public async Task CreateMemberEmbed(PKSystem system, PKMember member, DiscordGuild guild, LookupContext ctx) { - var name = member.Name; + + // string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone)); + + var name = member.NamePrivacy.CanAccess(ctx) ? member.Name : member.DisplayName ?? member.Name; if (system.Name != null) name = $"{member.Name} ({system.Name})"; DiscordColor color; @@ -104,11 +110,11 @@ namespace PluralKit.Bot { var eb = new DiscordEmbedBuilder() // TODO: add URL of website when that's up .WithAuthor(name, iconUrl: DiscordUtils.WorkaroundForUrlBug(avatar)) - .WithColor(member.MemberPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) - .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} | Created on {DateTimeFormats.ZonedDateTimeFormat.Format(member.Created.InZone(system.Zone))}"); + .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) + .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {DateTimeFormats.ZonedDateTimeFormat.Format(member.Created.InZone(system.Zone))}":"")}"); var description = ""; - if (member.MemberPrivacy == PrivacyLevel.Private) description += "*(this member is private)*\n"; + if (member.MemberVisibility == PrivacyLevel.Private) description += "*(this member is hidden)*\n"; if (guildSettings?.AvatarUrl != null) if (member.AvatarUrl != null) description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl}) to see the global avatar)*\n"; @@ -118,14 +124,18 @@ namespace PluralKit.Bot { if (avatar != null) eb.WithThumbnailUrl(avatar); - if (!member.DisplayName.EmptyOrNull()) eb.AddField("Display Name", member.DisplayName.Truncate(1024), true); + if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx)) 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 && member.MemberPrivacy.CanAccess(ctx)) eb.AddField("Birthdate", member.BirthdayString, true); - if (!member.Pronouns.EmptyOrNull() && member.MemberPrivacy.CanAccess(ctx)) eb.AddField("Pronouns", member.Pronouns.Truncate(1024), true); - if (member.MessageCount > 0 && member.MemberPrivacy.CanAccess(ctx)) eb.AddField("Message Count", member.MessageCount.ToString(), true); + if (member.Birthday != null && member.BirthdayPrivacy.CanAccess(ctx)) eb.AddField("Birthdate", member.BirthdayString, true); + if (!member.Pronouns.EmptyOrNull() && member.PronounPrivacy.CanAccess(ctx)) eb.AddField("Pronouns", member.Pronouns.Truncate(1024), true); + if (member.MessageCount > 0 && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Message Count", member.MessageCount.ToString(), true); if (member.HasProxyTags) eb.AddField("Proxy Tags", string.Join('\n', proxyTagsStr).Truncate(1024), true); - if (!member.Color.EmptyOrNull() && member.MemberPrivacy.CanAccess(ctx)) eb.AddField("Color", $"#{member.Color}", true); - if (!member.Description.EmptyOrNull() && member.MemberPrivacy.CanAccess(ctx)) eb.AddField("Description", member.Description.NormalizeLineEndSpacing(), false); + // --- For when this gets added to the member object itself or however they get added + // if (member.LastMessage != null && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last message:" FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))); + // if (member.LastSwitchTime != null && m.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last switched in:", FormatTimestamp(member.LastSwitchTime.Value)); + if (!member.Color.EmptyOrNull() && member.ColorPrivacy.CanAccess(ctx)) eb.AddField("Color", $"#{member.Color}", true); + + if (!member.Description.EmptyOrNull() && member.DescriptionPrivacy.CanAccess(ctx)) eb.AddField("Description", member.Description.NormalizeLineEndSpacing(), false); return eb.Build(); } diff --git a/PluralKit.Core/Database/Database.cs b/PluralKit.Core/Database/Database.cs index b010df62..09e6ff20 100644 --- a/PluralKit.Core/Database/Database.cs +++ b/PluralKit.Core/Database/Database.cs @@ -20,7 +20,7 @@ namespace PluralKit.Core internal class Database: IDatabase { private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files - private const int TargetSchemaVersion = 7; + private const int TargetSchemaVersion = 8; private readonly CoreConfig _config; private readonly ILogger _logger; diff --git a/PluralKit.Core/Database/Migrations/8.sql b/PluralKit.Core/Database/Migrations/8.sql new file mode 100644 index 00000000..28439e5f --- /dev/null +++ b/PluralKit.Core/Database/Migrations/8.sql @@ -0,0 +1,22 @@ +-- SCHEMA VERSION 8: 2020-05-13 -- +-- Create new columns -- +alter table members add column description_privacy integer check (description_privacy in (1, 2)) not null default 1; +alter table members add column name_privacy integer check (name_privacy in (1, 2)) not null default 1; +alter table members add column birthday_privacy integer check (birthday_privacy in (1, 2)) not null default 1; +alter table members add column pronoun_privacy integer check (pronoun_privacy in (1, 2)) not null default 1; +alter table members add column metadata_privacy integer check (metadata_privacy in (1, 2)) not null default 1; +alter table members add column color_privacy integer check (color_privacy in (1, 2)) not null default 1; + +-- Transfer existing settings -- +update members set description_privacy = member_privacy; +update members set name_privacy = member_privacy; +update members set birthday_privacy = member_privacy; +update members set pronoun_privacy = member_privacy; +update members set metadata_privacy = member_privacy; +update members set color_privacy = member_privacy; + +-- Rename member_privacy to member_visibility -- +alter table members rename column member_privacy to member_visibility; + +-- Update Schema Info -- +update info set schema_version = 8; diff --git a/PluralKit.Core/Database/Views/DatabaseViewsExt.cs b/PluralKit.Core/Database/Views/DatabaseViewsExt.cs index b62637af5..df03e43a 100644 --- a/PluralKit.Core/Database/Views/DatabaseViewsExt.cs +++ b/PluralKit.Core/Database/Views/DatabaseViewsExt.cs @@ -17,7 +17,7 @@ namespace PluralKit.Core StringBuilder query = new StringBuilder("select * from member_list where system = @system"); if (privacyFilter != null) - query.Append($" and member_privacy = {(int) privacyFilter}"); + query.Append($" and member_visibility = {(int) privacyFilter}"); if (filter != null) { diff --git a/PluralKit.Core/Models/PKMember.cs b/PluralKit.Core/Models/PKMember.cs index c13c9c05..18ba7947 100644 --- a/PluralKit.Core/Models/PKMember.cs +++ b/PluralKit.Core/Models/PKMember.cs @@ -23,7 +23,13 @@ namespace PluralKit.Core { public Instant Created { get; } public int MessageCount { get; } - public PrivacyLevel MemberPrivacy { get; set; } + public PrivacyLevel MemberVisibility { get; set; } + public PrivacyLevel DescriptionPrivacy { get; set; } + public PrivacyLevel NamePrivacy { get; set; } //ignore setting if no display name is set + public PrivacyLevel BirthdayPrivacy { get; set; } + public PrivacyLevel PronounPrivacy { get; set; } + public PrivacyLevel MetadataPrivacy { get; set; } + public PrivacyLevel ColorPrivacy { get; set; } /// Returns a formatted string representing the member's birthday, taking into account that a year of "0001" or "0004" is hidden /// Before Feb 10 2020, the sentinel year was 0001, now it is 0004. diff --git a/PluralKit.Core/Services/PostgresDataStore.cs b/PluralKit.Core/Services/PostgresDataStore.cs index 84e34df6..a0783b59 100644 --- a/PluralKit.Core/Services/PostgresDataStore.cs +++ b/PluralKit.Core/Services/PostgresDataStore.cs @@ -150,7 +150,7 @@ namespace PluralKit.Core { 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, proxy_tags = @ProxyTags, keep_proxy = @KeepProxy, member_privacy = @MemberPrivacy 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, keep_proxy = @KeepProxy, member_visibility = @MemberVisibility, description_privacy = @DescriptionPrivacy, name_privacy = @NamePrivacy, birthday_privacy = @BirthdayPrivacy, pronoun_privacy = @PronounPrivacy, metadata_privacy = @MetadataPrivacy, color_privacy = @ColorPrivacy where id = @Id", member); _logger.Information("Updated member {@Member}", member); } @@ -164,8 +164,8 @@ namespace PluralKit.Core { public async Task GetSystemMemberCount(SystemId id, bool includePrivate) { - var query = "select count(*) from members where system = @id"; - if (!includePrivate) query += " and member_privacy = 1"; // 1 = public + var query = "select count(*) from members where system = @Id"; + if (!includePrivate) query += " and member_visibility = 1"; // 1 = public using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync(query, new { id }); diff --git a/docs/2-user-guide.md b/docs/2-user-guide.md index 6160b5f7..78ac9403 100644 --- a/docs/2-user-guide.md +++ b/docs/2-user-guide.md @@ -438,7 +438,7 @@ To update your system privacy settings, use the following commands: pk;system privacy -where `` is either `description`, `fronter`, `fronthistory` or `list`, corresponding to the options above, and `level` is either `public` or `private`. +where `` is either `description`, `fronter`, `fronthistory` or `list`, corresponding to the options above, and `` is either `public` or `private`. `` can also be `all` in order to change all subjects at once. For example: @@ -449,15 +449,37 @@ For example: When the **member list** is **private**, other users will not be able to view the full member list of your system, but they can still query individual members given their 5-letter ID. If **current fronter** is private, but **front history** isn't, someone can still see the current fronter by looking at the history (this combination doesn't make much sense). ### Member privacy -There is also an option to mark a specific member as private, using the command: +There are also nine options for configuring member privacy; - pk;member private +- Name +- Description +- Birthday +- Pronouns +- Colour +- Date created +- Message count +- Visibility -A private member will *not* be displayed in member lists (even if the member list is public), and will show only limited information if looked up by others - namely name, display name and avatar. Other information, such as description, pronouns and birthday will be hidden. +As with system privacy, each can be set to **public** or **private**. The same rules apply for how they are shown too. When set to **public**, anyone who queries your system (by account or system ID, or through the API), will see this information. When set to **private**, the information will only be shown when *you yourself* query the information. The cards will still be displayed in the channel the commands are run in, so it's still your responsibility not to pull up information in servers where you don't want it displayed. -All of this can only be accessed using the member's 5-letter ID, which is exposed when proxying. So, if you want to keep a member absolutely private, it's recommended you don't proxy with it publicly - that way the ID isn't exposed. +However there are two catches. When name is set to private, it will be replaced by a members display name, but only if they have one! When visibility is set to private, the member will not show up in the member list unless -all is used in the command (and you are part of the system). + +Member info will not be shown in member lists even if someone in the system queries the list, unless the user is part of the system and uses the -all flag. + +To update a members privacy you can use the command: + + member privacy + +where `` is the name or the id of a member in your system, `` is either `name`, `description`, `birthday`, `pronouns`, `color`, `metadata`, or `visiblity` corresponding to the options above, and `` is either `public` or `private`. `` can also be `all` in order to change all subjects at once. +`metatdata` will affect the message count, the date created, the last fronted, and the last message information. + +For example: + + pk;member John privacy visibility private + pk;member "Craig Johnson" privacy description public + pk;member Robert privacy color public + pk;member Skyler privacy all private -An example of a private member is `cmpuv` - try looking it up and see what's shown, as well as the corresponding system list (`pk;system exmpl list`). ## Moderation commands ### Log channel diff --git a/docs/3-command-list.md b/docs/3-command-list.md index db815301..caebfb8c 100644 --- a/docs/3-command-list.md +++ b/docs/3-command-list.md @@ -17,6 +17,8 @@ Words in **\** or **[square brackets]** mean fill-in-the-blank. - `pk;system rename [new name]` - Changes the name of your system. - `pk;system description [description]` - Changes the description of your system. - `pk;system avatar [avatar url]` - Changes the avatar of your system. +- `pk;system privacy` - Displays your system's current privacy settings. +- `pk;system privacy ` - Changes your systems privacy settings. - `pk;system tag [tag]` - Changes the system tag of your system. - `pk;system timezone [location]` - Changes the time zone of your system. - `pk;system proxy [on|off]` - Toggles message proxying for a specific server. @@ -42,6 +44,8 @@ Words in **\** or **[square brackets]** mean fill-in-the-blank. - `pk;member description [description]` - Changes the description of a member. - `pk;member avatar ` - Changes the avatar of a member. - `pk;member serveravatar ` - Changes the avatar of a member in a specific server. +- `pk;member privacy` - Displays a members current privacy settings. +- `pk;member privacy ` - Changes a members privacy setting. - `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. diff --git a/docs/4-api-documentation.md b/docs/4-api-documentation.md index b2979eb7..f2a0b4e9 100644 --- a/docs/4-api-documentation.md +++ b/docs/4-api-documentation.md @@ -59,12 +59,20 @@ 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` or `0004` means hidden year. Birthdays set after 2020-02-10 use `0004` as a sentinel year, but both options are recognized as valid.| -|prefix|string?|Yes|Deprecated. Use `proxy_tags` instead.| -|suffix|string?|Yes|Deprecated. Use `proxy_tags` instead.| +|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.| |keep_proxy|bool|Yes|Whether to display a member's proxy tags in the proxied message.| -|created|datetime|No|| -|privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|created|datetime|No| +|privacy|string?|Yes|**Deprecated.** Use `_privacy` and `visibility` fields.| +|visibility|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|name_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|description_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|birthday_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|pronoun_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|color_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|message_count_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| +|created_timestamp_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.| #### ProxyTag object @@ -165,7 +173,14 @@ If the request is not authenticated with the system's token, members marked as p "proxy_tags": [{"prefix": "[", "suffix": "]"}], "keep_proxy": false, "created": "2019-01-01T15:00:00.654321Z", - "privacy": null + "visibility": null, + "name_privacy": null, + "description_privacy": null, + "birthday_privacy": null, + "pronoun_privacy": null, + "color_privacy": null, + "message_count_privacy": null, + "created_timestamp_privacy": null } ] ``` @@ -220,6 +235,14 @@ If the system has chosen to hide its current fronters, this will return `403 For "description": "I am Craig, example user extraordinaire.", "proxy_tags": [{"prefix": "[", "suffix": "]"}], "keep_proxy": false, + "visibility": null, + "name_privacy": null, + "description_privacy": null, + "birthday_privacy": null, + "pronoun_privacy": null, + "color_privacy": null, + "message_count_privacy": null, + "created_timestamp_privacy": null, "created": "2019-01-01T15:00:00.654321Z" } ] @@ -302,7 +325,14 @@ If this member is marked private, and the request isn't authenticated with the m "proxy_tags": [{"prefix": "[", "suffix": "]"}], "keep_proxy": false, "created": "2019-01-01T15:00:00.654321Z", - "privacy": "public" + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } ``` @@ -324,7 +354,14 @@ Creates a new member with the information given. Missing fields (except for name "pronouns": "they/them", "description": "I am Craig, cooler example user extraordinaire.", "keep_proxy": false, - "privacy": "public" + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } ``` (note the absence of a `proxy_tags` field, which is cleared in the response) @@ -343,7 +380,14 @@ Creates a new member with the information given. Missing fields (except for name "proxy_tags": [], "keep_proxy": false, "created": "2019-01-01T15:00:00.654321Z", - "privacy": "public" + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } ``` @@ -365,7 +409,14 @@ Edits a member's information. Missing fields will keep their current values. Wil "pronouns": "they/them", "description": "I am Craig, cooler example user extraordinaire.", "keep_proxy": false, - "privacy": "public" + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } ``` (note the absence of a `proxy_tags` field, which keeps its old value in the response) @@ -384,7 +435,14 @@ Edits a member's information. Missing fields will keep their current values. Wil "proxy_tags": [{"prefix": "[", "suffix": "]"}], "keep_proxy": false, "created": "2019-01-01T15:00:00.654321Z", - "privacy": "public" + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } ``` @@ -459,12 +517,24 @@ The returned system and member's privacy settings will be respected, and as such "description": "I am Craig, example user extraordinaire.", "proxy_tags": [{"prefix": "[", "suffix": "]"}], "keep_proxy": false, - "created": "2019-01-01T15:00:00.654321Z" + "created": "2019-01-01T15:00:00.654321Z", + "visibility": "public", + "name_privacy": "public", + "description_privacy": "private", + "birthday_privacy": "private", + "pronoun_privacy": "public", + "color_privacy": "public", + "message_count_privacy": "private", + "created_timestamp_privacy": "public" } } ``` ## Version history + +* 2020-05-14 + * The API now has values for granular member privacy. The new fields are as follows: `visibility`, `name_privacy`, `description_privacy`, `birthday_privacy`, `pronoun_privacy`, `color_privacy`, `message_count_privacy`, and `created_timestamp_privacy`. All are strings and accept the values of `public`, `private` and `null` + * The `privacy` field has now been deprecated and should not be used. It is a reflection of visibility. * 2020-05-07 * The API (v1) is now formally(ish) defined with OpenAPI v3.0. [The definition file can be found here.](https://github.com/xSke/PluralKit/blob/master/PluralKit.API/openapi.yaml) * 2020-02-10