Feature/granular member privacy (#174)

* Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so

* Add options to member model

* Add Privacy to member embed

* Added member privacy display list

* Update database settings

* apparetnly this is nolonger needed?

* Fix sql call

* Fix more sql errors

* Added in settings control

* Add all subject to system privacy

* Basic API Privacy

* Name privacy in logs

* update todo

* remove CheckReadMemberPermission

* Added name privacy to log embed

* update todo

* Update todo

* Update api to handle privacy

* update todo

* Update systemlist full to respect privacy (as well as system list)

* include colour as option for member privacy subject

* move todo file (why was it there?)

* Update TODO.md

* Update TODO.md

* Update TODO.md

* Deleted to create pr

* Update command usage and add to the command tree

* Make api respect created privacy

* Add editing privacy through the api

* Fix pronoun privacy field in api

* Fix info leak of display name in api

* deprecate privacy field in api

* Deprecate privacy diffrently

* Update API

* Update documentation

* Update documentation

* Remove comment in yml

* Update userguide

* Update migration (fix typo in 5.sql too)

* Sanatize names

* some full stops

* Fix after merge

* update migration

* update schema version

* update edit command

* update privacy filter

* fix a dumb mistake

* clarify on what name privacy does

* make it easier on someone else

* Update docs

* Comment out unused code

* Add aliases for `member privacy all public` and `member privacy all private`
This commit is contained in:
BeeFox-sys
2020-06-18 05:31:39 +10:00
committed by GitHub
parent 627f544ee8
commit 721a4502bb
19 changed files with 389 additions and 95 deletions

View File

@@ -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 <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 <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 <name> private" == always private, "pk;m <name> 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 <member> privacy <subject> <level>`\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)