diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index 642be4f9..8671b0b9 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -8,6 +8,7 @@ public partial class CommandTree 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 SystemBannerImage = new Command("system banner", "system [system] banner [url]", "Set the system's banner image"); diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 78fbac6f..02bcd374 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -213,6 +213,8 @@ public partial class CommandTree await ctx.CheckSystem(target).Execute(SystemServerTag, m => m.ServerTag(ctx, target)); else if (ctx.Match("description", "desc", "bio")) await ctx.CheckSystem(target).Execute(SystemDesc, m => m.Description(ctx, target)); + else if (ctx.Match("pronouns", "prns")) + await ctx.CheckSystem(target).Execute(SystemPronouns, m => m.Pronouns(ctx, target)); else if (ctx.Match("color", "colour")) await ctx.CheckSystem(target).Execute(SystemColor, m => m.Color(ctx, target)); else if (ctx.Match("banner", "splash", "cover")) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 85a848d8..6149ecf0 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -339,6 +339,58 @@ public class SystemEdit await Set(); } + public async Task Pronouns(Context ctx, PKSystem target) + { + ctx.CheckSystemPrivacy(target.Id, target.PronounPrivacy); + + var isOwnSystem = ctx.System.Id == target.Id; + + var noPronounsSetMessage = "This system does not have pronouns set."; + if (isOwnSystem) + noPronounsSetMessage += " To set some, type `pk;system pronouns `"; + + if (ctx.MatchRaw()) + { + if (target.Pronouns == null) + await ctx.Reply(noPronounsSetMessage); + else + await ctx.Reply($"```\n{target.Pronouns}\n```"); + return; + } + + if (!ctx.HasNext(false)) + { + if (target.Pronouns == null) + await ctx.Reply(noPronounsSetMessage); + else + await ctx.Reply($"{(isOwnSystem ? "Your" : "This system's")} current pronouns are **{target.Pronouns}**.\nTo print the pronouns with formatting, type `pk;system pronouns -raw`." + + (isOwnSystem ? " To clear them, type `pk;system pronouns -clear`." + : "" )); + return; + } + + ctx.CheckSystem().CheckOwnSystem(target); + + if (await ctx.MatchClear("your system's pronouns")) + { + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Pronouns = null }); + + await ctx.Reply($"{Emojis.Success} System pronouns cleared."); + } + else + { + var newPronouns = ctx.RemainderOrNull(false).NormalizeLineEndSpacing(); + if (newPronouns != null) + if (newPronouns.Length > Limits.MaxPronounsLength) + throw Errors.StringTooLongError("Pronouns", newPronouns.Length, Limits.MaxPronounsLength); + + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { Pronouns = newPronouns }); + + await ctx.Reply( + $"{Emojis.Success} System pronouns changed."); + } + } + public async Task Avatar(Context ctx, PKSystem target) { async Task ClearIcon() @@ -525,6 +577,7 @@ public class SystemEdit var eb = new EmbedBuilder() .Title("Current privacy settings for your system") .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())) .Field(new Embed.Field("Group list", target.GroupListPrivacy.Explanation())) .Field(new Embed.Field("Current fronter(s)", target.FrontPrivacy.Explanation())) @@ -548,6 +601,7 @@ public class SystemEdit var subjectStr = subject switch { SystemPrivacySubject.Description => "description", + SystemPrivacySubject.Pronouns => "pronouns", SystemPrivacySubject.Front => "front", SystemPrivacySubject.FrontHistory => "front history", SystemPrivacySubject.MemberList => "member list", diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index d57e3763..f76d33a3 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -103,6 +103,9 @@ public class EmbedService "*(tag is disabled in this server)*")); } + if (system.PronounPrivacy.CanAccess(ctx) && system.Pronouns != null) + eb.Field(new Embed.Field("Pronouns", system.Pronouns, true)); + if (!system.Color.EmptyOrNull()) eb.Field(new Embed.Field("Color", $"#{system.Color}", true)); eb.Field(new Embed.Field("Linked accounts", string.Join("\n", users).Truncate(1000), true)); diff --git a/PluralKit.Core/Database/Migrations/28.sql b/PluralKit.Core/Database/Migrations/28.sql new file mode 100644 index 00000000..5e22db7d --- /dev/null +++ b/PluralKit.Core/Database/Migrations/28.sql @@ -0,0 +1,7 @@ +-- schema version 28 +-- system pronouns + +alter table systems add column pronouns text; +alter table systems add column pronoun_privacy integer check (pronoun_privacy in (1, 2)) not null default 1; + +update info set schema_version = 28; \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index ed22a618..26eff955 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -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 = 27; + private const int TargetSchemaVersion = 28; private readonly ILogger _logger; public DatabaseMigrator(ILogger logger) diff --git a/PluralKit.Core/Models/PKSystem.cs b/PluralKit.Core/Models/PKSystem.cs index 340a3563..ce992d55 100644 --- a/PluralKit.Core/Models/PKSystem.cs +++ b/PluralKit.Core/Models/PKSystem.cs @@ -39,6 +39,7 @@ public class PKSystem public string Name { get; } public string Description { get; } public string Tag { get; } + public string Pronouns { get; } public string AvatarUrl { get; } public string BannerImage { get; } public string Color { get; } @@ -51,6 +52,7 @@ public class PKSystem public PrivacyLevel FrontPrivacy { get; } public PrivacyLevel FrontHistoryPrivacy { get; } public PrivacyLevel GroupListPrivacy { get; } + public PrivacyLevel PronounPrivacy { get; } } public static class PKSystemExt @@ -68,6 +70,9 @@ public static class PKSystemExt o.Add("name", system.Name); o.Add("description", system.DescriptionFor(ctx)); o.Add("tag", system.Tag); + if (v == APIVersion.V2) + o.Add("pronouns", system.PronounPrivacy.Get(ctx, system.Pronouns)); + o.Add("avatar_url", system.AvatarUrl.TryGetCleanCdnUrl()); o.Add("banner", system.DescriptionPrivacy.Get(ctx, system.BannerImage).TryGetCleanCdnUrl()); o.Add("color", system.Color); @@ -103,6 +108,7 @@ public static class PKSystemExt var p = new JObject(); p.Add("description_privacy", system.DescriptionPrivacy.ToJsonString()); + p.Add("pronoun_privacy", system.PronounPrivacy.ToJsonString()); p.Add("member_list_privacy", system.MemberListPrivacy.ToJsonString()); p.Add("group_list_privacy", system.GroupListPrivacy.ToJsonString()); p.Add("front_privacy", system.FrontPrivacy.ToJsonString()); diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index 201e9b40..855ce8fd 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -13,6 +13,7 @@ public class SystemPatch: PatchObject public Partial Hid { get; set; } public Partial Description { get; set; } public Partial Tag { get; set; } + public Partial Pronouns { get; set; } public Partial AvatarUrl { get; set; } public Partial BannerImage { get; set; } public Partial Color { get; set; } @@ -24,12 +25,14 @@ public class SystemPatch: PatchObject public Partial GroupListPrivacy { get; set; } public Partial FrontPrivacy { get; set; } public Partial FrontHistoryPrivacy { get; set; } + public Partial PronounPrivacy { get; set; } public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper .With("name", Name) .With("hid", Hid) .With("description", Description) .With("tag", Tag) + .With("pronouns", Pronouns) .With("avatar_url", AvatarUrl) .With("banner_image", BannerImage) .With("color", Color) @@ -41,6 +44,7 @@ public class SystemPatch: PatchObject .With("group_list_privacy", GroupListPrivacy) .With("front_privacy", FrontPrivacy) .With("front_history_privacy", FrontHistoryPrivacy) + .With("pronoun_privacy", PronounPrivacy) ); public new void AssertIsValid() @@ -51,6 +55,8 @@ public class SystemPatch: PatchObject AssertValid(Description.Value, "description", Limits.MaxDescriptionLength); if (Tag.Value != null) AssertValid(Tag.Value, "tag", Limits.MaxSystemTagLength); + if (Pronouns.Value != null) + AssertValid(Pronouns.Value, "pronouns", Limits.MaxPronounsLength); if (AvatarUrl.Value != null) AssertValid(AvatarUrl.Value, "avatar_url", Limits.MaxUriLength, s => MiscUtils.TryMatchUri(s, out var avatarUri)); @@ -69,6 +75,7 @@ public class SystemPatch: PatchObject if (o.ContainsKey("name")) patch.Name = o.Value("name").NullIfEmpty(); if (o.ContainsKey("description")) patch.Description = o.Value("description").NullIfEmpty(); if (o.ContainsKey("tag")) patch.Tag = o.Value("tag").NullIfEmpty(); + if (o.ContainsKey("pronouns")) patch.Pronouns = o.Value("pronouns").NullIfEmpty(); if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value("avatar_url").NullIfEmpty(); if (o.ContainsKey("banner")) patch.BannerImage = o.Value("banner").NullIfEmpty(); if (o.ContainsKey("color")) patch.Color = o.Value("color").NullIfEmpty(); @@ -96,6 +103,9 @@ public class SystemPatch: PatchObject if (privacy.ContainsKey("description_privacy")) patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "description_privacy"); + if (privacy.ContainsKey("pronoun_privacy")) + patch.PronounPrivacy = patch.ParsePrivacy(privacy, "pronoun_privacy"); + if (privacy.ContainsKey("member_list_privacy")) patch.MemberListPrivacy = patch.ParsePrivacy(privacy, "member_list_privacy"); @@ -128,6 +138,8 @@ public class SystemPatch: PatchObject o.Add("description", Description.Value); if (Tag.IsPresent) o.Add("tag", Tag.Value); + if (Pronouns.IsPresent) + o.Add("pronouns", Pronouns.Value); if (AvatarUrl.IsPresent) o.Add("avatar_url", AvatarUrl.Value); if (BannerImage.IsPresent) @@ -137,6 +149,7 @@ public class SystemPatch: PatchObject if ( DescriptionPrivacy.IsPresent + || PronounPrivacy.IsPresent || MemberListPrivacy.IsPresent || GroupListPrivacy.IsPresent || FrontPrivacy.IsPresent @@ -148,6 +161,9 @@ public class SystemPatch: PatchObject if (DescriptionPrivacy.IsPresent) p.Add("description_privacy", DescriptionPrivacy.Value.ToJsonString()); + if (PronounPrivacy.IsPresent) + p.Add("pronoun_privacy", PronounPrivacy.Value.ToJsonString()); + if (MemberListPrivacy.IsPresent) p.Add("member_list_privacy", MemberListPrivacy.Value.ToJsonString()); diff --git a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs index 4d8bc36e..13f4af37 100644 --- a/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs +++ b/PluralKit.Core/Models/Privacy/SystemPrivacySubject.cs @@ -3,6 +3,7 @@ namespace PluralKit.Core; public enum SystemPrivacySubject { Description, + Pronouns, MemberList, GroupList, Front, @@ -17,6 +18,7 @@ public static class SystemPrivacyUtils _ = subject switch { SystemPrivacySubject.Description => system.DescriptionPrivacy = level, + SystemPrivacySubject.Pronouns => system.PronounPrivacy = level, SystemPrivacySubject.Front => system.FrontPrivacy = level, SystemPrivacySubject.FrontHistory => system.FrontHistoryPrivacy = level, SystemPrivacySubject.MemberList => system.MemberListPrivacy = level, @@ -44,6 +46,10 @@ public static class SystemPrivacyUtils case "info": subject = SystemPrivacySubject.Description; break; + case "pronouns": + case "prns": + subject = SystemPrivacySubject.Pronouns; + break; case "members": case "memberlist": case "list": diff --git a/docs/content/api/models.md b/docs/content/api/models.md index 124af596..8515a523 100644 --- a/docs/content/api/models.md +++ b/docs/content/api/models.md @@ -24,13 +24,14 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID. |name|string|100-character limit| |description|?string|1000-character limit| |tag|string|| +|pronouns|?string|100-character limit| |avatar_url|?string|256-character limit, must be a publicly-accessible URL| |banner|?string|256-character limit, must be a publicly-accessible URL| |color|string|6-character hex code, no `#` at the beginning| |created|datetime|| |privacy|?system privacy object|| -* System privacy keys: `description_privacy`, `member_list_privacy`, `group_list_privacy`, `front_privacy`, `front_history_privacy` +* System privacy keys: `description_privacy`, `pronoun_privacy`, `member_list_privacy`, `group_list_privacy`, `front_privacy`, `front_history_privacy` ### Member model diff --git a/docs/content/command-list.md b/docs/content/command-list.md index dfe93be7..07a9839d 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -43,6 +43,7 @@ Some arguments indicate the use of specific Discord features. These include: - `pk;system [system] privacy ` - Changes your systems privacy settings. - `pk;system [system] tag [tag]` - Changes the system tag of your system. - `pk;system [system] servertag [tag|-enable|-disable]` - Changes your system's tag in the current server, or disables it for the current server. +- `pk;system [system] pronouns [pronouns]` - Changes the pronouns of your system. - `pk;system proxy [server id] [on|off]` - Toggles message proxying for a specific server. - `pk;system [system] delete` - Deletes your system. - `pk;system [system] fronter` - Shows the current fronter of a system.