diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index 0a412099..bb4b1ffc 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -23,6 +23,8 @@ public partial class CommandTree public static Command ConfigPing = new Command("config ping", "config ping ", "Changes your system's ping preferences"); public static Command ConfigAutoproxyAccount = new Command("config autoproxy account", "autoproxy account [on|off]", "Toggles autoproxy globally for the current account"); public static Command ConfigAutoproxyTimeout = new Command("config autoproxy timeout", "autoproxy timeout [|off|reset]", "Sets the latch timeout duration for your system"); + public static Command ConfigMemberDefaultPrivacy = new("config member private", "config member private [on|off]", "Sets whether member privacy is automatically set to private when creating a new member"); + public static Command ConfigGroupDefaultPrivacy = new("config group private", "config group private [on|off]", "Sets whether group privacy is automatically set to private when creating a new group"); public static Command AutoproxySet = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for the current server"); public static Command MemberInfo = new Command("member", "member ", "Looks up information about a member"); public static Command MemberNew = new Command("member new", "member new ", "Creates a new member"); @@ -125,7 +127,8 @@ public partial class CommandTree public static Command[] ConfigCommands = { - ConfigTimezone, ConfigPing, ConfigAutoproxyAccount, ConfigAutoproxyTimeout + ConfigTimezone, ConfigPing, ConfigAutoproxyAccount, ConfigAutoproxyTimeout, + ConfigMemberDefaultPrivacy, ConfigGroupDefaultPrivacy }; public static Command[] LogCommands = { LogChannel, LogChannelClear, LogEnable, LogDisable }; diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 215ec25e..d39f02b1 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -481,6 +481,10 @@ public partial class CommandTree return ctx.Execute(null, m => m.SystemTimezone(ctx)); if (ctx.Match("ping")) return ctx.Execute(null, m => m.SystemPing(ctx)); + if (ctx.MatchMultiple(new[] { "private member" }) || ctx.Match("mp")) + return ctx.Execute(null, m => m.MemberDefaultPrivacy(ctx)); + if (ctx.MatchMultiple(new[] { "private group" }) || ctx.Match("gp")) + return ctx.Execute(null, m => m.GroupDefaultPrivacy(ctx)); // todo: maybe add the list of configuration keys here? return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `pk;commands config` for the list of possible config settings."); @@ -492,7 +496,6 @@ public partial class CommandTree await ctx.Reply( $"{Emojis.Error} Unknown command `pk;{ctx.FullCommand().Truncate(100)}`. Perhaps you meant to use one of the following commands?\n{commandListStr}\n\nFor a full list of possible commands, see ."); } - private async Task PrintCommandExpectedError(Context ctx, params Command[] potentialCommands) { var commandListStr = CreatePotentialCommandList(potentialCommands); diff --git a/PluralKit.Bot/Commands/Config.cs b/PluralKit.Bot/Commands/Config.cs index b14e70fc..d1233d29 100644 --- a/PluralKit.Bot/Commands/Config.cs +++ b/PluralKit.Bot/Commands/Config.cs @@ -58,6 +58,20 @@ public class Config "enabled" )); + items.Add(new( + "private member", + "Whether member privacy is automatically set to private for new members", + EnabledDisabled(ctx.Config.MemberDefaultPrivate), + "disabled" + )); + + items.Add(new( + "private group", + "Whether group privacy is automatically set to private for new groups", + EnabledDisabled(ctx.Config.GroupDefaultPrivate), + "disabled" + )); + items.Add(new( "Member limit", "The maximum number of registered members for your system", @@ -298,4 +312,56 @@ public class Config return $"**{z.Id}**"; }); } + + public async Task MemberDefaultPrivacy(Context ctx) + { + ctx.CheckSystem(); + + if (!ctx.HasNext()) + { + if (ctx.Config.MemberDefaultPrivate) { await ctx.Reply("Newly created members will currently have their privacy settings set to private. To change this, type `pk;config private member off`"); } + else { await ctx.Reply("Newly created members will currently have their privacy settings set to public. To automatically set new members' privacy settings to private, type `pk;config private member on`"); } + } + else + { + if (ctx.Match("on", "enable")) + { + await _repo.UpdateSystemConfig(ctx.System.Id, new() { MemberDefaultPrivate = true }); + + await ctx.Reply("Newly created members will now have their privacy settings set to private."); + } + if (ctx.Match("off", "disable")) + { + await _repo.UpdateSystemConfig(ctx.System.Id, new() { MemberDefaultPrivate = false }); + + await ctx.Reply("Newly created members will now have their privacy settings set to public."); + } + } + } + + public async Task GroupDefaultPrivacy(Context ctx) + { + ctx.CheckSystem(); + + if (!ctx.HasNext()) + { + if (ctx.Config.MemberDefaultPrivate) { await ctx.Reply("Newly created groups will currently have their privacy settings set to private. To change this, type `pk;config private group off`"); } + else { await ctx.Reply("Newly created groups will currently have their privacy settings set to public. To automatically set new groups' privacy settings to private, type `pk;config private group on`"); } + } + else + { + if (ctx.Match("on", "enable")) + { + await _repo.UpdateSystemConfig(ctx.System.Id, new() { GroupDefaultPrivate = true }); + + await ctx.Reply("Newly created groups will now have their privacy settings set to private."); + } + if (ctx.Match("off", "disable")) + { + await _repo.UpdateSystemConfig(ctx.System.Id, new() { GroupDefaultPrivate = false }); + + await ctx.Reply("Newly created groups will now have their privacy settings set to public."); + } + } + } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index 8a190f85..3183a81c 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -64,14 +64,24 @@ public class Groups throw new PKError("Group creation cancelled."); } + using var conn = await _db.Obtain(); var newGroup = await _repo.CreateGroup(ctx.System.Id, groupName); - _ = _dispatch.Dispatch(newGroup.Id, - new UpdateDispatchData - { - Event = DispatchEvent.CREATE_GROUP, - EventData = JObject.FromObject(new { name = groupName }) - }); + var dispatchData = new JObject(); + dispatchData.Add("name", groupName); + + if (ctx.Config.GroupDefaultPrivate) + { + var patch = new GroupPatch().WithAllPrivacy(PrivacyLevel.Private); + await _repo.UpdateGroup(newGroup.Id, patch, conn); + dispatchData.Merge(patch.ToJson()); + } + + _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData + { + Event = DispatchEvent.CREATE_GROUP, + EventData = dispatchData + }); var eb = new EmbedBuilder() .Description( diff --git a/PluralKit.Bot/Commands/Member.cs b/PluralKit.Bot/Commands/Member.cs index d356ab72..98a678a7 100644 --- a/PluralKit.Bot/Commands/Member.cs +++ b/PluralKit.Bot/Commands/Member.cs @@ -55,13 +55,22 @@ public class Member throw Errors.MemberLimitReachedError(memberLimit); // Create the member - var member = await _repo.CreateMember(ctx.System.Id, memberName); + var member = await _repo.CreateMember(ctx.System.Id, memberName, conn); memberCount++; + JObject dispatchData = new JObject(); + dispatchData.Add("name", memberName); + + if (ctx.Config.MemberDefaultPrivate) + { + var patch = new MemberPatch().WithAllPrivacy(PrivacyLevel.Private); + await _repo.UpdateMember(member.Id, patch, conn); + dispatchData.Merge(patch.ToJson()); + } + // Try to match an image attached to the message var avatarArg = ctx.Message.Attachments.FirstOrDefault(); Exception imageMatchError = null; - var sentDispatch = false; if (avatarArg != null) try { @@ -69,31 +78,25 @@ public class Member await _db.Execute(conn => _repo.UpdateMember(member.Id, new MemberPatch { AvatarUrl = avatarArg.Url }, conn)); - _ = _dispatch.Dispatch(member.Id, new UpdateDispatchData - { - Event = DispatchEvent.CREATE_MEMBER, - EventData = JObject.FromObject(new { name = memberName, avatar_url = avatarArg.Url }), - }); - sentDispatch = true; + dispatchData.Add("avatar_url", avatarArg.Url); } catch (Exception e) { imageMatchError = e; } - if (!sentDispatch) - _ = _dispatch.Dispatch(member.Id, new UpdateDispatchData - { - Event = DispatchEvent.CREATE_MEMBER, - EventData = JObject.FromObject(new { name = memberName }), - }); + _ = _dispatch.Dispatch(member.Id, new UpdateDispatchData + { + Event = DispatchEvent.CREATE_MEMBER, + EventData = dispatchData, + }); // Send confirmation and space hint await ctx.Reply( $"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member"); // todo: move this to ModelRepository if (await _db.Execute(conn => conn.QuerySingleAsync("select has_private_members(@System)", - new { System = ctx.System.Id }))) //if has private members + new { System = ctx.System.Id })) && !ctx.Config.MemberDefaultPrivate) //if has private members await ctx.Reply( $"{Emojis.Warn} This member is currently **public**. To change this, use `pk;member {member.Hid} private`."); if (avatarArg != null) diff --git a/PluralKit.Core/Database/Migrations/22.sql b/PluralKit.Core/Database/Migrations/22.sql new file mode 100644 index 00000000..ee451556 --- /dev/null +++ b/PluralKit.Core/Database/Migrations/22.sql @@ -0,0 +1,7 @@ +-- schema version 22 +-- automatically set members/groups as private when creating + +alter table system_config add column member_default_private bool not null default false; +alter table system_config add column group_default_private bool not null default false; + +update info set schema_version = 22; \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index 4581ac19..a9e990c8 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 = 21; + private const int TargetSchemaVersion = 22; private readonly ILogger _logger; public DatabaseMigrator(ILogger logger) diff --git a/PluralKit.Core/Models/Patch/SystemConfigPatch.cs b/PluralKit.Core/Models/Patch/SystemConfigPatch.cs index 5344ec66..ecd91e0f 100644 --- a/PluralKit.Core/Models/Patch/SystemConfigPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemConfigPatch.cs @@ -11,6 +11,8 @@ public class SystemConfigPatch: PatchObject public Partial UiTz { get; set; } public Partial PingsEnabled { get; set; } public Partial LatchTimeout { get; set; } + public Partial MemberDefaultPrivate { get; set; } + public Partial GroupDefaultPrivate { get; set; } public Partial MemberLimitOverride { get; set; } public Partial GroupLimitOverride { get; set; } @@ -18,6 +20,8 @@ public class SystemConfigPatch: PatchObject .With("ui_tz", UiTz) .With("pings_enabled", PingsEnabled) .With("latch_timeout", LatchTimeout) + .With("member_default_private", MemberDefaultPrivate) + .With("group_default_private", GroupDefaultPrivate) .With("member_limit_override", MemberLimitOverride) .With("group_limit_override", GroupLimitOverride) ); @@ -41,6 +45,12 @@ public class SystemConfigPatch: PatchObject if (LatchTimeout.IsPresent) o.Add("latch_timeout", LatchTimeout.Value); + if (MemberDefaultPrivate.IsPresent) + o.Add("member_default_private", MemberDefaultPrivate.Value); + + if (GroupDefaultPrivate.IsPresent) + o.Add("group_default_private", GroupDefaultPrivate.Value); + if (MemberLimitOverride.IsPresent) o.Add("member_limit", MemberLimitOverride.Value); @@ -63,6 +73,12 @@ public class SystemConfigPatch: PatchObject if (o.ContainsKey("latch_timeout")) patch.LatchTimeout = o.Value("latch_timeout"); + if (o.ContainsKey("member_default_private")) + patch.MemberDefaultPrivate = o.Value("member_default_private"); + + if (o.ContainsKey("group_default_private")) + patch.GroupDefaultPrivate = o.Value("group_default_private"); + return patch; } } \ No newline at end of file diff --git a/PluralKit.Core/Models/SystemConfig.cs b/PluralKit.Core/Models/SystemConfig.cs index 5a8c4ddd..1f7f5875 100644 --- a/PluralKit.Core/Models/SystemConfig.cs +++ b/PluralKit.Core/Models/SystemConfig.cs @@ -10,6 +10,8 @@ public class SystemConfig public string UiTz { get; set; } public bool PingsEnabled { get; } public int? LatchTimeout { get; } + public bool MemberDefaultPrivate { get; } + public bool GroupDefaultPrivate { get; } public int? MemberLimitOverride { get; } public int? GroupLimitOverride { get; } @@ -25,6 +27,8 @@ public static class SystemConfigExt o.Add("timezone", cfg.UiTz); o.Add("pings_enabled", cfg.PingsEnabled); o.Add("latch_timeout", cfg.LatchTimeout); + o.Add("member_default_private", cfg.MemberDefaultPrivate); + o.Add("group_default_private", cfg.GroupDefaultPrivate); o.Add("member_limit", cfg.MemberLimitOverride ?? Limits.MaxMemberCount); o.Add("group_limit", cfg.GroupLimitOverride ?? Limits.MaxGroupCount); diff --git a/docs/content/api/models.md b/docs/content/api/models.md index 4de50cc2..a2cf3e10 100644 --- a/docs/content/api/models.md +++ b/docs/content/api/models.md @@ -106,9 +106,13 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID. |timezone|string|defaults to `UTC`| |pings_enabled|boolean| |latch_timeout|int?| +|member_default_private*|bool|whether members created through the bot have privacy settings set to private by default| +|group_default_private*|bool|whether groups created through the bot have privacy settings set to private by default| |member_limit|int|read-only, defaults to 1000| |group_limit|int|read-only, defaults to 250| +\* this *does not* affect members/groups created through the API - please specify privacy keys in the JSON payload instead + ### System guild settings model |key|type|notes|