Skip to content

Commit

Permalink
define context commands
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacherr committed Sep 16, 2024
1 parent 0d0a787 commit fba7632
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 16 deletions.
36 changes: 36 additions & 0 deletions assyst-core/src/command/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,16 @@ impl ParseArgument for Rest {
ctxt: &mut InteractionCommandParseCtxt<'_>,
label: Label,
) -> Result<Self, TagParseError> {
// look for resolved messages: if so, it's a context menu command, and use that message
if let Some(ref ms) = ctxt.cx.data.resolved_messages
&& let Some(m) = ms.first()
{
if m.content.is_empty() {
return Err(TagParseError::ArgsExhausted(ArgsExhausted(label)));
}
return Ok(Rest(m.content.clone()));
}

// treat Rest as same as Word because there is no option type which is just one
// whitespace-delimited word
let word = &ctxt.option_by_name(&label.unwrap().0)?.value;
Expand Down Expand Up @@ -568,6 +578,16 @@ impl ParseArgument for RestNoFlags {
ctxt: &mut InteractionCommandParseCtxt<'_>,
label: Label,
) -> Result<Self, TagParseError> {
// look for resolved messages: if so, it's a context menu command, and use that message
if let Some(ref ms) = ctxt.cx.data.resolved_messages
&& let Some(m) = ms.first()
{
if m.content.is_empty() {
return Err(TagParseError::ArgsExhausted(ArgsExhausted(label)));
}
return Ok(RestNoFlags(m.content.clone()));
}

// treat Rest as same as Word because there is no option type which is just one
// whitespace-delimited word
let word = &ctxt.option_by_name(&label.unwrap().0)?.value;
Expand Down Expand Up @@ -947,6 +967,22 @@ impl ParseArgument for ImageUrl {
};
}

// if this is Some, this is a context menu command
// we must have our image defined here, instead of looking anywhere else
if let Some(ref r) = ctxt.cx.data.resolved_messages {
if let Some(m) = r.first()
&& let Some(i) = m.attachments.first()
{
return ImageUrl::attachment(Some(i));
} else if let Some(m) = r.first()
&& let Some(e) = m.embeds.first()
{
return ImageUrl::embed(Some(e));
} else {
return Err(TagParseError::ArgsExhausted(ArgsExhausted(label)));
}
}

let attachment_label = Some((
format!("{}-attachment", label.clone().unwrap().0),
label.clone().unwrap().1,
Expand Down
10 changes: 6 additions & 4 deletions assyst-core/src/command/fun/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ pub mod translation;
category = Category::Fun,
usage = "[video]",
examples = ["https://link.to.my/video.mp4"],
send_processing = true
send_processing = true,
context_menu_command = "Find Song"
)]
pub async fn findsong(ctxt: CommandCtxt<'_>, input: ImageUrl) -> anyhow::Result<()> {
let result = identify_song_notsoidentify(&ctxt.assyst().reqwest_client, input.0)
pub async fn findsong(ctxt: CommandCtxt<'_>, audio: ImageUrl) -> anyhow::Result<()> {
let result = identify_song_notsoidentify(&ctxt.assyst().reqwest_client, audio.0)
.await
.context("Failed to identify song")?;

Expand Down Expand Up @@ -56,7 +57,8 @@ pub async fn findsong(ctxt: CommandCtxt<'_>, input: ImageUrl) -> anyhow::Result<
category = Category::Fun,
usage = "[image]",
examples = ["https://link.to.my/image.png"],
send_processing = true
send_processing = true,
context_menu_command = "Identify Image"
)]
pub async fn identify(ctxt: CommandCtxt<'_>, input: ImageUrl) -> anyhow::Result<()> {
let result = identify_image(&ctxt.assyst().reqwest_client, &input.0)
Expand Down
3 changes: 2 additions & 1 deletion assyst-core/src/command/fun/translation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ impl ParseArgument for BadTranslateFlags {
("chain", "Show language chain"),
("count", "Set the amount of translations to perform")
],
send_processing = true
send_processing = true,
context_menu_command = "Bad Translate"
)]
pub async fn bad_translate(ctxt: CommandCtxt<'_>, text: Rest, flags: BadTranslateFlags) -> anyhow::Result<()> {
if text.0 == "languages" {
Expand Down
2 changes: 1 addition & 1 deletion assyst-core/src/command/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! defaults {
(send_processing) => { false };
(guild_only $x:expr) => { $x };
(guild_only) => { false };
(context_menu_command) => { false };
(context_menu_command) => { "" };
}

#[allow(clippy::crate_in_macro_def)]
Expand Down
12 changes: 9 additions & 3 deletions assyst-core/src/command/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ pub async fn grayscale(ctxt: CommandCtxt<'_>, source: Image) -> anyhow::Result<(
category = Category::Image,
usage = "[image]",
examples = ["https://link.to.my/image.png"],
send_processing = true
send_processing = true,
context_menu_command = "Image Information"
)]
pub async fn imageinfo(ctxt: CommandCtxt<'_>, source: Image) -> anyhow::Result<()> {
let result = ctxt.flux_handler().image_info(source.0).await?;
Expand Down Expand Up @@ -793,9 +794,14 @@ pub async fn scramble(ctxt: CommandCtxt<'_>, source: Image) -> anyhow::Result<()
pub async fn setloop(ctxt: CommandCtxt<'_>, source: Image, loops: i64) -> anyhow::Result<()> {
let result = ctxt
.flux_handler()
.set_loop(source.0, ctxt.data.author.id.get(), ctxt.data.guild_id.map(|x| x.get()), loops)
.set_loop(
source.0,
ctxt.data.author.id.get(),
ctxt.data.guild_id.map(|x| x.get()),
loops,
)
.await?;

ctxt.reply(result).await?;

Ok(())
Expand Down
4 changes: 3 additions & 1 deletion assyst-core/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub struct CommandMetadata {
pub send_processing: bool,
pub age_restricted: bool,
pub flag_descriptions: HashMap<&'static str, &'static str>,
pub context_menu_command: bool,
pub context_menu_command: &'static str,
pub guild_only: bool,
}

Expand Down Expand Up @@ -279,6 +279,8 @@ pub struct CommandData<'a> {
pub interaction_id: Option<Id<InteractionMarker>>,
pub interaction_attachments: HashMap<Id<AttachmentMarker>, Attachment>,
pub command_from_install_context: bool,
/// None if not a context menu command.
pub resolved_messages: Option<Vec<Message>>,
}

pub type RawMessageArgsIter<'a> = SplitAsciiWhitespace<'a>;
Expand Down
16 changes: 14 additions & 2 deletions assyst-core/src/command/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,16 @@ pub fn get_or_init_commands() -> &'static HashMap<&'static str, TCommand> {

/// Finds a command by its name.
pub fn find_command_by_name(name: &str) -> Option<TCommand> {
get_or_init_commands().get(name.to_lowercase().as_str()).copied()
get_or_init_commands()
.get(name.to_lowercase().as_str())
.copied()
// support context menu command names
.or_else(|| {
get_or_init_commands()
.iter()
.find(|x| x.1.metadata().context_menu_command == name)
.map(|c| *c.1)
})
}

pub async fn register_interaction_commands(assyst: ThreadSafeAssyst) -> anyhow::Result<Vec<InteractionCommand>> {
Expand All @@ -149,9 +158,12 @@ pub async fn register_interaction_commands(assyst: ThreadSafeAssyst) -> anyhow::
let mut deduplicated_commands: Vec<InteractionCommand> = vec![];
for command in interaction_commands {
if !deduplicated_commands.iter().any(|x| x.name == command.0.name) {
if command.1 {
if !command.1.is_empty() {
let mut copy = command.0.clone();
copy.name = command.1.to_owned();
copy.kind = CommandType::Message;
copy.options = vec![];
copy.description = String::new();
deduplicated_commands.push(copy);
}
deduplicated_commands.push(command.0);
Expand Down
3 changes: 2 additions & 1 deletion assyst-core/src/command/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ pub mod download;
category = Category::Services,
usage = "[text]",
examples = ["yep im burning"],
send_processing = true
send_processing = true,
context_menu_command = "Burn Text"
)]
pub async fn burntext(ctxt: CommandCtxt<'_>, text: Rest) -> anyhow::Result<()> {
let result = burn_text(&text.0).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use assyst_common::config::CONFIG;
use assyst_common::err;
use assyst_database::model::active_guild_premium_entitlement::ActiveGuildPremiumEntitlement;
use tracing::{debug, warn};
use twilight_model::application::command::CommandType;
use twilight_model::application::interaction::application_command::{
CommandData as DiscordCommandData, CommandDataOption, CommandOptionValue,
};
use twilight_model::application::interaction::{InteractionContextType, InteractionData, InteractionType};
use twilight_model::channel::Message;
use twilight_model::gateway::payload::incoming::InteractionCreate;
use twilight_model::http::interaction::{InteractionResponse, InteractionResponseType};
use twilight_model::util::Timestamp;
Expand Down Expand Up @@ -115,7 +117,8 @@ pub async fn handle(assyst: ThreadSafeAssyst, InteractionCreate(interaction): In
let incoming_match = incoming_options.iter().find(|x| x.name == option.name);
if let Some(op) = incoming_match {
sorted_incoming_options.push(op.clone());
} else {
// context menu commands have no options and are handled independently
} else if command_data.kind != CommandType::Message {
// default required: false
if option.required.unwrap_or(false) {
err!(
Expand All @@ -137,6 +140,20 @@ pub async fn handle(assyst: ThreadSafeAssyst, InteractionCreate(interaction): In
None
};

let interaction_attachments = command_data
.resolved
.clone()
.map(|x| x.attachments)
.unwrap_or(HashMap::new());

// resolve message attachments for context menu commands
let mut resolved_messages: Option<Vec<Message>> = None;
if let Some(ref ms) = command_data.resolved.map(|x| x.messages)
&& command_data.kind == CommandType::Message
{
resolved_messages = Some(ms.values().cloned().collect());
}

let data = CommandData {
source: Source::Interaction,
assyst: &assyst,
Expand All @@ -155,11 +172,12 @@ pub async fn handle(assyst: ThreadSafeAssyst, InteractionCreate(interaction): In
author: interaction.member.and_then(|x| x.user).or(interaction.user).unwrap(),
interaction_token: Some(interaction.token),
interaction_id: Some(interaction.id),
interaction_attachments: command_data.resolved.map(|x| x.attachments).unwrap_or(HashMap::new()),
interaction_attachments,
command_from_install_context: match interaction.context {
Some(c) => c == InteractionContextType::PrivateChannel,
None => false,
},
resolved_messages,
};

let ctxt = InteractionCommandParseCtxt::new(CommandCtxt::new(&data), &sorted_incoming_options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub async fn handle(assyst: ThreadSafeAssyst, MessageCreate(message): MessageCre
interaction_id: None,
interaction_attachments: HashMap::new(),
command_from_install_context: false,
resolved_messages: None,
};
let ctxt = RawMessageParseCtxt::new(CommandCtxt::new(&data), result.args);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub async fn handle(assyst: ThreadSafeAssyst, event: MessageUpdate) {
interaction_id: None,
interaction_attachments: HashMap::new(),
command_from_install_context: false,
resolved_messages: None,
};
let ctxt = RawMessageParseCtxt::new(CommandCtxt::new(&data), result.args);

Expand Down
2 changes: 1 addition & 1 deletion assyst-proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub fn command(attrs: TokenStream, func: TokenStream) -> TokenStream {
});
let send_processing = fields.remove("send_processing").unwrap_or_else(false_expr);
let age_restricted = fields.remove("age_restricted").unwrap_or_else(false_expr);
let context_menu_command = fields.remove("context_menu_command").unwrap_or_else(false_expr);
let context_menu_command = fields.remove("context_menu_command").unwrap_or_else(|| str_expr(""));
let flag_descriptions = fields.remove("flag_descriptions").unwrap_or_else(empty_array_expr);
let guild_only = fields.remove("guild_only").unwrap_or_else(false_expr);

Expand Down

0 comments on commit fba7632

Please sign in to comment.