From 5e28e0aba16f81c340e888de03d55a89c58aee05 Mon Sep 17 00:00:00 2001 From: Ske Date: Sat, 18 Jul 2020 13:53:02 +0200 Subject: [PATCH] Add group privacy command/structures --- PluralKit.Bot/Commands/CommandTree.cs | 7 ++ PluralKit.Bot/Commands/Groups.cs | 79 +++++++++++++++++-- .../Commands/Privacy/ContextPrivacyExt.cs | 9 +++ PluralKit.Bot/Services/EmbedService.cs | 2 +- PluralKit.Core/Models/ModelExtensions.cs | 6 ++ .../Models/Privacy/GroupPrivacySubject.cs | 66 ++++++++++++++++ 6 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index f219ea18..44edbb8a 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -53,6 +53,7 @@ namespace PluralKit.Bot public static Command GroupDesc = new Command("group description", "group description [description]", "Changes a group's description"); public static Command GroupAdd = new Command("group add", "group add [member 2] [member 3...]", "Adds one or more members to a group"); public static Command GroupRemove = new Command("group remove", "group remove [member 2] [member 3...]", "Removes one or more members from a group"); + public static Command GroupPrivacy = new Command("group privacy", "group privacy ", "Changes a group'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"); @@ -342,6 +343,12 @@ namespace PluralKit.Bot await ctx.Execute(GroupRemove, g => g.AddRemoveMembers(ctx, target, Groups.AddRemoveOperation.Remove)); else if (ctx.Match("members", "list", "ms", "l")) await ctx.Execute(GroupMemberList, g => g.ListGroupMembers(ctx, target)); + else if (ctx.Match("privacy")) + await ctx.Execute(GroupPrivacy, g => g.GroupPrivacy(ctx, target, null)); + else if (ctx.Match("public", "pub")) + await ctx.Execute(GroupPrivacy, g => g.GroupPrivacy(ctx, target, PrivacyLevel.Public)); + else if (ctx.Match("private", "priv")) + await ctx.Execute(GroupPrivacy, g => g.GroupPrivacy(ctx, target, PrivacyLevel.Private)); else if (!ctx.HasNext()) await ctx.Execute(GroupInfo, g => g.ShowGroupCard(ctx, target)); else diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index d381dcdb..7d50efaf 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -107,7 +108,11 @@ namespace PluralKit.Bot // TODO: integrate with the normal "search" system await using var conn = await _db.Obtain(); - var groups = (await conn.QueryGroupsInSystem(system.Id)).ToList(); + var pctx = LookupContext.ByNonOwner; + if (ctx.MatchFlag("a", "all") && system.Id == ctx.System.Id) + pctx = LookupContext.ByOwner; + + var groups = (await conn.QueryGroupsInSystem(system.Id)).Where(g => g.Visibility.CanAccess(pctx)).ToList(); if (groups.Count == 0) { if (system.Id == ctx.System?.Id) @@ -139,6 +144,7 @@ namespace PluralKit.Bot await using var conn = await _db.Obtain(); var system = await GetGroupSystem(ctx, target, conn); + var pctx = ctx.LookupContextFor(system); var memberCount = await conn.QueryGroupMemberCount(target.Id, PrivacyLevel.Public); var nameField = target.Name; @@ -146,7 +152,7 @@ namespace PluralKit.Bot nameField = $"{nameField} ({system.Name})"; var eb = new DiscordEmbedBuilder() - .WithAuthor(nameField) + .WithAuthor(nameField, iconUrl: DiscordUtils.WorkaroundForUrlBug(target.IconFor(pctx))) .WithFooter($"System ID: {system.Hid} | Group ID: {target.Hid} | Created on {target.Created.FormatZoned(system)}"); if (memberCount == 0) @@ -154,8 +160,11 @@ namespace PluralKit.Bot else eb.AddField($"Members ({memberCount})", $"(see `pk;group {target.Hid} list`)", true); - if (target.Description != null) - eb.AddField("Description", target.Description); + if (target.DescriptionFor(pctx) is {} desc) + eb.AddField("Description", desc); + + if (target.IconFor(pctx) is {} icon) + eb.WithThumbnail(icon); await ctx.Reply(embed: eb.Build()); } @@ -224,6 +233,66 @@ namespace PluralKit.Bot throw new PKSyntaxError("You must pass one or more members."); return members; } + + public async Task GroupPrivacy(Context ctx, PKGroup target, PrivacyLevel? newValueFromCommand) + { + ctx.CheckSystem().CheckOwnGroup(target); + // Display privacy settings + if (!ctx.HasNext() && newValueFromCommand == null) + { + await ctx.Reply(embed: new DiscordEmbedBuilder() + .WithTitle($"Current privacy settings for {target.Name}") + .AddField("Description", target.DescriptionPrivacy.Explanation()) + .AddField("Icon", target.IconPrivacy.Explanation()) + .AddField("Visibility", target.Visibility.Explanation()) + .WithDescription("To edit privacy settings, use the command:\n`pk;group privacy `\n\n- `subject` is one of `description`, `icon`, `visibility`, or `all`\n- `level` is either `public` or `private`.") + .Build()); + return; + } + + async Task SetAll(PrivacyLevel level) + { + await _db.Execute(c => c.UpdateGroup(target.Id, new GroupPatch().WithAllPrivacy(level))); + + if (level == PrivacyLevel.Private) + await ctx.Reply($"{Emojis.Success} All {target.Name}'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.Name}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the member card."); + } + + async Task SetLevel(GroupPrivacySubject subject, PrivacyLevel level) + { + await _db.Execute(c => c.UpdateGroup(target.Id, new GroupPatch().WithPrivacy(subject, level))); + + var subjectName = subject switch + { + GroupPrivacySubject.Description => "description privacy", + GroupPrivacySubject.Icon => "icon privacy", + GroupPrivacySubject.Visibility => "visibility", + _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") + }; + + var explanation = (subject, level) switch + { + (GroupPrivacySubject.Description, PrivacyLevel.Private) => "This group's description is now hidden from other systems.", + (GroupPrivacySubject.Icon, PrivacyLevel.Private) => "This group's icon is now hidden from other systems.", + (GroupPrivacySubject.Visibility, PrivacyLevel.Private) => "This group is now hidden from group lists and member cards.", + + (GroupPrivacySubject.Description, PrivacyLevel.Public) => "This group's description is no longer hidden from other systems.", + (GroupPrivacySubject.Icon, PrivacyLevel.Public) => "This group's icon is no longer hidden from other systems.", + (GroupPrivacySubject.Visibility, PrivacyLevel.Public) => "This group is no longer hidden from group lists and member cards.", + + _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})") + }; + + await ctx.Reply($"{Emojis.Success} {target.Name}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}"); + } + + if (ctx.Match("all") || newValueFromCommand != null) + await SetAll(newValueFromCommand ?? ctx.PopPrivacyLevel()); + else + await SetLevel(ctx.PopGroupPrivacySubject(), ctx.PopPrivacyLevel()); + } private static async Task GetGroupSystem(Context ctx, PKGroup target, IPKConnection conn) { diff --git a/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs b/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs index 8b774d25..8a0ef845 100644 --- a/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs +++ b/PluralKit.Bot/Commands/Privacy/ContextPrivacyExt.cs @@ -35,5 +35,14 @@ namespace PluralKit.Bot ctx.PopArgument(); return subject; } + + public static GroupPrivacySubject PopGroupPrivacySubject(this Context ctx) + { + if (!GroupPrivacyUtils.TryParseGroupPrivacy(ctx.PeekArgument(), out var subject)) + throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be `description`, `icon`, `visibility`, or `all)."); + + ctx.PopArgument(); + return subject; + } } } \ No newline at end of file diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 50de24e2..ac83eab3 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -109,7 +109,7 @@ namespace PluralKit.Bot { var guildDisplayName = guildSettings?.DisplayName; var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx); - var groups = (await conn.QueryMemberGroups(member.Id)).ToList(); + var groups = (await conn.QueryMemberGroups(member.Id)).Where(g => g.Visibility.CanAccess(ctx)).ToList(); var proxyTagsStr = string.Join('\n', member.ProxyTags.Select(t => $"`` {t.ProxyString} ``")); diff --git a/PluralKit.Core/Models/ModelExtensions.cs b/PluralKit.Core/Models/ModelExtensions.cs index 6092fa24..1c434c70 100644 --- a/PluralKit.Core/Models/ModelExtensions.cs +++ b/PluralKit.Core/Models/ModelExtensions.cs @@ -27,5 +27,11 @@ namespace PluralKit.Core public static int MessageCountFor(this PKMember member, LookupContext ctx) => member.MetadataPrivacy.Get(ctx, member.MessageCount); + + public static string DescriptionFor(this PKGroup group, LookupContext ctx) => + group.DescriptionPrivacy.Get(ctx, group.Description); + + public static string IconFor(this PKGroup group, LookupContext ctx) => + group.IconPrivacy.Get(ctx, group.Icon); } } \ No newline at end of file diff --git a/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs new file mode 100644 index 00000000..63487c16 --- /dev/null +++ b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs @@ -0,0 +1,66 @@ +using System; + +namespace PluralKit.Core +{ + public enum GroupPrivacySubject + { + Description, + Icon, + Visibility + } + + public static class GroupPrivacyUtils + { + public static GroupPatch WithPrivacy(this GroupPatch group, GroupPrivacySubject subject, PrivacyLevel level) + { + // what do you mean switch expressions can't be statements >.> + _ = subject switch + { + GroupPrivacySubject.Description => group.DescriptionPrivacy = level, + GroupPrivacySubject.Icon => group.IconPrivacy = level, + GroupPrivacySubject.Visibility => group.Visibility = level, + _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") + }; + + return group; + } + + public static GroupPatch WithAllPrivacy(this GroupPatch member, PrivacyLevel level) + { + foreach (var subject in Enum.GetValues(typeof(GroupPrivacySubject))) + member.WithPrivacy((GroupPrivacySubject) subject, level); + return member; + } + + public static bool TryParseGroupPrivacy(string input, out GroupPrivacySubject subject) + { + switch (input.ToLowerInvariant()) + { + case "description": + case "desc": + case "text": + case "info": + subject = GroupPrivacySubject.Description; + break; + case "avatar": + case "pfp": + case "pic": + case "icon": + subject = GroupPrivacySubject.Icon; + break; + case "visibility": + case "hidden": + case "shown": + case "visible": + case "list": + subject = GroupPrivacySubject.Visibility; + break; + default: + subject = default; + return false; + } + + return true; + } + } +} \ No newline at end of file