feat(bot): add system guild icon & guild name (#554)

This commit is contained in:
rladenson 2023-07-19 12:48:04 +12:00 committed by Iris System
parent d12cd481f6
commit 3045c5e307
22 changed files with 337 additions and 28 deletions

View file

@ -27,7 +27,7 @@ public class SystemControllerV2: PKControllerBase
if (system == null)
throw Errors.SystemNotFound;
return Ok(APIJsonExt.EmbedJson(system.Name ?? $"System with ID `{system.Hid}`", "System"));
return Ok(APIJsonExt.EmbedJson(system.NameFor(ContextFor(system)) ?? $"System with ID `{system.Hid}`", "System"));
}
[HttpPatch("{systemRef}")]

View file

@ -5,12 +5,14 @@ public partial class CommandTree
public static Command SystemInfo = new Command("system", "system [system]", "Looks up information about a system");
public static Command SystemNew = new Command("system new", "system new [name]", "Creates a new system");
public static Command SystemRename = new Command("system name", "system [system] rename [name]", "Renames your system");
public static Command SystemServerName = new Command("system servername", "system [system] servername [name]", "Changes your system displayname for this server");
public static Command SystemDesc = new Command("system description", "system [system] description [description]", "Changes your system's description");
public static Command SystemColor = new Command("system color", "system [system] color [color]", "Changes your system's color");
public static Command SystemTag = new Command("system tag", "system [system] tag [tag]", "Changes your system's tag");
public static Command SystemPronouns = new Command("system pronouns", "system [system] pronouns [pronouns]", "Changes your system's pronouns");
public static Command SystemServerTag = new Command("system servertag", "system [system] servertag [tag|enable|disable]", "Changes your system's tag in the current server");
public static Command SystemAvatar = new Command("system icon", "system [system] icon [url|@mention]", "Changes your system's icon");
public static Command SystemServerAvatar = new Command("system serveravatar", "system [system] serveravatar [tag]", "Changes your system's icon in the current server");
public static Command SystemBannerImage = new Command("system banner", "system [system] banner [url]", "Set the system's banner image");
public static Command SystemDelete = new Command("system delete", "system [system] delete", "Deletes your system");
public static Command SystemProxy = new Command("system proxy", "system proxy [server id] [on|off]", "Enables or disables message proxying in a specific server");
@ -20,7 +22,7 @@ public partial class CommandTree
public static Command SystemFrontHistory = new Command("system fronthistory", "system [system] fronthistory", "Shows a system's front history");
public static Command SystemFrontPercent = new Command("system frontpercent", "system [system] frontpercent [timespan]", "Shows a system's front breakdown");
public static Command SystemId = new Command("system id", "system [system] id", "Prints your system's id.");
public static Command SystemPrivacy = new Command("system privacy", "system [system] privacy <description|members|fronter|fronthistory|all> <public|private>", "Changes your system's privacy settings");
public static Command SystemPrivacy = new Command("system privacy", "system [system] privacy <name|avatar|description|members|fronter|fronthistory|all> <public|private>", "Changes your system's privacy settings");
public static Command ConfigTimezone = new Command("config timezone", "config timezone [timezone]", "Changes your system's time zone");
public static Command ConfigPing = new Command("config ping", "config ping [on|off]", "Changes your system's ping preferences");
public static Command ConfigAutoproxyAccount = new Command("config autoproxy account", "config autoproxy account [on|off]", "Toggles autoproxy globally for the current account");
@ -107,7 +109,7 @@ public partial class CommandTree
public static Command[] SystemCommands =
{
SystemInfo, SystemNew, SystemRename, SystemTag, SystemDesc, SystemAvatar, SystemBannerImage, SystemColor,
SystemInfo, SystemNew, SystemRename, SystemServerName, SystemTag, SystemDesc, SystemAvatar, SystemServerAvatar, SystemBannerImage, SystemColor,
SystemDelete, SystemList, SystemFronter, SystemFrontHistory, SystemFrontPercent, SystemPrivacy, SystemProxy
};

View file

@ -220,6 +220,9 @@ public partial class CommandTree
{
if (ctx.Match("name", "rename", "changename", "rn"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemRename, m => m.Name(ctx, target));
else if (ctx.Match("servername", "sn", "sname", "snick", "snickname", "servernick", "servernickname",
"serverdisplayname", "guildname", "guildnick", "guildnickname", "serverdn"))
await ctx.Execute<SystemEdit>(SystemServerName, m => m.ServerName(ctx, target));
else if (ctx.Match("tag", "t"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemTag, m => m.Tag(ctx, target));
else if (ctx.Match("servertag", "st", "stag", "deer"))
@ -234,6 +237,9 @@ public partial class CommandTree
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemBannerImage, m => m.BannerImage(ctx, target));
else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemAvatar, m => m.Avatar(ctx, target));
else if (ctx.Match("serveravatar", "sa", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic",
"guildavatar", "guildpic", "guildicon", "sicon", "spfp"))
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemServerAvatar, m => m.ServerAvatar(ctx, target));
else if (ctx.Match("list", "l", "members", "ls"))
await ctx.CheckSystem(target).Execute<SystemList>(SystemList, m => m.MemberList(ctx, target));
else if (ctx.Match("find", "search", "query", "fd", "s"))
@ -319,7 +325,7 @@ public partial class CommandTree
else
await ctx.Execute<GroupMember>(MemberGroups, m => m.ListMemberGroups(ctx, target));
else if (ctx.Match("serveravatar", "sa", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic",
"guildavatar", "guildpic", "guildicon", "sicon"))
"guildavatar", "guildpic", "guildicon", "sicon", "spfp"))
await ctx.Execute<MemberAvatar>(MemberServerAvatar, m => m.ServerAvatar(ctx, target));
else if (ctx.Match("displayname", "dn", "dname", "nick", "nickname", "dispname"))
await ctx.Execute<MemberEdit>(MemberDisplayName, m => m.DisplayName(ctx, target));

View file

@ -131,10 +131,23 @@ public class GroupMember
opts.GroupFilter = target.Id;
var title = new StringBuilder($"Members of {target.DisplayName ?? target.Name} (`{target.Hid}`) in ");
if (targetSystem.Name != null)
title.Append($"{targetSystem.Name} (`{targetSystem.Hid}`)");
if (ctx.Guild != null)
{
var guildSettings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, targetSystem.Id);
if (guildSettings.DisplayName != null)
title.Append($"{guildSettings.DisplayName} (`{targetSystem.Hid}`)");
else if (targetSystem.NameFor(ctx) != null)
title.Append($"{targetSystem.NameFor(ctx)} (`{targetSystem.Hid}`)");
else
title.Append($"`{targetSystem.Hid}`");
}
else
title.Append($"`{targetSystem.Hid}`");
{
if (targetSystem.NameFor(ctx) != null)
title.Append($"{targetSystem.NameFor(ctx)} (`{targetSystem.Hid}`)");
else
title.Append($"`{targetSystem.Hid}`");
}
if (opts.Search != null)
title.Append($" matching **{opts.Search.Truncate(100)}**");

View file

@ -28,6 +28,7 @@ public class SystemEdit
public async Task Name(Context ctx, PKSystem target)
{
ctx.CheckSystemPrivacy(target.Id, target.NamePrivacy);
var isOwnSystem = target.Id == ctx.System?.Id;
var noNameSetMessage = $"{(isOwnSystem ? "Your" : "This")} system does not have a name set.";
@ -76,6 +77,60 @@ public class SystemEdit
}
}
public async Task ServerName(Context ctx, PKSystem target)
{
ctx.CheckGuildContext();
var isOwnSystem = target.Id == ctx.System?.Id;
var noNameSetMessage = $"{(isOwnSystem ? "Your" : "This")} system does not have a name specific to this server.";
if (isOwnSystem)
noNameSetMessage += " Type `pk;system servername <name>` to set one.";
var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id);
if (ctx.MatchRaw())
{
if (settings.DisplayName != null)
await ctx.Reply($"```\n{settings.DisplayName}\n```");
else
await ctx.Reply(noNameSetMessage);
return;
}
if (!ctx.HasNext(false))
{
if (settings.DisplayName != null)
await ctx.Reply(
$"{(isOwnSystem ? "Your" : "This")} system's name for this server is currently **{settings.DisplayName}**."
+ (isOwnSystem ? " Type `pk;system servername -clear` to clear it." : "")
+ $" Using {settings.DisplayName.Length}/{Limits.MaxSystemNameLength} characters.");
else
await ctx.Reply(noNameSetMessage);
return;
}
ctx.CheckSystem().CheckOwnSystem(target);
if (ctx.MatchClear() && await ctx.ConfirmClear("your system's name for this server"))
{
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { DisplayName = null });
await ctx.Reply($"{Emojis.Success} System name for this server cleared.");
}
else
{
var newSystemGuildName = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
if (newSystemGuildName.Length > Limits.MaxSystemNameLength)
throw Errors.StringTooLongError("System name for this server", newSystemGuildName.Length, Limits.MaxSystemNameLength);
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { DisplayName = newSystemGuildName });
await ctx.Reply($"{Emojis.Success} System name for this server changed (using {newSystemGuildName.Length}/{Limits.MaxSystemNameLength} characters).");
}
}
public async Task Description(Context ctx, PKSystem target)
{
ctx.CheckSystemPrivacy(target.Id, target.DescriptionPrivacy);
@ -471,6 +526,80 @@ public class SystemEdit
await ShowIcon();
}
public async Task ServerAvatar(Context ctx, PKSystem target)
{
async Task ClearIcon()
{
ctx.CheckOwnSystem(target);
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = null });
await ctx.Reply($"{Emojis.Success} System server avatar cleared.");
}
async Task SetIcon(ParsedImage img)
{
ctx.CheckOwnSystem(target);
await AvatarUtils.VerifyAvatarOrThrow(_client, img.Url);
await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = img.Url });
var msg = img.Source switch
{
AvatarSource.User =>
$"{Emojis.Success} System icon for this server changed to {img.SourceUser?.Username}'s avatar! It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon for this server will need to be re-set.",
AvatarSource.Url =>
$"{Emojis.Success} System icon for this server changed to the image at the given URL. It will now be used for anything that uses system avatar in this server.",
AvatarSource.Attachment =>
$"{Emojis.Success} System icon for this server changed to attached image. It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon for this server will stop working.",
_ => throw new ArgumentOutOfRangeException()
};
// The attachment's already right there, no need to preview it.
var hasEmbed = img.Source != AvatarSource.Attachment;
await (hasEmbed
? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build())
: ctx.Reply(msg));
}
async Task ShowIcon()
{
var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id);
if ((settings.AvatarUrl?.Trim() ?? "").Length > 0)
{
var eb = new EmbedBuilder()
.Title("System server icon")
.Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl()));
if (target.Id == ctx.System?.Id)
eb.Description("To clear, use `pk;system servericon clear`.");
await ctx.Reply(embed: eb.Build());
}
else
{
throw new PKSyntaxError(
"This system does not have a icon specific to this server. Set one by attaching an image to this command, or by passing an image URL or @mention.");
}
}
ctx.CheckGuildContext();
if (target != null && target?.Id != ctx.System?.Id)
{
await ShowIcon();
return;
}
if (ctx.MatchClear() && await ctx.ConfirmClear("your system's icon for this server"))
await ClearIcon();
else if (await ctx.MatchImage() is { } img)
await SetIcon(img);
else
await ShowIcon();
}
public async Task BannerImage(Context ctx, PKSystem target)
{
ctx.CheckSystemPrivacy(target.Id, target.DescriptionPrivacy);
@ -630,6 +759,8 @@ public class SystemEdit
{
var eb = new EmbedBuilder()
.Title("Current privacy settings for your system")
.Field(new Embed.Field("Name", target.NamePrivacy.Explanation()))
.Field(new Embed.Field("Avatar", target.AvatarPrivacy.Explanation()))
.Field(new Embed.Field("Description", target.DescriptionPrivacy.Explanation()))
.Field(new Embed.Field("Pronouns", target.PronounPrivacy.Explanation()))
.Field(new Embed.Field("Member list", target.MemberListPrivacy.Explanation()))
@ -637,7 +768,7 @@ public class SystemEdit
.Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation()))
.Field(new Embed.Field("Front/switch history", target.FrontHistoryPrivacy.Explanation()))
.Description(
"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`.");
"To edit privacy settings, use the command:\n`pk;system privacy <subject> <level>`\n\n- `subject` is one of `name`, `avatar`, `description`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`.");
return ctx.Reply(embed: eb.Build());
}
@ -654,6 +785,8 @@ public class SystemEdit
var subjectStr = subject switch
{
SystemPrivacySubject.Name => "name",
SystemPrivacySubject.Avatar => "avatar",
SystemPrivacySubject.Description => "description",
SystemPrivacySubject.Pronouns => "pronouns",
SystemPrivacySubject.Front => "front",

View file

@ -44,10 +44,17 @@ public class SystemFront
.Scan(new FrontHistoryEntry(null, null),
(lastEntry, newSwitch) => new FrontHistoryEntry(lastEntry.ThisSwitch?.Timestamp, newSwitch));
var embedTitle = system.Name != null
? $"Front history of {system.Name} (`{system.Hid}`)"
var embedTitle = system.NameFor(ctx) != null
? $"Front history of {system.NameFor(ctx)} (`{system.Hid}`)"
: $"Front history of `{system.Hid}`";
if (ctx.Guild != null)
{
var guildSettings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, system.Id);
if (guildSettings.DisplayName != null)
embedTitle = $"Front history of {guildSettings.DisplayName} (`{system.Hid}`)";
}
var showMemberId = ctx.MatchFlag("with-id", "wid");
await ctx.Paginate(
@ -127,10 +134,13 @@ public class SystemFront
if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture;
var title = new StringBuilder("Frontpercent of ");
var guildSettings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, system.Id);
if (group != null)
title.Append($"{group.NameFor(ctx)} (`{group.Hid}`)");
else if (system.Name != null)
title.Append($"{system.Name} (`{system.Hid}`)");
else if (guildSettings.DisplayName != null)
title.Append($"{guildSettings.DisplayName} (`{system.Hid}`)");
else if (system.NameFor(ctx) != null)
title.Append($"{system.NameFor(ctx)} (`{system.Hid}`)");
else
title.Append($"`{system.Hid}`");

View file

@ -21,18 +21,21 @@ public class SystemList
await ctx.RenderMemberList(
ctx.LookupContextFor(target.Id),
target.Id,
GetEmbedTitle(target, opts),
await GetEmbedTitle(target, opts, ctx),
target.Color,
opts
);
}
private string GetEmbedTitle(PKSystem target, ListOptions opts)
private async Task<string> GetEmbedTitle(PKSystem target, ListOptions opts, Context ctx)
{
var title = new StringBuilder("Members of ");
if (target.Name != null)
title.Append($"{target.Name} (`{target.Hid}`)");
var systemGuildSettings = ctx.Guild != null ? await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id) : null;
if (systemGuildSettings != null && systemGuildSettings.DisplayName != null)
title.Append($"{systemGuildSettings.DisplayName} (`{target.Hid}`)");
else if (target.NameFor(ctx) != null)
title.Append($"{target.NameFor(ctx)} (`{target.Hid}`)");
else
title.Append($"`{target.Hid}`");

View file

@ -68,13 +68,16 @@ public class EmbedService
}
var eb = new EmbedBuilder()
.Title(system.Name)
.Thumbnail(new Embed.EmbedThumbnail(system.AvatarUrl.TryGetCleanCdnUrl()))
.Title(system.NameFor(ctx))
.Footer(new Embed.EmbedFooter(
$"System ID: {system.Hid} | Created on {system.Created.FormatZoned(cctx.Zone)}"))
.Color(color)
.Url($"https://dash.pluralkit.me/profile/s/{system.Hid}");
var avatar = system.AvatarFor(ctx);
if (avatar != null)
eb.Thumbnail(new Embed.EmbedThumbnail(avatar));
if (system.DescriptionPrivacy.CanAccess(ctx))
eb.Image(new Embed.EmbedImage(system.BannerImage));
@ -106,6 +109,21 @@ public class EmbedService
if (!guildSettings.TagEnabled)
eb.Field(new Embed.Field($"Tag (in server '{cctx.Guild.Name}')",
"*(tag is disabled in this server)*"));
if (guildSettings.DisplayName != null)
eb.Title(guildSettings.DisplayName);
var guildAvatar = guildSettings.AvatarUrl.TryGetCleanCdnUrl();
if (guildAvatar != null)
{
eb.Thumbnail(new Embed.EmbedThumbnail(guildAvatar));
var sysDesc = "*(this system has a server-specific avatar set";
if (avatar != null)
sysDesc += $"; [click here]({system.AvatarUrl.TryGetCleanCdnUrl()}) to see their global avatar)*";
else
sysDesc += ")*";
eb.Description(sysDesc);
}
}
if (system.PronounPrivacy.CanAccess(ctx) && system.Pronouns != null)
@ -162,7 +180,13 @@ public class EmbedService
// string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone));
var name = member.NameFor(ctx);
if (system.Name != null) name = $"{name} ({system.Name})";
var systemGuildSettings = guild != null ? await _repo.GetSystemGuild(guild.Id, system.Id) : null;
if (systemGuildSettings != null && systemGuildSettings.DisplayName != null)
name = $"{name} ({systemGuildSettings.DisplayName})";
else if (system.NameFor(ctx) != null)
name = $"{name} ({system.NameFor(ctx)})";
else
name = $"{name}";
uint color;
try
@ -256,8 +280,13 @@ public class EmbedService
var memberCount = await _repo.GetGroupMemberCount(target.Id, countctx == LookupContext.ByOwner ? null : PrivacyLevel.Public);
var nameField = target.NamePrivacy.Get(pctx, target.Name, target.DisplayName ?? target.Name);
if (system.Name != null)
var nameField = target.NameFor(ctx);
var systemGuildSettings = ctx.Guild != null ? await _repo.GetSystemGuild(ctx.Guild.Id, system.Id) : null;
if (systemGuildSettings != null && systemGuildSettings.DisplayName != null)
nameField = $"{nameField} ({systemGuildSettings.DisplayName})";
else if (system.NameFor(ctx) != null)
nameField = $"{nameField} ({system.NameFor(ctx)})";
else
nameField = $"{nameField} ({system.Name})";
uint color;
@ -395,7 +424,7 @@ public class EmbedService
.Field(new Embed.Field("System",
msg.System == null
? "*(deleted or unknown system)*"
: msg.System.Name != null ? $"{msg.System.Name} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`"
: msg.System.NameFor(ctx) != null ? $"{msg.System.NameFor(ctx)} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`"
, true))
.Field(new Embed.Field("Member",
msg.Member == null

View file

@ -12,6 +12,9 @@ public static class ModelUtils
public static string NameFor(this PKGroup group, Context ctx) =>
group.NameFor(ctx.LookupContextFor(group.System));
public static string NameFor(this PKSystem system, Context ctx) =>
system.NameFor(ctx.LookupContextFor(system.Id));
public static string AvatarFor(this PKMember member, Context ctx) =>
member.AvatarFor(ctx.LookupContextFor(member.System)).TryGetCleanCdnUrl();

View file

@ -26,6 +26,7 @@ public class MessageContext
public string? SystemGuildTag { get; }
public bool TagEnabled { get; }
public string? SystemAvatar { get; }
public string? SystemGuildAvatar { get; }
public bool AllowAutoproxy { get; }
public int? LatchTimeout { get; }
public bool CaseSensitiveProxyTags { get; }

View file

@ -42,5 +42,5 @@ public class ProxyMember
return memberName;
}
public string? ProxyAvatar(MessageContext ctx) => ServerAvatar ?? WebhookAvatar ?? Avatar ?? ctx.SystemAvatar;
public string? ProxyAvatar(MessageContext ctx) => ServerAvatar ?? WebhookAvatar ?? Avatar ?? ctx.SystemGuildAvatar ?? ctx.SystemAvatar;
}

View file

@ -13,6 +13,7 @@ create function message_context(account_id bigint, guild_id bigint, channel_id b
system_guild_tag text,
tag_enabled bool,
system_avatar text,
system_guild_avatar text,
allow_autoproxy bool,
latch_timeout integer,
case_sensitive_proxy_tags bool,
@ -22,6 +23,7 @@ as $$
-- CTEs to query "static" (accessible only through args) data
with
system as (select systems.*, system_config.latch_timeout, system_guild.tag as guild_tag, system_guild.tag_enabled as tag_enabled,
system_guild.avatar_url as guild_avatar,
allow_autoproxy as account_autoproxy, system_config.case_sensitive_proxy_tags, system_config.proxy_error_message_enabled from accounts
left join systems on systems.id = accounts.system
left join system_config on system_config.system = accounts.system
@ -44,6 +46,7 @@ as $$
system.guild_tag as system_guild_tag,
coalesce(system.tag_enabled, true) as tag_enabled,
system.avatar_url as system_avatar,
system.guild_avatar as system_guild_avatar,
system.account_autoproxy as allow_autoproxy,
system.latch_timeout as latch_timeout,
system.case_sensitive_proxy_tags as case_sensitive_proxy_tags,

View file

@ -0,0 +1,7 @@
-- database version 35
-- add guild avatar and guild name to system guild settings
alter table system_guild add column avatar_url text;
alter table system_guild add column display_name text;
update info set schema_version = 35;

View file

@ -0,0 +1,7 @@
-- database version 36
-- add system avatar privacy and system name privacy
alter table systems add column name_privacy integer not null default 1;
alter table systems add column avatar_privacy integer not null default 1;
update info set schema_version = 36;

View file

@ -9,7 +9,7 @@ namespace PluralKit.Core;
internal class DatabaseMigrator
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 34;
private const int TargetSchemaVersion = 36;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)

View file

@ -46,6 +46,8 @@ public class PKSystem
public string WebhookUrl { get; }
public string WebhookToken { get; }
public Instant Created { get; }
public PrivacyLevel NamePrivacy { get; }
public PrivacyLevel AvatarPrivacy { get; }
public PrivacyLevel DescriptionPrivacy { get; }
public PrivacyLevel MemberListPrivacy { get; }
public PrivacyLevel FrontPrivacy { get; }
@ -59,18 +61,24 @@ public static class PKSystemExt
public static string DescriptionFor(this PKSystem system, LookupContext ctx) =>
system.DescriptionPrivacy.Get(ctx, system.Description);
public static string NameFor(this PKSystem system, LookupContext ctx) =>
system.NamePrivacy.Get(ctx, system.Name);
public static string AvatarFor(this PKSystem system, LookupContext ctx) =>
system.AvatarPrivacy.Get(ctx, system.AvatarUrl.TryGetCleanCdnUrl());
public static JObject ToJson(this PKSystem system, LookupContext ctx)
{
var o = new JObject();
o.Add("id", system.Hid);
o.Add("uuid", system.Uuid.ToString());
o.Add("name", system.Name);
o.Add("name", system.NameFor(ctx));
o.Add("description", system.DescriptionFor(ctx));
o.Add("tag", system.Tag);
o.Add("pronouns", system.PronounPrivacy.Get(ctx, system.Pronouns));
o.Add("avatar_url", system.AvatarUrl.TryGetCleanCdnUrl());
o.Add("avatar_url", system.AvatarFor(ctx));
o.Add("banner", system.DescriptionPrivacy.Get(ctx, system.BannerImage).TryGetCleanCdnUrl());
o.Add("color", system.Color);
o.Add("created", system.Created.FormatExport());
@ -83,6 +91,8 @@ public static class PKSystemExt
var p = new JObject();
p.Add("name_privacy", system.NamePrivacy.ToJsonString());
p.Add("avatar_privacy", system.AvatarPrivacy.ToJsonString());
p.Add("description_privacy", system.DescriptionPrivacy.ToJsonString());
p.Add("pronoun_privacy", system.PronounPrivacy.ToJsonString());
p.Add("member_list_privacy", system.MemberListPrivacy.ToJsonString());

View file

@ -11,17 +11,26 @@ public class SystemGuildPatch: PatchObject
public Partial<bool> ProxyEnabled { get; set; }
public Partial<string?> Tag { get; set; }
public Partial<bool?> TagEnabled { get; set; }
public Partial<string?> AvatarUrl { get; set; }
public Partial<string?> DisplayName { get; set; }
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
.With("proxy_enabled", ProxyEnabled)
.With("tag", Tag)
.With("tag_enabled", TagEnabled)
.With("avatar_url", AvatarUrl)
.With("display_name", DisplayName)
);
public new void AssertIsValid()
{
if (Tag.Value != null)
AssertValid(Tag.Value, "tag", Limits.MaxSystemTagLength);
if (AvatarUrl.Value != null)
AssertValid(AvatarUrl.Value, "avatar_url", Limits.MaxUriLength,
s => MiscUtils.TryMatchUri(s, out var avatarUri));
if (DisplayName.Value != null)
AssertValid(DisplayName.Value, "display_name", Limits.MaxMemberNameLength);
}
#nullable disable
@ -38,6 +47,12 @@ public class SystemGuildPatch: PatchObject
if (o.ContainsKey("tag_enabled") && o["tag_enabled"].Type != JTokenType.Null)
patch.TagEnabled = o.Value<bool>("tag_enabled");
if (o.ContainsKey("avatar_url"))
patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty();
if (o.ContainsKey("display_name"))
patch.DisplayName = o.Value<string>("display_name").NullIfEmpty();
return patch;
}
@ -56,6 +71,12 @@ public class SystemGuildPatch: PatchObject
if (TagEnabled.IsPresent)
o.Add("tag_enabled", TagEnabled.Value);
if (AvatarUrl.IsPresent)
o.Add("avatar_url", AvatarUrl.Value);
if (DisplayName.IsPresent)
o.Add("display_name", DisplayName.Value);
return o;
}
}

View file

@ -18,6 +18,8 @@ public class SystemPatch: PatchObject
public Partial<string?> Token { get; set; }
public Partial<string?> WebhookUrl { get; set; }
public Partial<string?> WebhookToken { get; set; }
public Partial<PrivacyLevel> NamePrivacy { get; set; }
public Partial<PrivacyLevel> AvatarPrivacy { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
public Partial<PrivacyLevel> MemberListPrivacy { get; set; }
public Partial<PrivacyLevel> GroupListPrivacy { get; set; }
@ -37,6 +39,8 @@ public class SystemPatch: PatchObject
.With("token", Token)
.With("webhook_url", WebhookUrl)
.With("webhook_token", WebhookToken)
.With("name_privacy", NamePrivacy)
.With("avatar_privacy", AvatarPrivacy)
.With("description_privacy", DescriptionPrivacy)
.With("member_list_privacy", MemberListPrivacy)
.With("group_list_privacy", GroupListPrivacy)
@ -93,6 +97,12 @@ public class SystemPatch: PatchObject
{
var privacy = o.Value<JObject>("privacy");
if (privacy.ContainsKey("name_privacy"))
patch.NamePrivacy = patch.ParsePrivacy(privacy, "name_privacy");
if (privacy.ContainsKey("avatar_privacy"))
patch.AvatarPrivacy = patch.ParsePrivacy(privacy, "avatar_privacy");
if (privacy.ContainsKey("description_privacy"))
patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "description_privacy");
@ -137,7 +147,9 @@ public class SystemPatch: PatchObject
o.Add("color", Color.Value);
if (
DescriptionPrivacy.IsPresent
NamePrivacy.IsPresent
|| AvatarPrivacy.IsPresent
|| DescriptionPrivacy.IsPresent
|| PronounPrivacy.IsPresent
|| MemberListPrivacy.IsPresent
|| GroupListPrivacy.IsPresent
@ -147,6 +159,12 @@ public class SystemPatch: PatchObject
{
var p = new JObject();
if (NamePrivacy.IsPresent)
p.Add("name_privacy", NamePrivacy.Value.ToJsonString());
if (AvatarPrivacy.IsPresent)
p.Add("avatar_privacy", AvatarPrivacy.Value.ToJsonString());
if (DescriptionPrivacy.IsPresent)
p.Add("description_privacy", DescriptionPrivacy.Value.ToJsonString());

View file

@ -2,6 +2,8 @@ namespace PluralKit.Core;
public enum SystemPrivacySubject
{
Name,
Avatar,
Description,
Pronouns,
MemberList,
@ -17,6 +19,8 @@ public static class SystemPrivacyUtils
// what do you mean switch expressions can't be statements >.>
_ = subject switch
{
SystemPrivacySubject.Name => system.NamePrivacy = level,
SystemPrivacySubject.Avatar => system.AvatarPrivacy = level,
SystemPrivacySubject.Description => system.DescriptionPrivacy = level,
SystemPrivacySubject.Pronouns => system.PronounPrivacy = level,
SystemPrivacySubject.Front => system.FrontPrivacy = level,
@ -40,6 +44,15 @@ public static class SystemPrivacyUtils
{
switch (input.ToLowerInvariant())
{
case "name":
subject = SystemPrivacySubject.Name;
break;
case "avatar":
case "pfp":
case "pic":
case "icon":
subject = SystemPrivacySubject.Avatar;
break;
case "description":
case "desc":
case "text":

View file

@ -9,6 +9,8 @@ public class SystemGuildSettings
public bool ProxyEnabled { get; } = true;
public string? Tag { get; }
public bool TagEnabled { get; }
public string? AvatarUrl { get; }
public string? DisplayName { get; }
}
public static class SystemGuildExt
@ -20,6 +22,8 @@ public static class SystemGuildExt
o.Add("proxying_enabled", settings.ProxyEnabled);
o.Add("tag", settings.Tag);
o.Add("tag_enabled", settings.TagEnabled);
o.Add("avatar_url", settings.AvatarUrl);
o.Add("display_name", settings.DisplayName);
return o;
}

View file

@ -50,8 +50,10 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the
- `pk;system [system]` - Shows information about a system.
- `pk;system new [name]` - Creates a new system registered to your account.
- `pk;system [system] rename [new name]` - Changes the name of your system.
- `pk;system [system] servername [servername]` - Changes the name of your system in the current server.
- `pk;system [system] description [description]` - Changes the description of your system.
- `pk;system [system] avatar [avatar url|@mention|upload]` - Changes the avatar of your system.
- `pk;system [system] serveravatar [avatar url|@mention|upload]` - Changes the avatar of your system in the current server.
- `pk;system [system] banner [image url|upload]` - Changes your system's banner image.
- `pk;system [system] color [color]` - Changes your system's color.
- `pk;system [system] privacy` - Displays your system's current privacy settings.

View file

@ -41,6 +41,18 @@ To view information about *a different* system, there are a number of ways to do
pk;system 466378653216014359
pk;system abcde
### System renaming
If you want to change the name of your system, you can use the `pk;system rename` command, like so:
pk;system rename New System Name
### System server names
If you'd like to set a name for your system, but only for a specific server, you can set the system's *server display name*. This shows up in replacement of the server name in the server you set it in. For example:
pk;system servername Name For This Server
To clear your system servername for a server, simply run `pk;system servername clear` in in the server in question. The servername cannot be run in DMs, it only applies to servers.
### System description
If you'd like to add a small blurb to your system information card, you can add a *system description*. To do so, use the `pk;system description` command, as follows:
@ -51,13 +63,21 @@ There's a 1000 character length limit on your system description - which is quit
If you'd like to remove your system description, just type `pk;system description` without any further parameters.
### System avatars
If you'd like your system to have an associated "system avatar", displayed on your system information card, you can add a system avatar. To do so, use the `pk;system avatar` command. You can either supply it with an direct URL to an image, or attach an image directly. For example.
If you'd like your system to have an associated "system avatar", displayed on your system information card, you can add a system avatar. Your system avatar will also show up as the avatar on members who do not have their own when proxying. To do so, use the `pk;system avatar` command. You can either supply it with an direct URL to an image, or attach an image directly. For example.
pk;system avatar http://placebeard.it/512.jpg
pk;system avatar [with attached image]
To clear your avatar, simply type `pk;system avatar` with no attachment or link.
### System server avatars
If you'd like your system to have an avatar (as above), but only for a specific server, you can set the *system server avatar*. This will override the global system avatar, but only in the server you set it in. For example:
pk;system serveravatar http://placebeard.it/512.jpg
pk;system serveravatar [with attached image]
To clear your system serveravatar for a server, simply type `pk;system serveravatar clear` with no attachment or link in the server in question. The serveravatar command cannot be run in DMs, it only functions in servers.
### System tags
Your system tag is a little snippet of text that'll be added to the end of all proxied messages.
For example, if you want to proxy a member named `James`, and your system tag is `| The Boys`, the final name displayed
@ -645,6 +665,8 @@ At the moment, there are a few aspects of system privacy that can be configured.
- Group list
- Current fronter
- Front history
- System name
- System avatar
Each of these can be set to **public** or **private**. When set to **public**, anyone who queries your system (by account or system ID, or through the API), will see this information. When set to **private**, the information will only be shown when *you yourself* query the information. The cards will still be displayed in the channel the commands are run in, so it's still your responsibility not to pull up information in servers where you don't want it displayed.
@ -659,6 +681,8 @@ To update your system privacy settings, use the following commands:
* `groups`
* `fronter`
* `fronthistory`
* `name`
* `avatar`
* `all` (to change all subjects at once)
* `level` is either `public` or `private`