diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 4a4bb498..9d6a860f 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -82,6 +82,19 @@ public partial class CommandTree Commands.SystemShowServerAvatar(var param, var flags) => ctx.Execute(SystemServerAvatar, m => m.ShowServerAvatar(ctx, param.target, flags.GetReplyFormat())), Commands.SystemClearServerAvatar(var param, var flags) => ctx.Execute(SystemServerAvatar, m => m.ClearServerAvatar(ctx, ctx.System, flags.yes)), Commands.SystemChangeServerAvatar(var param, _) => ctx.Execute(SystemServerAvatar, m => m.ChangeServerAvatar(ctx, ctx.System, param.avatar)), + Commands.SystemShowBannerSelf(_, var flags) => ((Func)(() => + { + // we want to change banner if an attached image is passed + // we can't have a separate parsed command for this since the parser can't be aware of any attachments + var attachedImage = ctx.ExtractImageFromAttachment(); + if (attachedImage is { } image) + return ctx.Execute(SystemBannerImage, m => m.ChangeBannerImage(ctx, ctx.System, image)); + // if no attachment show the banner like intended + return ctx.Execute(SystemBannerImage, m => m.ShowBannerImage(ctx, ctx.System, flags.GetReplyFormat())); + }))(), + Commands.SystemShowBanner(var param, var flags) => ctx.Execute(SystemBannerImage, m => m.ShowBannerImage(ctx, param.target, flags.GetReplyFormat())), + Commands.SystemClearBanner(var param, var flags) => ctx.Execute(SystemBannerImage, m => m.ClearBannerImage(ctx, ctx.System, flags.yes)), + Commands.SystemChangeBanner(var param, _) => ctx.Execute(SystemBannerImage, m => m.ChangeBannerImage(ctx, ctx.System, param.banner)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -330,9 +343,7 @@ public partial class CommandTree private async Task HandleSystemCommandTargeted(Context ctx, PKSystem target) { - if (ctx.Match("banner", "splash", "cover")) - await ctx.CheckSystem(target).Execute(SystemBannerImage, m => m.BannerImage(ctx, target)); - else if (ctx.Match("list", "l", "members", "ls")) + if (ctx.Match("list", "l", "members", "ls")) await ctx.CheckSystem(target).Execute(SystemList, m => m.MemberList(ctx, target)); else if (ctx.Match("find", "search", "query", "fd", "s")) await ctx.CheckSystem(target).Execute(SystemFind, m => m.MemberList(ctx, target)); diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index ab4d3c1c..66a51a39 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -701,72 +701,77 @@ public class SystemEdit : ctx.Reply(msg)); } - public async Task BannerImage(Context ctx, PKSystem target) + public async Task ClearBannerImage(Context ctx, PKSystem target, bool flagConfirmYes) { ctx.CheckSystemPrivacy(target.Id, target.BannerPrivacy); - - var isOwnSystem = target.Id == ctx.System?.Id; - - if ((!ctx.HasNext() && ctx.Message.Attachments.Length == 0) || ctx.PeekMatchFormat() != ReplyFormat.Standard) - { - if ((target.BannerImage?.Trim() ?? "").Length > 0) - switch (ctx.MatchFormat()) - { - case ReplyFormat.Raw: - await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`"); - break; - case ReplyFormat.Plaintext: - var ebP = new EmbedBuilder() - .Description($"Showing banner for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); - await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); - break; - default: - var ebS = new EmbedBuilder() - .Title("System banner image") - .Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl())); - if (target.Id == ctx.System?.Id) - ebS.Description($"To clear, use `{ctx.DefaultPrefix}system banner clear`."); - await ctx.Reply(embed: ebS.Build()); - break; - } - else - throw new PKSyntaxError("This system does not have a banner image set." - + (isOwnSystem ? "Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); - - return; - } - ctx.CheckSystem().CheckOwnSystem(target); - if (ctx.MatchClear() && await ctx.ConfirmClear("your system's banner image")) + if (await ctx.ConfirmClear("your system's banner image", flagConfirmYes)) { await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { BannerImage = null }); await ctx.Reply($"{Emojis.Success} System banner image cleared."); } + } - else if (await ctx.MatchImage() is { } img) + public async Task ShowBannerImage(Context ctx, PKSystem target, ReplyFormat format) + { + ctx.CheckSystemPrivacy(target.Id, target.BannerPrivacy); + var isOwnSystem = target.Id == ctx.System?.Id; + + if ((target.BannerImage?.Trim() ?? "").Length > 0) { - img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System); - await _avatarHosting.VerifyAvatarOrThrow(img.Url, true); - - await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { BannerImage = img.CleanUrl ?? img.Url }); - - var msg = img.Source switch + switch (format) { - AvatarSource.Url => $"{Emojis.Success} System banner image changed to the image at the given URL.", - AvatarSource.HostedCdn => $"{Emojis.Success} System banner image changed to attached image.", - AvatarSource.Attachment => - $"{Emojis.Success} System banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.", - AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."), - _ => throw new ArgumentOutOfRangeException() - }; - - // The attachment's already right there, no need to preview it. - var hasEmbed = img.Source != AvatarSource.Attachment && img.Source != AvatarSource.HostedCdn; - await (hasEmbed - ? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build()) - : ctx.Reply(msg)); + case ReplyFormat.Raw: + await ctx.Reply($"`{target.BannerImage.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing banner for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); + await ctx.Reply(text: $"<{target.BannerImage.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("System banner image") + .Image(new Embed.EmbedImage(target.BannerImage.TryGetCleanCdnUrl())); + if (target.Id == ctx.System?.Id) + ebS.Description($"To clear, use `{ctx.DefaultPrefix}system banner clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } } + else + { + throw new PKSyntaxError("This system does not have a banner image set." + + (isOwnSystem ? " Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); + } + } + + public async Task ChangeBannerImage(Context ctx, PKSystem target, ParsedImage img) + { + ctx.CheckSystemPrivacy(target.Id, target.BannerPrivacy); + ctx.CheckSystem().CheckOwnSystem(target); + + img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Banner, ctx.Author.Id, ctx.System); + await _avatarHosting.VerifyAvatarOrThrow(img.Url, true); + + await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { BannerImage = img.CleanUrl ?? img.Url }); + + var msg = img.Source switch + { + AvatarSource.Url => $"{Emojis.Success} System banner image changed to the image at the given URL.", + AvatarSource.HostedCdn => $"{Emojis.Success} System banner image changed to attached image.", + AvatarSource.Attachment => + $"{Emojis.Success} System banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working.", + AvatarSource.User => throw new PKError("Cannot set a banner image to an user's avatar."), + _ => throw new ArgumentOutOfRangeException() + }; + + // The attachment's already right there, no need to preview it. + var hasEmbed = img.Source != AvatarSource.Attachment && img.Source != AvatarSource.HostedCdn; + await (hasEmbed + ? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build()) + : ctx.Reply(msg)); } public async Task Delete(Context ctx, PKSystem target) diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index b5e58789..e7befa9a 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -175,6 +175,23 @@ pub fn edit() -> impl Iterator { ] .into_iter(); + let system_banner = tokens!(system_target, ("banner", ["cover"])); + let system_banner_cmd = + [command!(system_banner => "system_show_banner").help("Shows the system's banner")] + .into_iter(); + + let system_banner_self = tokens!(system, ("banner", ["cover"])); + let system_banner_self_cmd = [ + command!(system_banner_self => "system_show_banner_self") + .help("Shows your system's banner"), + command!(system_banner_self, ("clear", ["c"]) => "system_clear_banner") + .flag(("yes", ["y"])) + .help("Clears your system's banner"), + command!(system_banner_self, ("banner", Avatar) => "system_change_banner") + .help("Changes your system's banner"), + ] + .into_iter(); + system_new_cmd .chain(system_name_self_cmd) .chain(system_server_name_self_cmd) @@ -185,6 +202,7 @@ pub fn edit() -> impl Iterator { .chain(system_pronouns_self_cmd) .chain(system_avatar_self_cmd) .chain(system_server_avatar_self_cmd) + .chain(system_banner_self_cmd) .chain(system_name_cmd) .chain(system_server_name_cmd) .chain(system_description_cmd) @@ -194,5 +212,6 @@ pub fn edit() -> impl Iterator { .chain(system_pronouns_cmd) .chain(system_avatar_cmd) .chain(system_server_avatar_cmd) + .chain(system_banner_cmd) .chain(system_info_cmd) }