Refactor system/member privacy commands

This commit is contained in:
Ske 2020-07-08 00:46:58 +02:00
parent 1449234a84
commit 9f523b3c5f
8 changed files with 249 additions and 196 deletions

View File

@ -372,29 +372,6 @@ namespace PluralKit.Bot
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
}
private DiscordEmbed CreatePrivacyEmbed(Context ctx, PKMember member)
{
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 {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`, `avatar`, `birthday`, `pronouns`, `created`, `messages`, `visibility`, or `all`\n- `level` is either `public` or `private`.");
return eb.Build();
}
public async Task Privacy(Context ctx, PKMember target, PrivacyLevel? newValueFromCommand)
{
if (ctx.System == null) throw Errors.NoSystemError;
@ -403,7 +380,17 @@ namespace PluralKit.Bot
// Display privacy settings
if (!ctx.HasNext() && newValueFromCommand == null)
{
await ctx.Reply(embed: CreatePrivacyEmbed(ctx, target));
await ctx.Reply(embed: new DiscordEmbedBuilder()
.WithTitle($"Current privacy settings for {target.NameFor(ctx)}")
.AddField("Name (replaces name with display name if member has one)",target.NamePrivacy.Explanation())
.AddField("Description", target.DescriptionPrivacy.Explanation())
.AddField("Avatar", target.AvatarPrivacy.Explanation())
.AddField("Birthday", target.BirthdayPrivacy.Explanation())
.AddField("Pronouns", target.PronounPrivacy.Explanation())
.AddField("Meta (message count, last front, last message)",target.MetadataPrivacy.Explanation())
.AddField("Visibility", target.MemberVisibility.Explanation())
.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`.")
.Build());
return;
}
@ -412,37 +399,33 @@ namespace PluralKit.Bot
if (ctx.Guild != null)
guildSettings = await _db.Execute(c => c.QueryOrInsertMemberGuildConfig(ctx.Guild.Id, target.Id));
// Set Privacy Settings
PrivacyLevel PopPrivacyLevel(string subjectName)
async Task SetAll(PrivacyLevel level)
{
if (ctx.Match("public", "show", "shown", "visible"))
return PrivacyLevel.Public;
if (ctx.Match("private", "hide", "hidden"))
return PrivacyLevel.Private;
if (!ctx.HasNext())
throw new PKSyntaxError($"You must pass a privacy level for `{subjectName}` (`public` or `private`)");
throw new PKSyntaxError($"Invalid privacy level `{ctx.PopArgument()}` (must be `public` or `private`).");
await _db.Execute(c => c.UpdateMember(target.Id, new MemberPatch().WithAllPrivacy(level)));
if (level == PrivacyLevel.Private)
await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see nothing on the member card.");
else
await ctx.Reply($"{Emojis.Success} All {target.NameFor(ctx)}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the member card.");
}
// See if we have a subject given
PrivacyLevel newLevel;
if (PrivacyUtils.TryParseMemberPrivacy(ctx.PeekArgument(), out var subject))
{
// We peeked before, pop it now
ctx.PopArgument();
// Read the privacy level from args
newLevel = PopPrivacyLevel(subject.Name());
// Set the level on the given subject
var patch = new MemberPatch();
patch.SetPrivacy(subject, newLevel);
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
// Print response
var explanation = (subject, newLevel) switch
async Task SetLevel(MemberPrivacySubject subject, PrivacyLevel level)
{
await _db.Execute(c => c.UpdateMember(target.Id, new MemberPatch().WithPrivacy(subject, level)));
var subjectName = subject switch
{
MemberPrivacySubject.Name => "name privacy",
MemberPrivacySubject.Description => "description privacy",
MemberPrivacySubject.Avatar => "avatar privacy",
MemberPrivacySubject.Pronouns => "pronoun privacy",
MemberPrivacySubject.Birthday => "birthday privacy",
MemberPrivacySubject.Metadata => "metadata privacy",
MemberPrivacySubject.Visibility => "visibility",
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
};
var explanation = (subject, level) switch
{
(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.",
@ -460,37 +443,24 @@ namespace PluralKit.Bot
(MemberPrivacySubject.Metadata, PrivacyLevel.Public) => "This member's metadata (eg. created timestamp, message count, etc) is no longer hidden from other systems.",
(MemberPrivacySubject.Visibility, PrivacyLevel.Public) => "This member is no longer hidden from member lists.",
_ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {newLevel})")
_ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})")
};
await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s {subject.Name()} has been set to **{newLevel.LevelName()}**. {explanation}");
}
else if (ctx.Match("all") || newValueFromCommand != null)
{
newLevel = newValueFromCommand ?? PopPrivacyLevel("all");
await ctx.Reply($"{Emojis.Success} {target.NameFor(ctx)}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}");
var patch = new MemberPatch();
patch.SetAllPrivacy(newLevel);
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
// Name privacy only works given a display name
if (subject == MemberPrivacySubject.Name && level == PrivacyLevel.Private && target.DisplayName == null)
await ctx.Reply($"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**.");
if(newLevel == PrivacyLevel.Private)
await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see nothing on the member card.");
else
await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see everything on the member card.");
// Avatar privacy doesn't apply when proxying if no server avatar is set
if (subject == MemberPrivacySubject.Avatar && level == PrivacyLevel.Private && guildSettings?.AvatarUrl == null)
await ctx.Reply($"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `pk;member {target.Hid} serveravatar`");
}
if (ctx.Match("all") || newValueFromCommand != null)
await SetAll(newValueFromCommand ?? ctx.PopPrivacyLevel());
else
{
var subjectList = "`name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all`";
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be {subjectList}).");
}
// Name privacy only works given a display name
if (subject == MemberPrivacySubject.Name && newLevel == PrivacyLevel.Private && target.DisplayName == null)
await ctx.Reply($"{Emojis.Warn} This member does not have a display name set, and name privacy **will not take effect**.");
// Avatar privacy doesn't apply when proxying if no server avatar is set
if (subject == MemberPrivacySubject.Avatar && newLevel == PrivacyLevel.Private &&
guildSettings?.AvatarUrl == null)
await ctx.Reply($"{Emojis.Warn} This member does not have a server avatar set, so *proxying* will **still show the member avatar**. If you want to hide your avatar when proxying here, set a server avatar: `pk;member {target.Hid} serveravatar`");
await SetLevel(ctx.PopMemberPrivacySubject(), ctx.PopPrivacyLevel());
}
public async Task Delete(Context ctx, PKMember target)

View File

@ -0,0 +1,39 @@
using PluralKit.Core;
namespace PluralKit.Bot
{
public static class ContextPrivacyExt
{
public static PrivacyLevel PopPrivacyLevel(this Context ctx)
{
if (ctx.Match("public", "show", "shown", "visible"))
return PrivacyLevel.Public;
if (ctx.Match("private", "hide", "hidden"))
return PrivacyLevel.Private;
if (!ctx.HasNext())
throw new PKSyntaxError("You must pass a privacy level (`public` or `private`)");
throw new PKSyntaxError($"Invalid privacy level `{ctx.PopArgument()}` (must be `public` or `private`).");
}
public static SystemPrivacySubject PopSystemPrivacySubject(this Context ctx)
{
if (!SystemPrivacyUtils.TryParseSystemPrivacy(ctx.PeekArgument(), out var subject))
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be `description`, `members`, `front`, `fronthistory`, or `all`).");
ctx.PopArgument();
return subject;
}
public static MemberPrivacySubject PopMemberPrivacySubject(this Context ctx)
{
if (!MemberPrivacyUtils.TryParseMemberPrivacy(ctx.PeekArgument(), out var subject))
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be `name`, `description`, `avatar`, `birthday`, `pronouns`, `metadata`, `visibility`, or `all).");
ctx.PopArgument();
return subject;
}
}
}

View File

@ -13,6 +13,8 @@ using NodaTime.TimeZones;
using PluralKit.Core;
using Sentry.Protocol;
namespace PluralKit.Bot
{
public class SystemEdit
@ -261,93 +263,62 @@ namespace PluralKit.Bot
{
ctx.CheckSystem();
if (!ctx.HasNext())
Task PrintEmbed()
{
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 your system")
.AddField("Description", PrivacyLevelString(ctx.System.DescriptionPrivacy))
.AddField("Member list", PrivacyLevelString(ctx.System.MemberListPrivacy))
.AddField("Current fronter(s)", PrivacyLevelString(ctx.System.FrontPrivacy))
.AddField("Front/switch history", PrivacyLevelString(ctx.System.FrontHistoryPrivacy))
.AddField("Description", ctx.System.DescriptionPrivacy.Explanation())
.AddField("Member list", ctx.System.MemberListPrivacy.Explanation())
.AddField("Current fronter(s)", ctx.System.FrontPrivacy.Explanation())
.AddField("Front/switch history", ctx.System.FrontHistoryPrivacy.Explanation())
.WithDescription("To edit privacy settings, use the command:\n`pk;system privacy <subject> <level>`\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;
return ctx.Reply(embed: eb.Build());
}
PrivacyLevel PopPrivacyLevel(string subject, out string levelStr, out string levelExplanation)
async Task SetLevel(SystemPrivacySubject subject, PrivacyLevel level)
{
if (ctx.Match("public", "show", "shown", "visible"))
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, new SystemPatch().WithPrivacy(subject, level)));
var levelExplanation = level switch
{
levelStr = "public";
levelExplanation = "be able to query";
return PrivacyLevel.Public;
}
PrivacyLevel.Public => "be able to query",
PrivacyLevel.Private => "*not* be able to query",
_ => ""
};
if (ctx.Match("private", "hide", "hidden"))
var subjectStr = subject switch
{
levelStr = "private";
levelExplanation = "*not* be able to query";
return PrivacyLevel.Private;
}
SystemPrivacySubject.Description => "description",
SystemPrivacySubject.Front => "front",
SystemPrivacySubject.FrontHistory => "front history",
SystemPrivacySubject.MemberList => "member list",
_ => ""
};
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()}` (must be `public` or `private`).");
var msg = $"System {subjectStr} privacy has been set to **{level.LevelName()}**. Other accounts will now {levelExplanation} your system {subjectStr}.";
await ctx.Reply($"{Emojis.Success} {msg}");
}
string levelStr, levelExplanation, subjectStr;
var subjectList = "`description`, `members`, `front`, `fronthistory`, or `all`";
SystemPatch patch = new SystemPatch();
if (ctx.Match("description", "desc", "text", "info"))
async Task SetAll(PrivacyLevel level)
{
subjectStr = "description";
patch.DescriptionPrivacy = PopPrivacyLevel("description", out levelStr, out levelExplanation);
}
else if (ctx.Match("members", "memberlist", "list", "mlist"))
{
subjectStr = "member list";
patch.MemberListPrivacy = PopPrivacyLevel("members", out levelStr, out levelExplanation);
}
else if (ctx.Match("front", "fronter"))
{
subjectStr = "fronter(s)";
patch.FrontPrivacy = PopPrivacyLevel("front", out levelStr, out levelExplanation);
}
else if (ctx.Match("switch", "switches", "fronthistory", "fh"))
{
subjectStr = "front history";
patch.FrontHistoryPrivacy = PopPrivacyLevel("fronthistory", out levelStr, out levelExplanation);
}
else if (ctx.Match("all")){
subjectStr = "all";
PrivacyLevel level = PopPrivacyLevel("all", out levelStr, out levelExplanation);
patch.DescriptionPrivacy = level;
patch.MemberListPrivacy = level;
patch.FrontPrivacy = level;
patch.FrontHistoryPrivacy = level;
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, new SystemPatch().WithAllPrivacy(level)));
var msg = level switch
{
PrivacyLevel.Private => $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now not be able to view your member list, front history, or system description.",
PrivacyLevel.Public => $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now be able to view everything.",
_ => ""
};
await ctx.Reply($"{Emojis.Success} {msg}");
}
if (!ctx.HasNext())
await PrintEmbed();
else if (ctx.Match("all"))
await SetAll(ctx.PopPrivacyLevel());
else
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be {subjectList}).");
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
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}.");
await SetLevel(ctx.PopSystemPrivacySubject(), ctx.PopPrivacyLevel());
}
public async Task SystemPing(Context ctx)

View File

@ -0,0 +1,9 @@
namespace PluralKit.Core
{
public enum LookupContext
{
ByOwner,
ByNonOwner,
API
}
}

View File

@ -2,7 +2,8 @@
namespace PluralKit.Core
{
public enum MemberPrivacySubject {
public enum MemberPrivacySubject
{
Visibility,
Name,
Description,
@ -11,48 +12,34 @@ namespace PluralKit.Core
Pronouns,
Metadata
}
public static class PrivacyUtils
{
public static string Name(this MemberPrivacySubject subject) => subject switch
{
MemberPrivacySubject.Name => "name",
MemberPrivacySubject.Description => "description",
MemberPrivacySubject.Avatar => "avatar",
MemberPrivacySubject.Pronouns => "pronouns",
MemberPrivacySubject.Birthday => "birthday",
MemberPrivacySubject.Metadata => "metadata",
MemberPrivacySubject.Visibility => "visibility",
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
};
public static void SetPrivacy(this MemberPatch member, MemberPrivacySubject subject, PrivacyLevel level)
public static class MemberPrivacyUtils
{
public static MemberPatch WithPrivacy(this MemberPatch member, MemberPrivacySubject subject, PrivacyLevel level)
{
// what do you mean switch expressions can't be statements >.>
_ = subject switch
{
MemberPrivacySubject.Name => member.NamePrivacy = Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Description => member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Avatar => member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Pronouns => member.PronounPrivacy = Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Birthday => member.BirthdayPrivacy= Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Metadata => member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level),
MemberPrivacySubject.Visibility => member.Visibility = Partial<PrivacyLevel>.Present(level),
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,
MemberPrivacySubject.Visibility => member.Visibility = level,
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
};
return member;
}
public static void SetAllPrivacy(this MemberPatch member, PrivacyLevel level)
public static MemberPatch WithAllPrivacy(this MemberPatch member, PrivacyLevel level)
{
member.NamePrivacy = Partial<PrivacyLevel>.Present(level);
member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level);
member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level);
member.PronounPrivacy = Partial<PrivacyLevel>.Present(level);
member.BirthdayPrivacy = Partial<PrivacyLevel>.Present(level);
member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level);
member.Visibility = Partial<PrivacyLevel>.Present(level);
foreach (var subject in Enum.GetValues(typeof(MemberPrivacySubject)))
member.WithPrivacy((MemberPrivacySubject) subject, level);
return member;
}
public static bool TryParseMemberPrivacy(string input, out MemberPrivacySubject subject)
{
switch (input.ToLowerInvariant())
@ -89,7 +76,7 @@ namespace PluralKit.Core
subject = MemberPrivacySubject.Metadata;
break;
case "visibility":
case "hidden":
case "hidden":
case "shown":
case "visible":
case "list":
@ -99,7 +86,8 @@ namespace PluralKit.Core
subject = MemberPrivacySubject.Name;
return false;
}
return true;
}
}
}
}

View File

@ -1,11 +1,7 @@
namespace PluralKit.Core
{
public enum PrivacyLevel
{
Public = 1,
Private = 2
}
using System;
namespace PluralKit.Core
{
public static class PrivacyExt
{
public static bool CanAccess(this PrivacyLevel level, LookupContext ctx) =>
@ -16,6 +12,14 @@
public static T Get<T>(this PrivacyLevel level, LookupContext ctx, T input, T fallback = default) =>
level.CanAccess(ctx) ? input : fallback;
public static string Explanation(this 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)
};
public static bool TryGet<T>(this PrivacyLevel level, LookupContext ctx, T input, out T output, T absentValue = default)
{
@ -29,11 +33,4 @@
return true;
}
}
public enum LookupContext
{
ByOwner,
ByNonOwner,
API
}
}

View File

@ -0,0 +1,8 @@
namespace PluralKit.Core
{
public enum PrivacyLevel
{
Public = 1,
Private = 2
}
}

View File

@ -0,0 +1,71 @@
using System;
namespace PluralKit.Core
{
public enum SystemPrivacySubject
{
Description,
MemberList,
Front,
FrontHistory
}
public static class SystemPrivacyUtils
{
public static SystemPatch WithPrivacy(this SystemPatch system, SystemPrivacySubject subject, PrivacyLevel level)
{
// what do you mean switch expressions can't be statements >.>
_ = subject switch
{
SystemPrivacySubject.Description => system.DescriptionPrivacy = level,
SystemPrivacySubject.Front => system.FrontPrivacy = level,
SystemPrivacySubject.FrontHistory => system.FrontHistoryPrivacy = level,
SystemPrivacySubject.MemberList => system.MemberListPrivacy = level,
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
};
return system;
}
public static SystemPatch WithAllPrivacy(this SystemPatch system, PrivacyLevel level)
{
foreach (var subject in Enum.GetValues(typeof(SystemPrivacySubject)))
WithPrivacy(system, (SystemPrivacySubject) subject, level);
return system;
}
public static bool TryParseSystemPrivacy(string input, out SystemPrivacySubject subject)
{
switch (input.ToLowerInvariant())
{
case "description":
case "desc":
case "text":
case "info":
subject = SystemPrivacySubject.Description;
break;
case "members":
case "memberlist":
case "list":
case "mlist":
subject = SystemPrivacySubject.MemberList;
break;
case "fronter":
case "fronters":
case "front":
subject = SystemPrivacySubject.Front;
break;
case "switch":
case "switches":
case "fronthistory":
case "fh":
subject = SystemPrivacySubject.FrontHistory;
break;
default:
subject = default;
return false;
}
return true;
}
}
}