Add a few utility admin commands
Signed-off-by: Ske <voltasalt@gmail.com>
This commit is contained in:
		| @@ -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<bool?> Tts { get; init; } | ||||
|          | ||||
|         [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] | ||||
|         public Optional<string?> Content { get; init; } | ||||
|          | ||||
|         [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] | ||||
|         public Optional<Embed[]?> Embeds { get; init; } | ||||
|          | ||||
|         [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] | ||||
|         public Optional<AllowedMentions?> AllowedMentions { get; init; } | ||||
|          | ||||
|         [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] | ||||
|         public Optional<Message.MessageFlags> Flags { get; init; } | ||||
|          | ||||
|         [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] | ||||
|         public Optional<MessageComponent[]?> Components { get; init; } | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
| @@ -13,5 +13,7 @@ namespace PluralKit.Bot | ||||
|         public string[] Prefixes { get; set; } | ||||
|          | ||||
|         public int? MaxShardConcurrency { get; set; } | ||||
|          | ||||
|         public ulong? AdminRole { get; set; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								PluralKit.Bot/Commands/Admin.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								PluralKit.Bot/Commands/Admin.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -90,6 +90,7 @@ namespace PluralKit.Bot | ||||
|         public static Command BlacklistRemove = new Command("blacklist remove", "blacklist remove all|<channel> [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 <guild>", "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<Misc>(null, m => m.Stats(ctx)); | ||||
|             if (ctx.Match("permcheck")) | ||||
|                 return ctx.Execute<Misc>(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<Random>(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 <https://pluralkit.me/commands>."); | ||||
|         } | ||||
|  | ||||
|         private async Task HandleAdminCommand(Context ctx) | ||||
|         { | ||||
|             if (ctx.Match("usid", "updatesystemid")) | ||||
|                 await ctx.Execute<Admin>(Admin, a => a.UpdateSystemId(ctx)); | ||||
|             else if (ctx.Match("umid", "updatememberid")) | ||||
|                 await ctx.Execute<Admin>(Admin, a => a.UpdateMemberId(ctx)); | ||||
|             else if (ctx.Match("uml", "updatememberlimit")) | ||||
|                 await ctx.Execute<Admin>(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 | ||||
|   | ||||
| @@ -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() | ||||
|                 }); | ||||
|         } | ||||
|   | ||||
| @@ -44,6 +44,7 @@ namespace PluralKit.Bot | ||||
|  | ||||
|             // Commands | ||||
|             builder.RegisterType<CommandTree>().AsSelf(); | ||||
|             builder.RegisterType<Admin>().AsSelf(); | ||||
|             builder.RegisterType<Autoproxy>().AsSelf(); | ||||
|             builder.RegisterType<Fun>().AsSelf(); | ||||
|             builder.RegisterType<Groups>().AsSelf(); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ namespace PluralKit.Core | ||||
|     public class MemberPatch: PatchObject | ||||
|     { | ||||
|         public Partial<string> Name { get; set; } | ||||
|         public Partial<string> Hid { get; set; } | ||||
|         public Partial<string?> DisplayName { get; set; } | ||||
|         public Partial<string?> AvatarUrl { get; set; } | ||||
|         public Partial<string?> 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) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ namespace PluralKit.Core | ||||
|     public class SystemPatch: PatchObject | ||||
|     { | ||||
|         public Partial<string?> Name { get; set; } | ||||
|         public Partial<string?> Hid { get; set; } | ||||
|         public Partial<string?> Description { get; set; } | ||||
|         public Partial<string?> Tag { get; set; } | ||||
|         public Partial<string?> AvatarUrl { get; set; } | ||||
| @@ -19,9 +20,12 @@ namespace PluralKit.Core | ||||
|         public Partial<PrivacyLevel> FrontHistoryPrivacy { get; set; } | ||||
|         public Partial<bool> PingsEnabled { get; set; } | ||||
|         public Partial<int?> LatchTimeout { get; set; } | ||||
|         public Partial<int?> MemberLimitOverride { get; set; } | ||||
|         public Partial<int?> 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() | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user