diff --git a/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs b/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs index b9ac4372..68d46aa3 100644 --- a/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs +++ b/Myriad/Types/Application/InteractionApplicationCommandCallbackData.cs @@ -1,14 +1,28 @@ -using Myriad.Rest.Types; +using System.Text.Json.Serialization; + +using Myriad.Rest.Types; +using Myriad.Utils; namespace Myriad.Types { public record InteractionApplicationCommandCallbackData { - public bool? Tts { get; init; } - public string? Content { get; init; } - public Embed[]? Embeds { get; init; } - public AllowedMentions? AllowedMentions { get; init; } - public Message.MessageFlags Flags { get; init; } - public MessageComponent[]? Components { get; init; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Tts { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Content { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Embeds { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional AllowedMentions { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Flags { get; init; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Optional Components { get; init; } } } \ No newline at end of file diff --git a/PluralKit.Bot/Bot.cs b/PluralKit.Bot/Bot.cs index 9d869340..94ee2816 100644 --- a/PluralKit.Bot/Bot.cs +++ b/PluralKit.Bot/Bot.cs @@ -14,7 +14,6 @@ using Myriad.Cache; using Myriad.Extensions; using Myriad.Gateway; using Myriad.Rest; -using Myriad.Rest.Exceptions; using Myriad.Types; using NodaTime; diff --git a/PluralKit.Bot/BotConfig.cs b/PluralKit.Bot/BotConfig.cs index 095b77cd..378e20d1 100644 --- a/PluralKit.Bot/BotConfig.cs +++ b/PluralKit.Bot/BotConfig.cs @@ -13,5 +13,7 @@ namespace PluralKit.Bot public string[] Prefixes { get; set; } public int? MaxShardConcurrency { get; set; } + + public ulong? AdminRole { get; set; } } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Admin.cs b/PluralKit.Bot/Commands/Admin.cs new file mode 100644 index 00000000..0e492cc2 --- /dev/null +++ b/PluralKit.Bot/Commands/Admin.cs @@ -0,0 +1,128 @@ +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +using PluralKit.Bot.Interactive; +using PluralKit.Core; + +namespace PluralKit.Bot +{ + public class Admin + { + private readonly BotConfig _botConfig; + private readonly IDatabase _db; + private readonly ModelRepository _repo; + + public Admin(BotConfig botConfig, IDatabase db, ModelRepository repo) + { + _botConfig = botConfig; + _db = db; + _repo = repo; + } + + public async Task UpdateSystemId(Context ctx) + { + AssertBotAdmin(ctx); + + var target = await ctx.MatchSystem(); + if (target == null) + throw new PKError("Unknown system."); + + var newHid = ctx.PopArgument(); + if (!Regex.IsMatch(newHid, "^[a-z]{5}$")) + throw new PKError($"Invalid new system ID `{newHid}`."); + + var existingSystem = await _db.Execute(c => _repo.GetSystemByHid(c, newHid)); + if (existingSystem != null) + throw new PKError($"Another system already exists with ID `{newHid}`."); + + var prompt = new YesNoPrompt(ctx) + { + Message = $"Change system ID of `{target.Hid}` to `{newHid}`?", + AcceptLabel = "Change" + }; + await prompt.Run(); + + await _db.Execute(c => _repo.UpdateSystem(c, target.Id, new SystemPatch {Hid = newHid})); + await ctx.Reply($"{Emojis.Success} System ID updated (`{target.Hid}` -> `{newHid}`)."); + } + + public async Task UpdateMemberId(Context ctx) + { + AssertBotAdmin(ctx); + + var target = await ctx.MatchMember(); + if (target == null) + throw new PKError("Unknown member."); + + var newHid = ctx.PopArgument(); + if (!Regex.IsMatch(newHid, "^[a-z]{5}$")) + throw new PKError($"Invalid new member ID `{newHid}`."); + + var existingMember = await _db.Execute(c => _repo.GetMemberByHid(c, newHid)); + if (existingMember != null) + throw new PKError($"Another member already exists with ID `{newHid}`."); + + var prompt = new YesNoPrompt(ctx) + { + Message = $"Change member ID of **{target.NameFor(LookupContext.ByNonOwner)}** (`{target.Hid}`) to `{newHid}`?", + AcceptLabel = "Change" + }; + await prompt.Run(); + + if (prompt.Result != true) + throw new PKError("ID change cancelled."); + + await _db.Execute(c => _repo.UpdateMember(c, target.Id, new MemberPatch {Hid = newHid})); + await ctx.Reply($"{Emojis.Success} Member ID updated (`{target.Hid}` -> `{newHid}`)."); + } + + public async Task SystemMemberLimit(Context ctx) + { + AssertBotAdmin(ctx); + + var target = await ctx.MatchSystem(); + if (target == null) + throw new PKError("Unknown system."); + + var currentLimit = target.MemberLimitOverride ?? Limits.MaxMemberCount; + if (!ctx.HasNext()) + { + await ctx.Reply($"Current member limit is **{currentLimit}** members."); + return; + } + + var newLimitStr = ctx.PopArgument(); + if (!int.TryParse(newLimitStr, out var newLimit)) + throw new PKError($"Couldn't parse `{newLimitStr}` as number."); + + var prompt = new YesNoPrompt(ctx) + { + Message = $"Update member limit from **{currentLimit}** to **{newLimit}**?", + AcceptLabel = "Update" + }; + await prompt.Run(); + + if (prompt.Result != true) + throw new PKError("Member limit change cancelled."); + + await using var conn = await _db.Obtain(); + await _repo.UpdateSystem(conn, target.Id, new SystemPatch + { + MemberLimitOverride = newLimit + }); + await ctx.Reply($"{Emojis.Success} Member limit updated."); + } + + private void AssertBotAdmin(Context ctx) + { + if (!IsBotAdmin(ctx)) + throw new PKError("This command is only usable by bot admins."); + } + + private bool IsBotAdmin(Context ctx) + { + return _botConfig.AdminRole != null && ctx.Member.Roles.Contains(_botConfig.AdminRole.Value); + } + } +} \ No newline at end of file diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 8c3ade92..51250527 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -90,6 +90,7 @@ namespace PluralKit.Bot public static Command BlacklistRemove = new Command("blacklist remove", "blacklist remove all| [channel 2] [channel 3...]", "Removes certain channels from the proxy blacklist"); public static Command Invite = new Command("invite", "invite", "Gets a link to invite PluralKit to other servers"); public static Command PermCheck = new Command("permcheck", "permcheck ", "Checks whether a server's permission setup is correct"); + public static Command Admin = new Command("admin", "admin", "Super secret admin commands (sshhhh)"); public static Command[] SystemCommands = { SystemInfo, SystemNew, SystemRename, SystemTag, SystemDesc, SystemAvatar, SystemColor, SystemDelete, SystemTimezone, @@ -197,6 +198,8 @@ namespace PluralKit.Bot if (ctx.Match("stats")) return ctx.Execute(null, m => m.Stats(ctx)); if (ctx.Match("permcheck")) return ctx.Execute(PermCheck, m => m.PermCheckGuild(ctx)); + if (ctx.Match("admin")) + return HandleAdminCommand(ctx); if (ctx.Match("random", "r")) if (ctx.Match("group", "g") || ctx.MatchFlag("group", "g")) return ctx.Execute(GroupRandom, r => r.Group(ctx)); @@ -208,6 +211,18 @@ namespace PluralKit.Bot $"{Emojis.Error} Unknown command {ctx.PeekArgument().AsCode()}. For a list of possible commands, see ."); } + private async Task HandleAdminCommand(Context ctx) + { + if (ctx.Match("usid", "updatesystemid")) + await ctx.Execute(Admin, a => a.UpdateSystemId(ctx)); + else if (ctx.Match("umid", "updatememberid")) + await ctx.Execute(Admin, a => a.UpdateMemberId(ctx)); + else if (ctx.Match("uml", "updatememberlimit")) + await ctx.Execute(Admin, a => a.SystemMemberLimit(ctx)); + else + await ctx.Reply($"{Emojis.Error} Unknown command."); + } + private async Task HandleSystemCommand(Context ctx) { // If we have no parameters, default to self-target diff --git a/PluralKit.Bot/Interactive/BaseInteractive.cs b/PluralKit.Bot/Interactive/BaseInteractive.cs index 36cbf29f..36eb9f57 100644 --- a/PluralKit.Bot/Interactive/BaseInteractive.cs +++ b/PluralKit.Bot/Interactive/BaseInteractive.cs @@ -43,13 +43,11 @@ namespace PluralKit.Bot.Interactive return button; } - protected async Task Update(InteractionContext ctx, string? content = null, Embed? embed = null) + protected async Task Update(InteractionContext ctx) { await ctx.Respond(InteractionResponse.ResponseType.UpdateMessage, new InteractionApplicationCommandCallbackData { - Content = content, - Embeds = embed != null ? new[] { embed } : null, Components = GetComponents() }); } diff --git a/PluralKit.Bot/Modules.cs b/PluralKit.Bot/Modules.cs index 28bfac5f..4a940de4 100644 --- a/PluralKit.Bot/Modules.cs +++ b/PluralKit.Bot/Modules.cs @@ -44,6 +44,7 @@ namespace PluralKit.Bot // Commands builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); diff --git a/PluralKit.Core/Models/Patch/MemberPatch.cs b/PluralKit.Core/Models/Patch/MemberPatch.cs index f07b3074..cd7054ed 100644 --- a/PluralKit.Core/Models/Patch/MemberPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberPatch.cs @@ -8,6 +8,7 @@ namespace PluralKit.Core public class MemberPatch: PatchObject { public Partial Name { get; set; } + public Partial Hid { get; set; } public Partial DisplayName { get; set; } public Partial AvatarUrl { get; set; } public Partial Color { get; set; } @@ -28,6 +29,7 @@ namespace PluralKit.Core public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b .With("name", Name) + .With("hid", Hid) .With("display_name", DisplayName) .With("avatar_url", AvatarUrl) .With("color", Color) diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index dbbfa1f5..38e122d0 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -6,6 +6,7 @@ namespace PluralKit.Core public class SystemPatch: PatchObject { public Partial Name { get; set; } + public Partial Hid { get; set; } public Partial Description { get; set; } public Partial Tag { get; set; } public Partial AvatarUrl { get; set; } @@ -19,9 +20,12 @@ namespace PluralKit.Core public Partial FrontHistoryPrivacy { get; set; } public Partial PingsEnabled { get; set; } public Partial LatchTimeout { get; set; } + public Partial MemberLimitOverride { get; set; } + public Partial GroupLimitOverride { get; set; } public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b .With("name", Name) + .With("hid", Hid) .With("description", Description) .With("tag", Tag) .With("avatar_url", AvatarUrl) @@ -34,7 +38,9 @@ namespace PluralKit.Core .With("front_privacy", FrontPrivacy) .With("front_history_privacy", FrontHistoryPrivacy) .With("pings_enabled", PingsEnabled) - .With("latch_timeout", LatchTimeout); + .With("latch_timeout", LatchTimeout) + .With("member_limit_override", MemberLimitOverride) + .With("group_limit_override", GroupLimitOverride); public new void CheckIsValid() {