mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat: implement system avatar commands
This commit is contained in:
parent
293570c91c
commit
b62340cbb3
11 changed files with 155 additions and 101 deletions
|
|
@ -56,6 +56,19 @@ public partial class CommandTree
|
||||||
Commands.SystemShowPronouns(var param, var flags) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ShowPronouns(ctx, param.target, flags.GetReplyFormat())),
|
Commands.SystemShowPronouns(var param, var flags) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ShowPronouns(ctx, param.target, flags.GetReplyFormat())),
|
||||||
Commands.SystemClearPronouns(var param, var flags) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ClearPronouns(ctx, ctx.System, flags.yes)),
|
Commands.SystemClearPronouns(var param, var flags) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ClearPronouns(ctx, ctx.System, flags.yes)),
|
||||||
Commands.SystemChangePronouns(var param, _) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ChangePronouns(ctx, ctx.System, param.pronouns)),
|
Commands.SystemChangePronouns(var param, _) => ctx.Execute<SystemEdit>(SystemPronouns, m => m.ChangePronouns(ctx, ctx.System, param.pronouns)),
|
||||||
|
Commands.SystemShowAvatarSelf(_, var flags) => ((Func<Task>)(() =>
|
||||||
|
{
|
||||||
|
// 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<SystemEdit>(SystemAvatar, m => m.ChangeAvatar(ctx, ctx.System, image));
|
||||||
|
// if no attachment show the avatar like intended
|
||||||
|
return ctx.Execute<SystemEdit>(SystemAvatar, m => m.ShowAvatar(ctx, ctx.System, flags.GetReplyFormat()));
|
||||||
|
}))(),
|
||||||
|
Commands.SystemShowAvatar(var param, var flags) => ctx.Execute<SystemEdit>(SystemAvatar, m => m.ShowAvatar(ctx, param.target, flags.GetReplyFormat())),
|
||||||
|
Commands.SystemClearAvatar(var param, var flags) => ctx.Execute<SystemEdit>(SystemAvatar, m => m.ClearAvatar(ctx, ctx.System, flags.yes)),
|
||||||
|
Commands.SystemChangeAvatar(var param, _) => ctx.Execute<SystemEdit>(SystemAvatar, m => m.ChangeAvatar(ctx, ctx.System, param.avatar)),
|
||||||
_ =>
|
_ =>
|
||||||
// this should only ever occur when deving if commands are not implemented...
|
// this should only ever occur when deving if commands are not implemented...
|
||||||
ctx.Reply(
|
ctx.Reply(
|
||||||
|
|
@ -306,8 +319,6 @@ public partial class CommandTree
|
||||||
{
|
{
|
||||||
if (ctx.Match("banner", "splash", "cover"))
|
if (ctx.Match("banner", "splash", "cover"))
|
||||||
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemBannerImage, m => m.BannerImage(ctx, target));
|
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemBannerImage, m => m.BannerImage(ctx, target));
|
||||||
else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp"))
|
|
||||||
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemAvatar, m => m.Avatar(ctx, target));
|
|
||||||
else if (ctx.Match("serveravatar", "sa", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic",
|
else if (ctx.Match("serveravatar", "sa", "servericon", "serverimage", "serverpfp", "serverpic", "savatar", "spic",
|
||||||
"guildavatar", "guildpic", "guildicon", "sicon", "spfp"))
|
"guildavatar", "guildpic", "guildicon", "sicon", "spfp"))
|
||||||
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemServerAvatar, m => m.ServerAvatar(ctx, target));
|
await ctx.CheckSystem(target).Execute<SystemEdit>(SystemServerAvatar, m => m.ServerAvatar(ctx, target));
|
||||||
|
|
|
||||||
|
|
@ -6,34 +6,8 @@ namespace PluralKit.Bot;
|
||||||
|
|
||||||
public static class ContextAvatarExt
|
public static class ContextAvatarExt
|
||||||
{
|
{
|
||||||
public static async Task<ParsedImage?> MatchImage(this Context ctx)
|
public static ParsedImage? ExtractImageFromAttachment(this Context ctx)
|
||||||
{
|
{
|
||||||
// If we have a user @mention/ID, use their avatar
|
|
||||||
if (await ctx.MatchUser() is { } user)
|
|
||||||
{
|
|
||||||
var url = user.AvatarUrl("png", 256);
|
|
||||||
return new ParsedImage { Url = url, Source = AvatarSource.User, SourceUser = user };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have raw or plaintext, don't try to parse as a URL
|
|
||||||
if (ctx.PeekMatchFormat() != ReplyFormat.Standard)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// If we have a positional argument, try to parse it as a URL
|
|
||||||
var arg = ctx.RemainderOrNull();
|
|
||||||
if (arg != null)
|
|
||||||
{
|
|
||||||
// Allow surrounding the URL with <angle brackets> to "de-embed"
|
|
||||||
if (arg.StartsWith("<") && arg.EndsWith(">"))
|
|
||||||
arg = arg.Substring(1, arg.Length - 2);
|
|
||||||
|
|
||||||
if (!Core.MiscUtils.TryMatchUri(arg, out var uri))
|
|
||||||
throw Errors.InvalidUrl;
|
|
||||||
|
|
||||||
// ToString URL-decodes, which breaks URLs to spaces; AbsoluteUri doesn't
|
|
||||||
return new ParsedImage { Url = uri.AbsoluteUri, Source = AvatarSource.Url };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an attachment, use that
|
// If we have an attachment, use that
|
||||||
if (ctx.Message.Attachments.FirstOrDefault() is { } attachment)
|
if (ctx.Message.Attachments.FirstOrDefault() is { } attachment)
|
||||||
{
|
{
|
||||||
|
|
@ -51,6 +25,33 @@ public static class ContextAvatarExt
|
||||||
// and if there are no attachments (which would have been caught just before)
|
// and if there are no attachments (which would have been caught just before)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
public static async Task<ParsedImage?> GetUserPfp(this Context ctx, string arg)
|
||||||
|
{
|
||||||
|
// If we have a user @mention/ID, use their avatar
|
||||||
|
if (await ctx.ParseUser(arg) is { } user)
|
||||||
|
{
|
||||||
|
var url = user.AvatarUrl("png", 256);
|
||||||
|
return new ParsedImage { Url = url, Source = AvatarSource.User, SourceUser = user };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public static ParsedImage ParseImage(this Context ctx, string arg)
|
||||||
|
{
|
||||||
|
// Allow surrounding the URL with <angle brackets> to "de-embed"
|
||||||
|
if (arg.StartsWith("<") && arg.EndsWith(">"))
|
||||||
|
arg = arg.Substring(1, arg.Length - 2);
|
||||||
|
|
||||||
|
if (!Core.MiscUtils.TryMatchUri(arg, out var uri))
|
||||||
|
throw Errors.InvalidUrl;
|
||||||
|
|
||||||
|
// ToString URL-decodes, which breaks URLs to spaces; AbsoluteUri doesn't
|
||||||
|
return new ParsedImage { Url = uri.AbsoluteUri, Source = AvatarSource.Url };
|
||||||
|
}
|
||||||
|
public static async Task<ParsedImage?> MatchImage(this Context ctx)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ParsedImage
|
public struct ParsedImage
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ namespace PluralKit.Bot;
|
||||||
|
|
||||||
public static class ContextEntityArgumentsExt
|
public static class ContextEntityArgumentsExt
|
||||||
{
|
{
|
||||||
|
public static async Task<User> ParseUser(this Context ctx, string arg)
|
||||||
|
{
|
||||||
|
if (arg.TryParseMention(out var id))
|
||||||
|
return await ctx.Cache.GetOrFetchUser(ctx.Rest, id);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<User> MatchUser(this Context ctx)
|
public static async Task<User> MatchUser(this Context ctx)
|
||||||
{
|
{
|
||||||
var text = ctx.PeekArgument();
|
var text = ctx.PeekArgument();
|
||||||
|
|
|
||||||
|
|
@ -51,4 +51,12 @@ public static class ContextParametersExt
|
||||||
param => (param as Parameter.Toggle)?.value
|
param => (param as Parameter.Toggle)?.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<ParsedImage?> ParamResolveAvatar(this Context ctx, string param_name)
|
||||||
|
{
|
||||||
|
return await ctx.Parameters.ResolveParameter(
|
||||||
|
ctx, param_name,
|
||||||
|
param => (param as Parameter.Avatar)?.avatar
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Myriad.Types;
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
using uniffi.commands;
|
using uniffi.commands;
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ public abstract record Parameter()
|
||||||
public record PrivacyLevel(string level): Parameter;
|
public record PrivacyLevel(string level): Parameter;
|
||||||
public record Toggle(bool value): Parameter;
|
public record Toggle(bool value): Parameter;
|
||||||
public record Opaque(string value): Parameter;
|
public record Opaque(string value): Parameter;
|
||||||
|
public record Avatar(ParsedImage avatar): Parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Parameters
|
public class Parameters
|
||||||
|
|
@ -79,6 +81,8 @@ public class Parameters
|
||||||
return new Parameter.Toggle(toggle.toggle);
|
return new Parameter.Toggle(toggle.toggle);
|
||||||
case uniffi.commands.Parameter.OpaqueString opaque:
|
case uniffi.commands.Parameter.OpaqueString opaque:
|
||||||
return new Parameter.Opaque(opaque.raw);
|
return new Parameter.Opaque(opaque.raw);
|
||||||
|
case uniffi.commands.Parameter.Avatar avatar:
|
||||||
|
return new Parameter.Avatar(await ctx.GetUserPfp(avatar.avatar) ?? ctx.ParseImage(avatar.avatar));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -542,90 +542,82 @@ public class SystemEdit
|
||||||
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters." : ""));
|
+ $" Using {target.Pronouns.Length}/{Limits.MaxPronounsLength} characters." : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Avatar(Context ctx, PKSystem target)
|
public async Task ClearAvatar(Context ctx, PKSystem target, bool flagConfirmYes)
|
||||||
{
|
{
|
||||||
async Task ClearIcon()
|
ctx.CheckSystemPrivacy(target.Id, target.AvatarPrivacy);
|
||||||
{
|
ctx.CheckSystem().CheckOwnSystem(target);
|
||||||
ctx.CheckOwnSystem(target);
|
|
||||||
|
|
||||||
|
if (await ctx.ConfirmClear("your system's icon", flagConfirmYes))
|
||||||
|
{
|
||||||
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { AvatarUrl = null });
|
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { AvatarUrl = null });
|
||||||
await ctx.Reply($"{Emojis.Success} System icon cleared.");
|
await ctx.Reply($"{Emojis.Success} System icon cleared.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task SetIcon(ParsedImage img)
|
public async Task ShowAvatar(Context ctx, PKSystem target, ReplyFormat format)
|
||||||
|
{
|
||||||
|
ctx.CheckSystemPrivacy(target.Id, target.AvatarPrivacy);
|
||||||
|
var isOwnSystem = target.Id == ctx.System?.Id;
|
||||||
|
|
||||||
|
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
|
||||||
{
|
{
|
||||||
ctx.CheckOwnSystem(target);
|
if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id)))
|
||||||
|
throw new PKSyntaxError("This system does not have an icon set or it is private.");
|
||||||
|
|
||||||
img = await _avatarHosting.TryRehostImage(img, AvatarHostingService.RehostedImageType.Avatar, ctx.Author.Id, ctx.System);
|
switch (format)
|
||||||
await _avatarHosting.VerifyAvatarOrThrow(img.Url);
|
|
||||||
|
|
||||||
await ctx.Repository.UpdateSystem(target.Id, new SystemPatch { AvatarUrl = img.CleanUrl ?? img.Url });
|
|
||||||
|
|
||||||
var msg = img.Source switch
|
|
||||||
{
|
{
|
||||||
AvatarSource.User =>
|
case ReplyFormat.Raw:
|
||||||
$"{Emojis.Success} System icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon will need to be re-set.",
|
await ctx.Reply($"`{target.AvatarUrl.TryGetCleanCdnUrl()}`");
|
||||||
AvatarSource.Url => $"{Emojis.Success} System icon changed to the image at the given URL.",
|
break;
|
||||||
AvatarSource.HostedCdn => $"{Emojis.Success} System icon changed to attached image.",
|
case ReplyFormat.Plaintext:
|
||||||
AvatarSource.Attachment =>
|
var ebP = new EmbedBuilder()
|
||||||
$"{Emojis.Success} System icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon will stop working.",
|
.Description($"Showing icon for system {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`)");
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
await ctx.Reply(text: $"<{target.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
||||||
};
|
break;
|
||||||
|
default:
|
||||||
// The attachment's already right there, no need to preview it.
|
var ebS = new EmbedBuilder()
|
||||||
var hasEmbed = img.Source != AvatarSource.Attachment && img.Source != AvatarSource.HostedCdn;
|
.Title("System icon")
|
||||||
await (hasEmbed
|
.Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl()));
|
||||||
? ctx.Reply(msg, new EmbedBuilder().Image(new Embed.EmbedImage(img.Url)).Build())
|
if (target.Id == ctx.System?.Id)
|
||||||
: ctx.Reply(msg));
|
ebS.Description($"To clear, use `{ctx.DefaultPrefix}system icon clear`.");
|
||||||
}
|
await ctx.Reply(embed: ebS.Build());
|
||||||
|
break;
|
||||||
async Task ShowIcon()
|
|
||||||
{
|
|
||||||
if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
|
|
||||||
{
|
|
||||||
if (!target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.Id)))
|
|
||||||
throw new PKSyntaxError("This system does not have an icon set or it is private.");
|
|
||||||
switch (ctx.MatchFormat())
|
|
||||||
{
|
|
||||||
case ReplyFormat.Raw:
|
|
||||||
await ctx.Reply($"`{target.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: $"<{target.AvatarUrl.TryGetCleanCdnUrl()}>", embed: ebP.Build());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
var ebS = new EmbedBuilder()
|
|
||||||
.Title("System icon")
|
|
||||||
.Image(new Embed.EmbedImage(target.AvatarUrl.TryGetCleanCdnUrl()));
|
|
||||||
if (target.Id == ctx.System?.Id)
|
|
||||||
ebS.Description($"To clear, use `{ctx.DefaultPrefix}system icon clear`.");
|
|
||||||
await ctx.Reply(embed: ebS.Build());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var isOwner = target.Id == ctx.System?.Id;
|
|
||||||
throw new PKSyntaxError(
|
|
||||||
$"This system does not have an icon set{(isOwner ? "" : " or it is private")}."
|
|
||||||
+ (isOwner ? " Set one by attaching an image to this command, or by passing an image URL or @mention." : ""));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null && target?.Id != ctx.System?.Id)
|
|
||||||
{
|
|
||||||
await ShowIcon();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.MatchClear() && await ctx.ConfirmClear("your system's icon"))
|
|
||||||
await ClearIcon();
|
|
||||||
else if (await ctx.MatchImage() is { } img)
|
|
||||||
await SetIcon(img);
|
|
||||||
else
|
else
|
||||||
await ShowIcon();
|
{
|
||||||
|
throw new PKSyntaxError(
|
||||||
|
$"This system does not have an icon set{(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 ChangeAvatar(Context ctx, PKSystem target, ParsedImage img)
|
||||||
|
{
|
||||||
|
ctx.CheckSystemPrivacy(target.Id, target.AvatarPrivacy);
|
||||||
|
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.UpdateSystem(target.Id, new SystemPatch { AvatarUrl = img.CleanUrl ?? img.Url });
|
||||||
|
|
||||||
|
var msg = img.Source switch
|
||||||
|
{
|
||||||
|
AvatarSource.User =>
|
||||||
|
$"{Emojis.Success} System icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon will need to be re-set.",
|
||||||
|
AvatarSource.Url => $"{Emojis.Success} System icon changed to the image at the given URL.",
|
||||||
|
AvatarSource.HostedCdn => $"{Emojis.Success} System icon changed to attached image.",
|
||||||
|
AvatarSource.Attachment =>
|
||||||
|
$"{Emojis.Success} System icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon 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 ServerAvatar(Context ctx, PKSystem target)
|
public async Task ServerAvatar(Context ctx, PKSystem target)
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,23 @@ pub fn edit() -> impl Iterator<Item = Command> {
|
||||||
]
|
]
|
||||||
.into_iter();
|
.into_iter();
|
||||||
|
|
||||||
|
let system_avatar = tokens!(system_target, ("avatar", ["pfp"]));
|
||||||
|
let system_avatar_cmd =
|
||||||
|
[command!(system_avatar => "system_show_avatar").help("Shows the system's avatar")]
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
|
let system_avatar_self = tokens!(system, ("avatar", ["pfp"]));
|
||||||
|
let system_avatar_self_cmd = [
|
||||||
|
command!(system_avatar_self => "system_show_avatar_self")
|
||||||
|
.help("Shows your system's avatar"),
|
||||||
|
command!(system_avatar_self, ("clear", ["c"]) => "system_clear_avatar")
|
||||||
|
.flag(("yes", ["y"]))
|
||||||
|
.help("Clears your system's avatar"),
|
||||||
|
command!(system_avatar_self, ("avatar", Avatar) => "system_change_avatar")
|
||||||
|
.help("Changes your system's avatar"),
|
||||||
|
]
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
system_new_cmd
|
system_new_cmd
|
||||||
.chain(system_name_self_cmd)
|
.chain(system_name_self_cmd)
|
||||||
.chain(system_server_name_self_cmd)
|
.chain(system_server_name_self_cmd)
|
||||||
|
|
@ -149,6 +166,7 @@ pub fn edit() -> impl Iterator<Item = Command> {
|
||||||
.chain(system_tag_self_cmd)
|
.chain(system_tag_self_cmd)
|
||||||
.chain(system_server_tag_self_cmd)
|
.chain(system_server_tag_self_cmd)
|
||||||
.chain(system_pronouns_self_cmd)
|
.chain(system_pronouns_self_cmd)
|
||||||
|
.chain(system_avatar_self_cmd)
|
||||||
.chain(system_name_cmd)
|
.chain(system_name_cmd)
|
||||||
.chain(system_server_name_cmd)
|
.chain(system_server_name_cmd)
|
||||||
.chain(system_description_cmd)
|
.chain(system_description_cmd)
|
||||||
|
|
@ -156,5 +174,6 @@ pub fn edit() -> impl Iterator<Item = Command> {
|
||||||
.chain(system_tag_cmd)
|
.chain(system_tag_cmd)
|
||||||
.chain(system_server_tag_cmd)
|
.chain(system_server_tag_cmd)
|
||||||
.chain(system_pronouns_cmd)
|
.chain(system_pronouns_cmd)
|
||||||
|
.chain(system_avatar_cmd)
|
||||||
.chain(system_info_cmd)
|
.chain(system_info_cmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ pub enum ParameterValue {
|
||||||
MemberPrivacyTarget(String),
|
MemberPrivacyTarget(String),
|
||||||
PrivacyLevel(String),
|
PrivacyLevel(String),
|
||||||
Toggle(bool),
|
Toggle(bool),
|
||||||
|
Avatar(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -44,6 +45,7 @@ impl Display for Parameter {
|
||||||
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||||
ParameterKind::PrivacyLevel => write!(f, "[privacy level]"),
|
ParameterKind::PrivacyLevel => write!(f, "[privacy level]"),
|
||||||
ParameterKind::Toggle => write!(f, "on/off"),
|
ParameterKind::Toggle => write!(f, "on/off"),
|
||||||
|
ParameterKind::Avatar => write!(f, "<url|@mention>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +77,7 @@ pub enum ParameterKind {
|
||||||
MemberPrivacyTarget,
|
MemberPrivacyTarget,
|
||||||
PrivacyLevel,
|
PrivacyLevel,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
Avatar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParameterKind {
|
impl ParameterKind {
|
||||||
|
|
@ -87,6 +90,7 @@ impl ParameterKind {
|
||||||
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||||
ParameterKind::PrivacyLevel => "privacy_level",
|
ParameterKind::PrivacyLevel => "privacy_level",
|
||||||
ParameterKind::Toggle => "toggle",
|
ParameterKind::Toggle => "toggle",
|
||||||
|
ParameterKind::Avatar => "avatar",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,6 +100,7 @@ impl ParameterKind {
|
||||||
|
|
||||||
pub(crate) fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
pub(crate) fn match_value(&self, input: &str) -> Result<ParameterValue, SmolStr> {
|
||||||
match self {
|
match self {
|
||||||
|
// TODO: actually parse image url
|
||||||
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
ParameterKind::OpaqueString | ParameterKind::OpaqueStringRemainder => {
|
||||||
Ok(ParameterValue::OpaqueString(input.into()))
|
Ok(ParameterValue::OpaqueString(input.into()))
|
||||||
}
|
}
|
||||||
|
|
@ -108,6 +113,7 @@ impl ParameterKind {
|
||||||
ParameterKind::Toggle => {
|
ParameterKind::Toggle => {
|
||||||
Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into()))
|
Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into()))
|
||||||
}
|
}
|
||||||
|
ParameterKind::Avatar => Ok(ParameterValue::Avatar(input.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ fn get_param_ty(kind: ParameterKind) -> &'static str {
|
||||||
ParameterKind::MemberPrivacyTarget => "MemberPrivacySubject",
|
ParameterKind::MemberPrivacyTarget => "MemberPrivacySubject",
|
||||||
ParameterKind::PrivacyLevel => "string",
|
ParameterKind::PrivacyLevel => "string",
|
||||||
ParameterKind::Toggle => "bool",
|
ParameterKind::Toggle => "bool",
|
||||||
|
ParameterKind::Avatar => "ParsedImage",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,6 +177,7 @@ fn get_param_param_ty(kind: ParameterKind) -> &'static str {
|
||||||
ParameterKind::MemberPrivacyTarget => "MemberPrivacyTarget",
|
ParameterKind::MemberPrivacyTarget => "MemberPrivacyTarget",
|
||||||
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
||||||
ParameterKind::Toggle => "Toggle",
|
ParameterKind::Toggle => "Toggle",
|
||||||
|
ParameterKind::Avatar => "Avatar",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ interface Parameter {
|
||||||
PrivacyLevel(string level);
|
PrivacyLevel(string level);
|
||||||
OpaqueString(string raw);
|
OpaqueString(string raw);
|
||||||
Toggle(boolean toggle);
|
Toggle(boolean toggle);
|
||||||
|
Avatar(string avatar);
|
||||||
};
|
};
|
||||||
dictionary ParsedCommand {
|
dictionary ParsedCommand {
|
||||||
string command_ref;
|
string command_ref;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ pub enum Parameter {
|
||||||
PrivacyLevel { level: String },
|
PrivacyLevel { level: String },
|
||||||
OpaqueString { raw: String },
|
OpaqueString { raw: String },
|
||||||
Toggle { toggle: bool },
|
Toggle { toggle: bool },
|
||||||
|
Avatar { avatar: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParameterValue> for Parameter {
|
impl From<ParameterValue> for Parameter {
|
||||||
|
|
@ -39,6 +40,7 @@ impl From<ParameterValue> for Parameter {
|
||||||
ParameterValue::PrivacyLevel(level) => Self::PrivacyLevel { level },
|
ParameterValue::PrivacyLevel(level) => Self::PrivacyLevel { level },
|
||||||
ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw },
|
ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw },
|
||||||
ParameterValue::Toggle(toggle) => Self::Toggle { toggle },
|
ParameterValue::Toggle(toggle) => Self::Toggle { toggle },
|
||||||
|
ParameterValue::Avatar(avatar) => Self::Avatar { avatar },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue