Add member avatar privacy

This commit is contained in:
Ske 2020-06-20 16:00:50 +02:00
parent 27c8100cac
commit ae4e8f97d0
13 changed files with 61 additions and 14 deletions

View File

@ -50,7 +50,7 @@ namespace PluralKit.API
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("avatar_url", member.AvatarPrivacy.CanAccess(ctx) ? member.AvatarUrl : null);
o.Add("description", member.DescriptionPrivacy.CanAccess(ctx) ? member.Description : null);
var tagArray = new JArray();
@ -67,6 +67,7 @@ namespace PluralKit.API
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("avatar_privacy", ctx == LookupContext.ByOwner ? (member.AvatarPrivacy == 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);
@ -122,6 +123,7 @@ namespace PluralKit.API
member.MemberVisibility = plevel;
member.NamePrivacy = plevel;
member.AvatarPrivacy = plevel;
member.DescriptionPrivacy = plevel;
member.BirthdayPrivacy = plevel;
member.PronounPrivacy = plevel;
@ -133,6 +135,7 @@ namespace PluralKit.API
if (o.ContainsKey("visibility")) member.MemberVisibility = o.Value<string>("visibility").ParsePrivacy("member");
if (o.ContainsKey("name_privacy")) member.NamePrivacy = o.Value<string>("name_privacy").ParsePrivacy("member");
if (o.ContainsKey("description_privacy")) member.DescriptionPrivacy = o.Value<string>("description_privacy").ParsePrivacy("member");
if (o.ContainsKey("avatar_privacy")) member.AvatarPrivacy = o.Value<string>("avatar_privacy").ParsePrivacy("member");
if (o.ContainsKey("birthday_privacy")) member.BirthdayPrivacy = o.Value<string>("birthday_privacy").ParsePrivacy("member");
if (o.ContainsKey("pronoun_privacy")) member.PronounPrivacy = o.Value<string>("pronoun_privacy").ParsePrivacy("member");
// if (o.ContainsKey("color_privacy")) member.ColorPrivacy = o.Value<string>("color_privacy").ParsePrivacy("member");

View File

@ -625,6 +625,19 @@ components:
Because of this, there is no way for an unauthorized user to tell the difference between a private description and a `null` description - this is intentional.
example: public
avatar_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"
- description: |
The member's current avatar privacy setting, either "public" or "private".
If this is set to "private", the field `avatar_url` will be returned as `null` on all requests not authorized with this system's token.
In addition, this field will be returned as `null` if the request is not authorized with this system's token.
Because of this, there is no way for an unauthorized user to tell the difference between a private avatar and a `null` avatar - this is intentional.
example: public
pronouns_privacy:
allOf:
- $ref: "#/components/schemas/PrivacySetting"

View File

@ -47,7 +47,8 @@ namespace PluralKit.Bot
var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar";
var currentValue = location == AvatarLocation.Member ? target.AvatarUrl : guildData?.AvatarUrl;
if (string.IsNullOrEmpty(currentValue))
var canAccess = location != AvatarLocation.Member || target.AvatarPrivacy.CanAccess(ctx.LookupContextFor(target));
if (string.IsNullOrEmpty(currentValue) && !canAccess)
{
if (location == AvatarLocation.Member)
{

View File

@ -378,12 +378,13 @@ namespace PluralKit.Bot
.WithTitle($"Current privacy settings for {member.NameFor(ctx)}")
.AddField("Name (replaces name with display name if member has one)",PrivacyLevelString(member.NamePrivacy))
.AddField("Description", PrivacyLevelString(member.DescriptionPrivacy))
.AddField("Avatar", PrivacyLevelString(member.AvatarPrivacy))
.AddField("Birthday", PrivacyLevelString(member.BirthdayPrivacy))
.AddField("Pronouns", PrivacyLevelString(member.PronounPrivacy))
// .AddField("Color", PrivacyLevelString(target.ColorPrivacy))
.AddField("Meta (message count, last front, last message)", PrivacyLevelString(member.MetadataPrivacy))
.AddField("Visibility", PrivacyLevelString(member.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`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`.");
.WithDescription("To edit privacy settings, use the command:\n`pk;member <member> privacy <subject> <level>`\n\n- `subject` is one of `name`, `description`, `avatar`, `birthday`, `pronouns`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`.");
return eb.Build();
}
@ -432,6 +433,7 @@ namespace PluralKit.Bot
{
(MemberPrivacySubject.Name, PrivacyLevel.Private) => "This member's name is now hidden from other systems, and will be replaced by the member's display name.",
(MemberPrivacySubject.Description, PrivacyLevel.Private) => "This member's description is now hidden from other systems.",
(MemberPrivacySubject.Avatar, PrivacyLevel.Private) => "This member's avatar is now hidden from other systems.",
(MemberPrivacySubject.Birthday, PrivacyLevel.Private) => "This member's birthday is now hidden from other systems.",
(MemberPrivacySubject.Pronouns, PrivacyLevel.Private) => "This member's pronouns are now hidden from other systems.",
(MemberPrivacySubject.Metadata, PrivacyLevel.Private) => "This member's metadata (eg. created timestamp, message count, etc) is now hidden from other systems.",
@ -439,6 +441,7 @@ namespace PluralKit.Bot
(MemberPrivacySubject.Name, PrivacyLevel.Public) => "This member's name is no longer hidden from other systems.",
(MemberPrivacySubject.Description, PrivacyLevel.Public) => "This member's description is no longer hidden from other systems.",
(MemberPrivacySubject.Avatar, PrivacyLevel.Public) => "This member's avatar is no longer hidden from other systems.",
(MemberPrivacySubject.Birthday, PrivacyLevel.Public) => "This member's birthday is no longer hidden from other systems.",
(MemberPrivacySubject.Pronouns, PrivacyLevel.Public) => "This member's pronouns are no longer hidden other systems.",
(MemberPrivacySubject.Metadata, PrivacyLevel.Public) => "This member's metadata (eg. created timestamp, message count, etc) is no longer hidden from other systems.",
@ -462,7 +465,7 @@ namespace PluralKit.Bot
}
else
{
var subjectList = "`name`, `description`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all`";
var subjectList = "`name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all`";
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument().SanitizeMentions()}` (must be {subjectList}).");
}

View File

@ -72,8 +72,8 @@ namespace PluralKit.Bot {
var timestamp = DiscordUtils.SnowflakeToInstant(messageId);
var name = member.NameFor(LookupContext.ByNonOwner);
return new DiscordEmbedBuilder()
.WithAuthor($"#{channel.Name}: {name}", iconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarUrl))
.WithThumbnailUrl(member.AvatarUrl)
.WithAuthor($"#{channel.Name}: {name}", iconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarFor(LookupContext.ByNonOwner)))
.WithThumbnailUrl(member.AvatarFor(LookupContext.ByNonOwner))
.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}")
.WithTimestamp(timestamp.ToDateTimeOffset())
@ -103,7 +103,7 @@ namespace PluralKit.Bot {
var guildSettings = guild != null ? await _db.Execute(c => c.QueryOrInsertMemberGuildConfig(guild.Id, member.Id)) : null;
var guildDisplayName = guildSettings?.DisplayName;
var avatar = guildSettings?.AvatarUrl ?? member.AvatarUrl;
var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx);
var proxyTagsStr = string.Join('\n', member.ProxyTags.Select(t => $"`{t.ProxyString}`"));
@ -117,7 +117,7 @@ namespace PluralKit.Bot {
var description = "";
if (member.MemberVisibility == PrivacyLevel.Private) description += "*(this member is hidden)*\n";
if (guildSettings?.AvatarUrl != null)
if (member.AvatarUrl != null)
if (member.AvatarFor(ctx) != null)
description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl}) to see the global avatar)*\n";
else
description += "*(this member has a server-specific avatar set)*\n";
@ -179,7 +179,7 @@ namespace PluralKit.Bot {
// Put it all together
var eb = new DiscordEmbedBuilder()
.WithAuthor(msg.Member.NameFor(ctx), iconUrl: DiscordUtils.WorkaroundForUrlBug(msg.Member.AvatarUrl))
.WithAuthor(msg.Member.NameFor(ctx), iconUrl: DiscordUtils.WorkaroundForUrlBug(msg.Member.AvatarFor(ctx)))
.WithDescription(serverMsg?.Content?.NormalizeLineEndSpacing() ?? "*(message contents deleted or inaccessible)*")
.WithImageUrl(serverMsg?.Attachments?.FirstOrDefault()?.Url)
.AddField("System",

View File

@ -7,6 +7,9 @@ namespace PluralKit.Bot
public static string NameFor(this PKMember member, Context ctx) =>
member.NameFor(ctx.LookupContextFor(member));
public static string AvatarFor(this PKMember member, Context ctx) =>
member.AvatarFor(ctx.LookupContextFor(member));
public static string DisplayName(this PKMember member) =>
member.DisplayName ?? member.Name;
}

View File

@ -2,6 +2,7 @@
-- 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 avatar_privacy integer check (avatar_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;
@ -10,6 +11,7 @@ alter table members add column metadata_privacy integer check (metadata_privacy
-- Transfer existing settings --
update members set description_privacy = member_privacy;
update members set name_privacy = member_privacy;
update members set avatar_privacy = member_privacy;
update members set birthday_privacy = member_privacy;
update members set pronoun_privacy = member_privacy;
update members set metadata_privacy = member_privacy;

View File

@ -4,5 +4,8 @@
{
public static string NameFor(this PKMember member, LookupContext ctx) =>
member.NamePrivacy.CanAccess(ctx) ? member.Name : member.DisplayName ?? member.Name;
public static string AvatarFor(this PKMember member, LookupContext ctx) =>
member.AvatarPrivacy.CanAccess(ctx) ? member.AvatarUrl : null;
}
}

View File

@ -25,6 +25,7 @@ namespace PluralKit.Core {
public PrivacyLevel MemberVisibility { get; set; }
public PrivacyLevel DescriptionPrivacy { get; set; }
public PrivacyLevel AvatarPrivacy { 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; }

View File

@ -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_visibility = @MemberVisibility, description_privacy = @DescriptionPrivacy, name_privacy = @NamePrivacy, birthday_privacy = @BirthdayPrivacy, pronoun_privacy = @PronounPrivacy, metadata_privacy = @MetadataPrivacy 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, avatar_privacy = @AvatarPrivacy, birthday_privacy = @BirthdayPrivacy, pronoun_privacy = @PronounPrivacy, metadata_privacy = @MetadataPrivacy where id = @Id", member);
_logger.Information("Updated member {@Member}", member);
}

View File

@ -6,6 +6,7 @@ namespace PluralKit.Core
Visibility,
Name,
Description,
Avatar,
Birthday,
Pronouns,
Metadata
@ -17,6 +18,7 @@ namespace PluralKit.Core
{
MemberPrivacySubject.Name => "name",
MemberPrivacySubject.Description => "description",
MemberPrivacySubject.Avatar => "avatar",
MemberPrivacySubject.Pronouns => "pronouns",
MemberPrivacySubject.Birthday => "birthday",
MemberPrivacySubject.Metadata => "metadata",
@ -31,6 +33,7 @@ namespace PluralKit.Core
{
MemberPrivacySubject.Name => member.NamePrivacy = level,
MemberPrivacySubject.Description => member.DescriptionPrivacy = level,
MemberPrivacySubject.Avatar => member.AvatarPrivacy = level,
MemberPrivacySubject.Pronouns => member.PronounPrivacy = level,
MemberPrivacySubject.Birthday => member.BirthdayPrivacy= level,
MemberPrivacySubject.Metadata => member.MetadataPrivacy = level,
@ -43,6 +46,7 @@ namespace PluralKit.Core
{
member.NamePrivacy = level;
member.DescriptionPrivacy = level;
member.AvatarPrivacy = level;
member.PronounPrivacy = level;
member.BirthdayPrivacy = level;
member.MetadataPrivacy = level;
@ -62,6 +66,12 @@ namespace PluralKit.Core
case "info":
subject = MemberPrivacySubject.Description;
break;
case "avatar":
case "pfp":
case "pic":
case "icon":
subject = MemberPrivacySubject.Avatar;
break;
case "birthday":
case "birth":
case "bday":

View File

@ -449,10 +449,11 @@ 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 are also six options for configuring member privacy;
There are also seven options for configuring member privacy;
- Name
- Description
- Avatar
- Birthday
- Pronouns
- Metadata *(message count, creation date, etc)*
@ -468,8 +469,8 @@ To update a members privacy you can use the command:
member <member> privacy <subject> <level>
where `<member>` is the name or the id of a member in your system, `<subject>` is either `name`, `description`, `birthday`, `pronouns`, `metadata`, or `visiblity` corresponding to the options above, and `<level>` is either `public` or `private`. `<subject>` 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.
where `<member>` is the name or the id of a member in your system, `<subject>` is either `name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, or `visiblity` corresponding to the options above, and `<level>` is either `public` or `private`. `<subject>` can also be `all` in order to change all subjects at once.
`metadata` will affect the message count, the date created, the last fronted, and the last message information.
For example:

View File

@ -68,6 +68,7 @@ The following three models (usually represented in JSON format) represent the va
|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.|
|avatar_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.|
|metadata_privacy|string?|Yes|Patching with `private` will set it to private; `public` or `null` will set it to public.|
@ -234,6 +235,7 @@ If the system has chosen to hide its current fronters, this will return `403 For
"visibility": null,
"name_privacy": null,
"description_privacy": null,
"avatar_privacy": null,
"birthday_privacy": null,
"pronoun_privacy": null,
"metadata_privacy": null,
@ -322,6 +324,7 @@ If this member is marked private, and the request isn't authenticated with the m
"visibility": "public",
"name_privacy": "public",
"description_privacy": "private",
"avatar_privacy": "private",
"birthday_privacy": "private",
"pronoun_privacy": "public",
"metadata_privacy": "public"
@ -349,6 +352,7 @@ Creates a new member with the information given. Missing fields (except for name
"visibility": "public",
"name_privacy": "public",
"description_privacy": "private",
"avatar_privacy": "private",
"birthday_privacy": "private",
"pronoun_privacy": "public",
"metadata_privacy": "private"
@ -400,6 +404,7 @@ Edits a member's information. Missing fields will keep their current values. Wil
"visibility": "public",
"name_privacy": "public",
"description_privacy": "private",
"avatar_privacy": "private",
"birthday_privacy": "private",
"pronoun_privacy": "public",
"metadata_privacy": "private"
@ -424,6 +429,7 @@ Edits a member's information. Missing fields will keep their current values. Wil
"visibility": "public",
"name_privacy": "public",
"description_privacy": "private",
"avatar_privacy": "private",
"birthday_privacy": "private",
"pronoun_privacy": "public",
"metadata_privacy": "private"
@ -505,6 +511,7 @@ The returned system and member's privacy settings will be respected, and as such
"visibility": "public",
"name_privacy": "public",
"description_privacy": "private",
"avatar_privacy": "private",
"birthday_privacy": "private",
"pronoun_privacy": "public",
"metadata_privacy": "private"
@ -514,7 +521,7 @@ The returned system and member's privacy settings will be respected, and as such
## Version history
* 2020-06-17 (v1.1)
* The API now has values for granular member privacy. The new fields are as follows: `visibility`, `name_privacy`, `description_privacy`, `birthday_privacy`, `pronoun_privacy`, `metadata_privacy`. All are strings and accept the values of `public`, `private` and `null`
* The API now has values for granular member privacy. The new fields are as follows: `visibility`, `name_privacy`, `description_privacy`, `avatar_privacy`, `birthday_privacy`, `pronoun_privacy`, `metadata_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's still returned (mirroring the `visibility` field), and writing to it will write to *all privacy options*.
* 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)