Various additional tweaks/additions to groups

This commit is contained in:
Ske 2020-08-20 21:43:17 +02:00
parent 9e251352c7
commit 1bb5d203df
12 changed files with 109 additions and 25 deletions

View File

@ -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 <group> list", "Lists all members in a group");
public static Command GroupRename = new Command("group rename", "group <group> name <new name>", "Renames a group");
public static Command GroupDisplayName = new Command("group displayname", "group <member> displayname [display name]", "Changes a group's display name");
public static Command GroupDesc = new Command("group description", "group <group> description [description]", "Changes a group's description");
public static Command GroupAdd = new Command("group add", "group <group> add <member> [member 2] [member 3...]", "Adds one or more members to a group");
public static Command GroupRemove = new Command("group remove", "group <group> remove <member> [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<Groups>(GroupRename, g => g.RenameGroup(ctx, target));
else if (ctx.Match("dn", "displayname", "nickname"))
await ctx.Execute<Groups>(GroupDisplayName, g => g.GroupDisplayName(ctx, target));
else if (ctx.Match("description", "info", "bio", "text", "desc"))
await ctx.Execute<Groups>(GroupDesc, g => g.GroupDescription(ctx, target));
else if (ctx.Match("add", "a"))

View File

@ -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<string>.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 <display name>`.\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<string>.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 <name>`.");
await ctx.Reply("This system has no groups. To create one, use the command `pk;group new <name>`.");
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<PKGroup> 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 <member>`!", 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 <member>`!", 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 **<subject>** **<level>**\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 **<subject>** **<level>**\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})")
};

View File

@ -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 <subject> <level>`\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 <subject> <level>`\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.",
_ => ""
};

View File

@ -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));
}

View File

@ -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;

View File

@ -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; }

View File

@ -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);
}

View File

@ -4,19 +4,23 @@ namespace PluralKit.Core
public class GroupPatch: PatchObject
{
public Partial<string> Name { get; set; }
public Partial<string?> DisplayName { get; set; }
public Partial<string?> Description { get; set; }
public Partial<string?> Icon { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
public Partial<PrivacyLevel> IconPrivacy { get; set; }
public Partial<PrivacyLevel> ListPrivacy { get; set; }
public Partial<PrivacyLevel> 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);
}
}

View File

@ -11,6 +11,7 @@ namespace PluralKit.Core
public Partial<string> UiTz { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
public Partial<PrivacyLevel> MemberListPrivacy { get; set; }
public Partial<PrivacyLevel> GroupListPrivacy { get; set; }
public Partial<PrivacyLevel> FrontPrivacy { get; set; }
public Partial<PrivacyLevel> FrontHistoryPrivacy { get; set; }
public Partial<bool> 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);

View File

@ -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;

View File

@ -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)
};

View File

@ -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;