From 1bb5d203df25cdc0404205da2e385ead94799667 Mon Sep 17 00:00:00 2001 From: Ske Date: Thu, 20 Aug 2020 21:43:17 +0200 Subject: [PATCH] Various additional tweaks/additions to groups --- PluralKit.Bot/Commands/CommandTree.cs | 3 + PluralKit.Bot/Commands/Groups.cs | 90 +++++++++++++++---- PluralKit.Bot/Commands/SystemEdit.cs | 6 +- PluralKit.Bot/Services/EmbedService.cs | 4 +- PluralKit.Core/Database/Migrations/9.sql | 4 + PluralKit.Core/Models/PKGroup.cs | 2 + PluralKit.Core/Models/PKSystem.cs | 1 + PluralKit.Core/Models/Patch/GroupPatch.cs | 4 + PluralKit.Core/Models/Patch/SystemPatch.cs | 2 + .../Models/Privacy/GroupPrivacySubject.cs | 8 +- PluralKit.Core/Models/Privacy/PrivacyExt.cs | 4 +- .../Models/Privacy/SystemPrivacySubject.cs | 6 ++ 12 files changed, 109 insertions(+), 25 deletions(-) diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 38f63c05..99c83b7a 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -50,6 +50,7 @@ namespace PluralKit.Bot public static Command GroupList = new Command("group list", "group list", "Lists all groups in this system"); public static Command GroupMemberList = new Command("group members", "group list", "Lists all members in a group"); public static Command GroupRename = new Command("group rename", "group name ", "Renames a group"); + public static Command GroupDisplayName = new Command("group displayname", "group displayname [display name]", "Changes a group's display name"); 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"); @@ -355,6 +356,8 @@ namespace PluralKit.Bot // Commands with group argument if (ctx.Match("rename", "name", "changename", "setname")) await ctx.Execute(GroupRename, g => g.RenameGroup(ctx, target)); + else if (ctx.Match("dn", "displayname", "nickname")) + await ctx.Execute(GroupDisplayName, g => g.GroupDisplayName(ctx, target)); else if (ctx.Match("description", "info", "bio", "text", "desc")) await ctx.Execute(GroupDesc, g => g.GroupDescription(ctx, target)); else if (ctx.Match("add", "a")) diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index 2c95a7c8..9bb17cfb 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -80,7 +80,43 @@ namespace PluralKit.Bot await conn.UpdateGroup(target.Id, new GroupPatch {Name = newName}); - await ctx.Reply($"{Emojis.Success} Group name changed from \"**{target.Name}**\" to \"**{newName}**\"."); + await ctx.Reply($"{Emojis.Success} Group name changed from **{target.Name}** to **{newName}**."); + } + + public async Task GroupDisplayName(Context ctx, PKGroup target) + { + if (ctx.MatchClear()) + { + ctx.CheckOwnGroup(target); + + var patch = new GroupPatch {DisplayName = Partial.Null()}; + await _db.Execute(conn => conn.UpdateGroup(target.Id, patch)); + + await ctx.Reply($"{Emojis.Success} Group display name cleared."); + } + else if (!ctx.HasNext()) + { + // No perms check, display name isn't covered by member privacy + var eb = new DiscordEmbedBuilder() + .AddField("Name", target.Name) + .AddField("Display Name", target.DisplayName ?? "*(none)*"); + + if (ctx.System?.Id == target.System) + eb.WithDescription($"To change display name, type `pk;group {GroupReference(target)} displayname `.\nTo clear it, type `pk;group {GroupReference(target)} displayname -clear`."); + + await ctx.Reply(embed: eb.Build()); + } + else + { + ctx.CheckOwnGroup(target); + + var newDisplayName = ctx.RemainderOrNull(); + + var patch = new GroupPatch {DisplayName = Partial.Present(newDisplayName)}; + await _db.Execute(conn => conn.UpdateGroup(target.Id, patch)); + + await ctx.Reply($"{Emojis.Success} Group display name changed."); + } } public async Task GroupDescription(Context ctx, PKGroup target) @@ -195,8 +231,7 @@ namespace PluralKit.Bot system = ctx.System; } - // should this be split off to a separate permission? - ctx.CheckSystemPrivacy(system, system.MemberListPrivacy); + ctx.CheckSystemPrivacy(system, system.GroupListPrivacy); // TODO: integrate with the normal "search" system await using var conn = await _db.Obtain(); @@ -209,8 +244,7 @@ namespace PluralKit.Bot else throw new PKError("You do not have permission to access this information."); } - - + var groups = (await conn.QueryGroupsInSystem(system.Id)) .Where(g => g.Visibility.CanAccess(pctx)) .ToList(); @@ -218,9 +252,10 @@ namespace PluralKit.Bot if (groups.Count == 0) { if (system.Id == ctx.System?.Id) - await ctx.Reply($"This system has no groups. To create one, use the command `pk;group new `."); + await ctx.Reply("This system has no groups. To create one, use the command `pk;group new `."); else - await ctx.Reply($"This system has no groups."); + await ctx.Reply("This system has no groups."); + return; } @@ -229,7 +264,13 @@ namespace PluralKit.Bot Task Renderer(DiscordEmbedBuilder eb, IEnumerable page) { - eb.WithSimpleLineContent(page.Select(g => $"[`{g.Hid}`] **{g.Name}**")); + eb.WithSimpleLineContent(page.Select(g => + { + if (g.DisplayName != null) + return $"[`{g.Hid}`] **{g.Name}** ({g.DisplayName})"; + else + return $"[`{g.Hid}`] **{g.Name}**"; + })); eb.WithFooter($"{groups.Count} total."); return Task.CompletedTask; } @@ -251,10 +292,17 @@ namespace PluralKit.Bot .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) - eb.AddField("Members (0)", $"Add one with `pk;group {GroupReference(target)} add `!", true); - else - eb.AddField($"Members ({memberCount})", $"(see `pk;group {GroupReference(target)} list`)", true); + if (target.DisplayName != null) + eb.AddField("Display Name", target.DisplayName); + + if (target.ListPrivacy.CanAccess(pctx)) + { + if (memberCount == 0 && pctx == LookupContext.ByOwner) + // Only suggest the add command if this is actually the owner lol + eb.AddField("Members (0)", $"Add one with `pk;group {GroupReference(target)} add `!", true); + else + eb.AddField($"Members ({memberCount})", $"(see `pk;group {GroupReference(target)} list`)", true); + } if (target.DescriptionFor(pctx) is {} desc) eb.AddField("Description", desc); @@ -275,14 +323,15 @@ namespace PluralKit.Bot var existingMembersInGroup = (await conn.QueryMemberList(target.System, new DatabaseViewsExt.MemberListQueryOptions {GroupFilter = target.Id})) - .Select(m => m.Id) + .Select(m => m.Id.Value) .ToHashSet(); if (op == AddRemoveOperation.Add) { var membersNotInGroup = members - .Where(m => !existingMembersInGroup.Contains(m.Id)) + .Where(m => !existingMembersInGroup.Contains(m.Id.Value)) .Select(m => m.Id) + .Distinct() .ToList(); await conn.AddMembersToGroup(target.Id, membersNotInGroup); @@ -294,8 +343,9 @@ namespace PluralKit.Bot else if (op == AddRemoveOperation.Remove) { var membersInGroup = members - .Where(m => existingMembersInGroup.Contains(m.Id)) + .Where(m => existingMembersInGroup.Contains(m.Id.Value)) .Select(m => m.Id) + .Distinct() .ToList(); await conn.RemoveMembersFromGroup(target.Id, membersInGroup); @@ -311,12 +361,12 @@ namespace PluralKit.Bot await using var conn = await _db.Obtain(); var targetSystem = await GetGroupSystem(ctx, target, conn); - ctx.CheckSystemPrivacy(targetSystem, target.Visibility); + ctx.CheckSystemPrivacy(targetSystem, target.ListPrivacy); var opts = ctx.ParseMemberListOptions(ctx.LookupContextFor(target.System)); opts.GroupFilter = target.Id; - var title = new StringBuilder($"Members of {target.Name} (`{target.Hid}`) in "); + var title = new StringBuilder($"Members of {target.DisplayName ?? target.Name} (`{target.Hid}`) in "); if (targetSystem.Name != null) title.Append($"{targetSystem.Name} (`{targetSystem.Hid}`)"); else @@ -363,8 +413,9 @@ namespace PluralKit.Bot .WithTitle($"Current privacy settings for {target.Name}") .AddField("Description", target.DescriptionPrivacy.Explanation()) .AddField("Icon", target.IconPrivacy.Explanation()) + .AddField("Member list", target.ListPrivacy.Explanation()) .AddField("Visibility", target.Visibility.Explanation()) - .WithDescription($"To edit privacy settings, use the command:\n> pk;group **{GroupReference(target)}** privacy **** ****\n\n- `subject` is one of `description`, `icon`, `visibility`, or `all`\n- `level` is either `public` or `private`.") + .WithDescription($"To edit privacy settings, use the command:\n> pk;group **{GroupReference(target)}** privacy **** ****\n\n- `subject` is one of `description`, `icon`, `members`, `visibility`, or `all`\n- `level` is either `public` or `private`.") .Build()); return; } @@ -387,6 +438,7 @@ namespace PluralKit.Bot { GroupPrivacySubject.Description => "description privacy", GroupPrivacySubject.Icon => "icon privacy", + GroupPrivacySubject.List => "member list", GroupPrivacySubject.Visibility => "visibility", _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; @@ -396,10 +448,12 @@ namespace PluralKit.Bot (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.List, PrivacyLevel.Private) => "This group's member list is now hidden from other systems.", (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.", + (GroupPrivacySubject.List, PrivacyLevel.Public) => "This group's member list is no longer hidden from other systems.", _ => throw new InvalidOperationException($"Invalid subject/level tuple ({subject}, {level})") }; diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index e1390c8a..143fef45 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -268,9 +268,10 @@ namespace PluralKit.Bot .WithTitle("Current privacy settings for your system") .AddField("Description", ctx.System.DescriptionPrivacy.Explanation()) .AddField("Member list", ctx.System.MemberListPrivacy.Explanation()) + .AddField("Group list", ctx.System.GroupListPrivacy.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 `\n\n- `subject` is one of `description`, `list`, `front`, `fronthistory`, or `all` \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`, `groups`, or `all` \n- `level` is either `public` or `private`."); return ctx.Reply(embed: eb.Build()); } @@ -291,6 +292,7 @@ namespace PluralKit.Bot SystemPrivacySubject.Front => "front", SystemPrivacySubject.FrontHistory => "front history", SystemPrivacySubject.MemberList => "member list", + SystemPrivacySubject.GroupList => "group list", _ => "" }; @@ -304,7 +306,7 @@ namespace PluralKit.Bot 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.Private => $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now not be able to view your member list, group 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.", _ => "" }; diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index a813dd52..5985a3e5 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -147,8 +147,8 @@ namespace PluralKit.Bot { { // More than 5 groups show in "compact" format without ID var content = groups.Count > 5 - ? string.Join(", ", groups.Select(g => g.Name)) - : string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.Name}**")); + ? string.Join(", ", groups.Select(g => g.DisplayName ?? g.Name)) + : string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.DisplayName ?? g.Name}**")); eb.AddField($"Groups ({groups.Count})", content.Truncate(1000)); } diff --git a/PluralKit.Core/Database/Migrations/9.sql b/PluralKit.Core/Database/Migrations/9.sql index 8132ac3f..d7fb6ccb 100644 --- a/PluralKit.Core/Database/Migrations/9.sql +++ b/PluralKit.Core/Database/Migrations/9.sql @@ -7,12 +7,14 @@ create table groups ( system int not null references systems(id) on delete cascade, name text not null, + display_name text, description text, icon text, -- Description columns follow the same pattern as usual: 1 = public, 2 = private description_privacy integer check (description_privacy in (1, 2)) not null default 1, icon_privacy integer check (icon_privacy in (1, 2)) not null default 1, + list_privacy integer check (list_privacy in (1, 2)) not null default 1, visibility integer check (visibility in (1, 2)) not null default 1, created timestamp with time zone not null default (current_timestamp at time zone 'utc') @@ -24,4 +26,6 @@ create table group_members ( primary key (group_id, member_id) ); +alter table systems add column group_list_privacy integer check (group_list_privacy in (1, 2)) not null default systems.member_list_privacy; + update info set schema_version = 9; diff --git a/PluralKit.Core/Models/PKGroup.cs b/PluralKit.Core/Models/PKGroup.cs index e553b902..b5bf26f6 100644 --- a/PluralKit.Core/Models/PKGroup.cs +++ b/PluralKit.Core/Models/PKGroup.cs @@ -10,11 +10,13 @@ namespace PluralKit.Core public SystemId System { get; } public string Name { get; } = null!; + public string? DisplayName { get; } public string? Description { get; } public string? Icon { get; } public PrivacyLevel DescriptionPrivacy { get; } public PrivacyLevel IconPrivacy { get; } + public PrivacyLevel ListPrivacy { get; } public PrivacyLevel Visibility { get; } public Instant Created { get; } diff --git a/PluralKit.Core/Models/PKSystem.cs b/PluralKit.Core/Models/PKSystem.cs index aff336af..562261f0 100644 --- a/PluralKit.Core/Models/PKSystem.cs +++ b/PluralKit.Core/Models/PKSystem.cs @@ -22,6 +22,7 @@ namespace PluralKit.Core { public PrivacyLevel MemberListPrivacy { get;} public PrivacyLevel FrontPrivacy { get; } public PrivacyLevel FrontHistoryPrivacy { get; } + public PrivacyLevel GroupListPrivacy { get; } [JsonIgnore] public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz); } diff --git a/PluralKit.Core/Models/Patch/GroupPatch.cs b/PluralKit.Core/Models/Patch/GroupPatch.cs index 02281d8c..2f154c90 100644 --- a/PluralKit.Core/Models/Patch/GroupPatch.cs +++ b/PluralKit.Core/Models/Patch/GroupPatch.cs @@ -4,19 +4,23 @@ namespace PluralKit.Core public class GroupPatch: PatchObject { public Partial Name { get; set; } + public Partial DisplayName { get; set; } public Partial Description { get; set; } public Partial Icon { get; set; } public Partial DescriptionPrivacy { get; set; } public Partial IconPrivacy { get; set; } + public Partial ListPrivacy { get; set; } public Partial Visibility { get; set; } public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b .With("name", Name) + .With("display_name", DisplayName) .With("description", Description) .With("icon", Icon) .With("description_privacy", DescriptionPrivacy) .With("icon_privacy", IconPrivacy) + .With("list_privacy", ListPrivacy) .With("visibility", Visibility); } } \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index aecd160f..d574e8e2 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -11,6 +11,7 @@ namespace PluralKit.Core public Partial UiTz { get; set; } public Partial DescriptionPrivacy { get; set; } public Partial MemberListPrivacy { get; set; } + public Partial GroupListPrivacy { get; set; } public Partial FrontPrivacy { get; set; } public Partial FrontHistoryPrivacy { get; set; } public Partial PingsEnabled { get; set; } @@ -24,6 +25,7 @@ namespace PluralKit.Core .With("ui_tz", UiTz) .With("description_privacy", DescriptionPrivacy) .With("member_list_privacy", MemberListPrivacy) + .With("group_list_privacy", GroupListPrivacy) .With("front_privacy", FrontPrivacy) .With("front_history_privacy", FrontHistoryPrivacy) .With("pings_enabled", PingsEnabled); diff --git a/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs index 63487c16..90691d48 100644 --- a/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/GroupPrivacySubject.cs @@ -6,6 +6,7 @@ namespace PluralKit.Core { Description, Icon, + List, Visibility } @@ -18,6 +19,7 @@ namespace PluralKit.Core { GroupPrivacySubject.Description => group.DescriptionPrivacy = level, GroupPrivacySubject.Icon => group.IconPrivacy = level, + GroupPrivacySubject.List => group.ListPrivacy = level, GroupPrivacySubject.Visibility => group.Visibility = level, _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; @@ -52,9 +54,13 @@ namespace PluralKit.Core case "hidden": case "shown": case "visible": - case "list": subject = GroupPrivacySubject.Visibility; break; + case "list": + case "listing": + case "members": + subject = GroupPrivacySubject.List; + break; default: subject = default; return false; diff --git a/PluralKit.Core/Models/Privacy/PrivacyExt.cs b/PluralKit.Core/Models/Privacy/PrivacyExt.cs index 548f963e..3ce62859 100644 --- a/PluralKit.Core/Models/Privacy/PrivacyExt.cs +++ b/PluralKit.Core/Models/Privacy/PrivacyExt.cs @@ -16,8 +16,8 @@ namespace PluralKit.Core public static string Explanation(this PrivacyLevel level) => level switch { - PrivacyLevel.Private => "**Private** (visible only when queried by you)", - PrivacyLevel.Public => "**Public** (visible to everyone)", + PrivacyLevel.Private => "Private *(visible only when queried by you)*", + PrivacyLevel.Public => "Public *(visible to everyone)*", _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) }; diff --git a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs index 1b426d69..8fcf3efe 100644 --- a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs @@ -6,6 +6,7 @@ namespace PluralKit.Core { Description, MemberList, + GroupList, Front, FrontHistory } @@ -21,6 +22,7 @@ namespace PluralKit.Core SystemPrivacySubject.Front => system.FrontPrivacy = level, SystemPrivacySubject.FrontHistory => system.FrontHistoryPrivacy = level, SystemPrivacySubject.MemberList => system.MemberListPrivacy = level, + SystemPrivacySubject.GroupList => system.GroupListPrivacy = level, _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}") }; @@ -61,6 +63,10 @@ namespace PluralKit.Core case "fh": subject = SystemPrivacySubject.FrontHistory; break; + case "groups": + case "gs": + subject = SystemPrivacySubject.GroupList; + break; default: subject = default; return false;