Various additional tweaks/additions to groups
This commit is contained in:
parent
9e251352c7
commit
1bb5d203df
@ -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"))
|
||||
|
@ -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})")
|
||||
};
|
||||
|
@ -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.",
|
||||
_ => ""
|
||||
};
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user