diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index ab46b58e..4a4bb498 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -69,6 +69,19 @@ public partial class CommandTree Commands.SystemShowAvatar(var param, var flags) => ctx.Execute(SystemAvatar, m => m.ShowAvatar(ctx, param.target, flags.GetReplyFormat())), Commands.SystemClearAvatar(var param, var flags) => ctx.Execute(SystemAvatar, m => m.ClearAvatar(ctx, ctx.System, flags.yes)), Commands.SystemChangeAvatar(var param, _) => ctx.Execute(SystemAvatar, m => m.ChangeAvatar(ctx, ctx.System, param.avatar)), + Commands.SystemShowServerAvatarSelf(_, var flags) => ((Func)(() => + { + // we want to change avatar 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(SystemServerAvatar, m => m.ChangeServerAvatar(ctx, ctx.System, image)); + // if no attachment show the avatar like intended + return ctx.Execute(SystemServerAvatar, m => m.ShowServerAvatar(ctx, ctx.System, flags.GetReplyFormat())); + }))(), + 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)), _ => // this should only ever occur when deving if commands are not implemented... ctx.Reply( @@ -319,9 +332,6 @@ public partial class CommandTree { if (ctx.Match("banner", "splash", "cover")) await ctx.CheckSystem(target).Execute(SystemBannerImage, m => m.BannerImage(ctx, target)); - else if (ctx.Match("serveravatar", "sa", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic", - "guildavatar", "guildpic", "guildicon", "sicon", "spfp")) - await ctx.CheckSystem(target).Execute(SystemServerAvatar, m => m.ServerAvatar(ctx, target)); else 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")) diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 582d0df3..ab4d3c1c 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -620,96 +620,85 @@ public class SystemEdit : ctx.Reply(msg)); } - public async Task ServerAvatar(Context ctx, PKSystem target) + public async Task ClearServerAvatar(Context ctx, PKSystem target, bool flagConfirmYes) { - - async Task ClearIcon() - { - ctx.CheckOwnSystem(target); - - await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = null }); - await ctx.Reply($"{Emojis.Success} System server avatar cleared."); - } - - async Task SetIcon(ParsedImage img) - { - ctx.CheckOwnSystem(target); - - img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System); - await _avatarHosting.VerifyAvatarOrThrow(img.Url); - - await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = img.CleanUrl ?? img.Url }); - - var msg = img.Source switch - { - AvatarSource.User => - $"{Emojis.Success} System icon for this server changed to {img.SourceUser?.Username}'s avatar! It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon for this server will need to be re-set.", - AvatarSource.Url => - $"{Emojis.Success} System icon for this server changed to the image at the given URL. It will now be used for anything that uses system avatar in this server.", - AvatarSource.HostedCdn => $"{Emojis.Success} System icon for this server changed to attached image.", - AvatarSource.Attachment => - $"{Emojis.Success} System icon for this server changed to attached image. It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon for this server will stop working.", - _ => 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)); - } - - async Task ShowIcon() - { - var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id); - - if ((settings.AvatarUrl?.Trim() ?? "").Length > 0) - { - if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id))) - throw new PKSyntaxError("This system does not have a icon specific to this server or it is private."); - switch (ctx.MatchFormat()) - { - case ReplyFormat.Raw: - await ctx.Reply($"`{settings.AvatarUrl.TryGetCleanCdnUrl()}`"); - break; - case ReplyFormat.Plaintext: - var ebP = new EmbedBuilder() - .Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); - await ctx.Reply(text: $"<{settings.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); - break; - default: - var ebS = new EmbedBuilder() - .Title("System server icon") - .Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl())); - if (target.Id == ctx.System?.Id) - ebS.Description($"To clear, use `{ctx.DefaultPrefix}system servericon clear`."); - await ctx.Reply(embed: ebS.Build()); - break; - } - } - else - { - var isOwner = target.Id == ctx.System?.Id; - throw new PKSyntaxError( - $"This system does not have a icon specific to this server{(isOwner ? "" : " or it is private")}." - + (isOwner ? " Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); - } - } - ctx.CheckGuildContext(); + ctx.CheckSystem().CheckOwnSystem(target); - if (target != null && target?.Id != ctx.System?.Id) + if (await ctx.ConfirmClear("your system's icon for this server", flagConfirmYes)) { - await ShowIcon(); - return; + await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = null }); + await ctx.Reply($"{Emojis.Success} System server icon cleared."); } + } - if (ctx.MatchClear() && await ctx.ConfirmClear("your system's icon for this server")) - await ClearIcon(); - else if (await ctx.MatchImage() is { } img) - await SetIcon(img); + public async Task ShowServerAvatar(Context ctx, PKSystem target, ReplyFormat format) + { + ctx.CheckGuildContext(); + var isOwnSystem = target.Id == ctx.System?.Id; + + var settings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, target.Id); + + if ((settings.AvatarUrl?.Trim() ?? "").Length > 0) + { + if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id))) + throw new PKSyntaxError("This system does not have a icon specific to this server or it is private."); + + switch (format) + { + case ReplyFormat.Raw: + await ctx.Reply($"`{settings.AvatarUrl.TryGetCleanCdnUrl()}`"); + break; + case ReplyFormat.Plaintext: + var ebP = new EmbedBuilder() + .Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)"); + await ctx.Reply(text: $"<{settings.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build()); + break; + default: + var ebS = new EmbedBuilder() + .Title("System server icon") + .Image(new Embed.EmbedImage(settings.AvatarUrl.TryGetCleanCdnUrl())); + if (target.Id == ctx.System?.Id) + ebS.Description($"To clear, use `{ctx.DefaultPrefix}system servericon clear`."); + await ctx.Reply(embed: ebS.Build()); + break; + } + } else - await ShowIcon(); + { + throw new PKSyntaxError( + $"This system does not have a icon specific to this server{(isOwnSystem ? "" : " or it is private")}." + + (isOwnSystem ? " Set one by attaching an image to this command, or by passing an image URL or @mention." : "")); + } + } + + public async Task ChangeServerAvatar(Context ctx, PKSystem target, ParsedImage img) + { + ctx.CheckGuildContext(); + ctx.CheckSystem().CheckOwnSystem(target); + + img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System); + await _avatarHosting.VerifyAvatarOrThrow(img.Url); + + await ctx.Repository.UpdateSystemGuild(target.Id, ctx.Guild.Id, new SystemGuildPatch { AvatarUrl = img.CleanUrl ?? img.Url }); + + var msg = img.Source switch + { + AvatarSource.User => + $"{Emojis.Success} System icon for this server changed to {img.SourceUser?.Username}'s avatar! It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon for this server will need to be re-set.", + AvatarSource.Url => + $"{Emojis.Success} System icon for this server changed to the image at the given URL. It will now be used for anything that uses system avatar in this server.", + AvatarSource.HostedCdn => $"{Emojis.Success} System icon for this server changed to attached image.", + AvatarSource.Attachment => + $"{Emojis.Success} System icon for this server changed to attached image. It will now be used for anything that uses system avatar in this server.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon for this server will stop working.", + _ => 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 BannerImage(Context ctx, PKSystem target) diff --git a/crates/command_definitions/src/system.rs b/crates/command_definitions/src/system.rs index 0980ca87..b5e58789 100644 --- a/crates/command_definitions/src/system.rs +++ b/crates/command_definitions/src/system.rs @@ -158,6 +158,23 @@ pub fn edit() -> impl Iterator { ] .into_iter(); + let system_server_avatar = tokens!(system_target, ("serveravatar", ["spfp"])); + let system_server_avatar_cmd = [command!(system_server_avatar => "system_show_server_avatar") + .help("Shows the system's server avatar")] + .into_iter(); + + let system_server_avatar_self = tokens!(system, ("serveravatar", ["spfp"])); + let system_server_avatar_self_cmd = [ + command!(system_server_avatar_self => "system_show_server_avatar_self") + .help("Shows your system's server avatar"), + command!(system_server_avatar_self, ("clear", ["c"]) => "system_clear_server_avatar") + .flag(("yes", ["y"])) + .help("Clears your system's server avatar"), + command!(system_server_avatar_self, ("avatar", Avatar) => "system_change_server_avatar") + .help("Changes your system's server avatar"), + ] + .into_iter(); + system_new_cmd .chain(system_name_self_cmd) .chain(system_server_name_self_cmd) @@ -167,6 +184,7 @@ pub fn edit() -> impl Iterator { .chain(system_server_tag_self_cmd) .chain(system_pronouns_self_cmd) .chain(system_avatar_self_cmd) + .chain(system_server_avatar_self_cmd) .chain(system_name_cmd) .chain(system_server_name_cmd) .chain(system_description_cmd) @@ -175,5 +193,6 @@ pub fn edit() -> impl Iterator { .chain(system_server_tag_cmd) .chain(system_pronouns_cmd) .chain(system_avatar_cmd) + .chain(system_server_avatar_cmd) .chain(system_info_cmd) }