From 2cd3f2c7de07f12b8e3b9dcb77198cd1735d74f4 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Fri, 19 Jan 2024 00:38:31 +0000 Subject: [PATCH 1/8] Remove redundant ctx or user_data fields (#239) --- examples/basic_structure/main.rs | 2 +- examples/event_handler/main.rs | 11 ++++---- examples/feature_showcase/main.rs | 2 +- examples/manual_dispatch/main.rs | 4 +-- macros/src/command/prefix.rs | 2 +- macros/src/command/slash.rs | 2 +- src/dispatch/common.rs | 2 +- src/dispatch/mod.rs | 29 +++++++++++---------- src/dispatch/prefix.rs | 43 ++++++++++++------------------- src/dispatch/slash.rs | 12 +-------- src/framework/mod.rs | 5 +++- src/modal.rs | 2 +- src/reply/send_reply.rs | 10 +++---- src/structs/context.rs | 19 +++----------- src/structs/framework_error.rs | 23 +++++------------ src/structs/framework_options.rs | 7 ++--- src/structs/prefix.rs | 8 ------ src/structs/slash.rs | 9 +------ 18 files changed, 67 insertions(+), 125 deletions(-) diff --git a/examples/basic_structure/main.rs b/examples/basic_structure/main.rs index 6046e02e1e84..5d4d200477fb 100644 --- a/examples/basic_structure/main.rs +++ b/examples/basic_structure/main.rs @@ -81,7 +81,7 @@ async fn main() { // Enforce command checks even for owners (enforced by default) // Set to true to bypass checks, which is useful for testing skip_checks_for_owners: false, - event_handler: |_ctx, event, _framework, _data| { + event_handler: |_framework, event| { Box::pin(async move { println!( "Got an event in event handler: {:?}", diff --git a/examples/event_handler/main.rs b/examples/event_handler/main.rs index 34fb9fbb2cf7..a7b502d36d43 100644 --- a/examples/event_handler/main.rs +++ b/examples/event_handler/main.rs @@ -31,9 +31,7 @@ async fn main() { }) }) .options(poise::FrameworkOptions { - event_handler: |ctx, event, framework, data| { - Box::pin(event_handler(ctx, event, framework, data)) - }, + event_handler: |framework, event| Box::pin(event_handler(framework, event)), ..Default::default() }) .build(); @@ -46,11 +44,12 @@ async fn main() { } async fn event_handler( - ctx: &serenity::Context, + framework: poise::FrameworkContext<'_, Data, Error>, event: &serenity::FullEvent, - _framework: poise::FrameworkContext<'_, Data, Error>, - data: &Data, ) -> Result<(), Error> { + let data = framework.user_data; + let ctx = framework.serenity_context; + match event { serenity::FullEvent::Ready { data_about_bot, .. } => { println!("Logged in as {}", data_about_bot.user.name); diff --git a/examples/feature_showcase/main.rs b/examples/feature_showcase/main.rs index 20c5b3c110c2..ba3cf6adf2c3 100644 --- a/examples/feature_showcase/main.rs +++ b/examples/feature_showcase/main.rs @@ -78,7 +78,7 @@ async fn main() { ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("~".into()), - non_command_message: Some(|_, _, msg| { + non_command_message: Some(|_, msg| { Box::pin(async move { println!("non command message!: {}", msg.content); Ok(()) diff --git a/examples/manual_dispatch/main.rs b/examples/manual_dispatch/main.rs index f6595ae20a7f..19e4ef82ec1a 100644 --- a/examples/manual_dispatch/main.rs +++ b/examples/manual_dispatch/main.rs @@ -24,14 +24,14 @@ impl serenity::EventHandler for Handler { // FrameworkContext contains all data that poise::Framework usually manages let shard_manager = (*self.shard_manager.lock().unwrap()).clone().unwrap(); let framework_data = poise::FrameworkContext { - bot_id: serenity::UserId::new(846453852164587620), + serenity_context: &ctx, options: &self.options, user_data: &(), shard_manager: &shard_manager, }; let event = serenity::FullEvent::Message { new_message }; - poise::dispatch_event(framework_data, &ctx, event).await; + poise::dispatch_event(framework_data, event).await; } // For slash commands or edit tracking to work, forward interaction_create and message_update diff --git a/macros/src/command/prefix.rs b/macros/src/command/prefix.rs index 681e86ebaa3a..65e906b69a80 100644 --- a/macros/src/command/prefix.rs +++ b/macros/src/command/prefix.rs @@ -54,7 +54,7 @@ pub fn generate_prefix_action(inv: &Invocation) -> Result + ctx.serenity_context(), ctx.msg, ctx.args, 0 => #( #param_specs, )* #wildcard_arg ).await.map_err(|(error, input)| poise::FrameworkError::new_argument_parse( diff --git a/macros/src/command/slash.rs b/macros/src/command/slash.rs index ce27546b3a19..5b7ab9f33cd5 100644 --- a/macros/src/command/slash.rs +++ b/macros/src/command/slash.rs @@ -181,7 +181,7 @@ pub fn generate_slash_action(inv: &Invocation) -> Result + ctx.serenity_context(), ctx.interaction, ctx.args => #( (#param_names: #param_types), )* ).await.map_err(|error| error.to_framework_error(ctx))?; diff --git a/src/dispatch/common.rs b/src/dispatch/common.rs index ac4944832802..6f8bcf42e042 100644 --- a/src/dispatch/common.rs +++ b/src/dispatch/common.rs @@ -129,7 +129,7 @@ async fn check_permissions_and_cooldown_single<'a, U, E>( } // Before running any pre-command checks, make sure the bot has the permissions it needs - match missing_permissions(ctx, ctx.framework().bot_id, cmd.required_bot_permissions).await { + match missing_permissions(ctx, ctx.framework().bot_id(), cmd.required_bot_permissions).await { Some(missing_permissions) if missing_permissions.is_empty() => {} Some(missing_permissions) => { return Err(crate::FrameworkError::MissingBotPermissions { diff --git a/src/dispatch/mod.rs b/src/dispatch/mod.rs index 38c29fe8e793..d1c58866c4c0 100644 --- a/src/dispatch/mod.rs +++ b/src/dispatch/mod.rs @@ -10,11 +10,12 @@ pub use slash::*; use crate::serenity_prelude as serenity; -// TODO: integrate serenity::Context in here? Every place where FrameworkContext is passed is also -// passed serenity::Context /// A view into data stored by [`crate::Framework`] pub struct FrameworkContext<'a, U, E> { - /// User ID of this bot + /// Serenity's context + pub serenity_context: &'a serenity::Context, + /// User ID of this bot, available through serenity_context if cache is enabled. + #[cfg(not(feature = "cache"))] pub bot_id: serenity::UserId, /// Framework configuration pub options: &'a crate::FrameworkOptions, @@ -32,6 +33,16 @@ impl Clone for FrameworkContext<'_, U, E> { } } impl<'a, U, E> FrameworkContext<'a, U, E> { + /// Returns the user ID of the bot. + pub fn bot_id(&self) -> serenity::UserId { + #[cfg(feature = "cache")] + let bot_id = self.serenity_context.cache.current_user().id; + #[cfg(not(feature = "cache"))] + let bot_id = self.bot_id; + + bot_id + } + /// Returns the stored framework options, including commands. /// /// This function exists for API compatiblity with [`crate::Framework`]. On this type, you can @@ -61,7 +72,6 @@ impl<'a, U, E> FrameworkContext<'a, U, E> { /// Central event handling function of this library pub async fn dispatch_event( framework: crate::FrameworkContext<'_, U, E>, - ctx: &serenity::Context, event: serenity::FullEvent, ) { match &event { @@ -71,7 +81,6 @@ pub async fn dispatch_event( let trigger = crate::MessageDispatchTrigger::MessageCreate; if let Err(error) = prefix::dispatch_message( framework, - ctx, new_message, trigger, &invocation_data, @@ -101,7 +110,6 @@ pub async fn dispatch_event( }; if let Err(error) = prefix::dispatch_message( framework, - ctx, &msg, trigger, &invocation_data, @@ -123,7 +131,7 @@ pub async fn dispatch_event( .unwrap() .process_message_delete(*deleted_message_id); if let Some(bot_response) = bot_response { - if let Err(e) = bot_response.delete(ctx).await { + if let Err(e) = bot_response.delete(framework.serenity_context).await { tracing::warn!("failed to delete bot response: {}", e); } } @@ -136,7 +144,6 @@ pub async fn dispatch_event( let mut parent_commands = Vec::new(); if let Err(error) = slash::dispatch_interaction( framework, - ctx, interaction, &std::sync::atomic::AtomicBool::new(false), &invocation_data, @@ -155,7 +162,6 @@ pub async fn dispatch_event( let mut parent_commands = Vec::new(); if let Err(error) = slash::dispatch_autocomplete( framework, - ctx, interaction, &std::sync::atomic::AtomicBool::new(false), &invocation_data, @@ -172,12 +178,9 @@ pub async fn dispatch_event( // Do this after the framework's Ready handling, so that get_user_data() doesnt // potentially block infinitely - if let Err(error) = - (framework.options.event_handler)(ctx, &event, framework, framework.user_data).await - { + if let Err(error) = (framework.options.event_handler)(framework, &event).await { let error = crate::FrameworkError::EventHandler { error, - ctx, event: &event, framework, }; diff --git a/src/dispatch/prefix.rs b/src/dispatch/prefix.rs index cdec95adf63f..a73ef0cb84e1 100644 --- a/src/dispatch/prefix.rs +++ b/src/dispatch/prefix.rs @@ -7,16 +7,13 @@ use crate::serenity_prelude as serenity; /// Returns tuple of stripped prefix and rest of the message, if any prefix matches async fn strip_prefix<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, msg: &'a serenity::Message, ) -> Option<(&'a str, &'a str)> { let partial_ctx = crate::PartialContext { guild_id: msg.guild_id, channel_id: msg.channel_id, author: &msg.author, - serenity_context: ctx, framework, - data: framework.user_data, __non_exhaustive: (), }; @@ -68,7 +65,7 @@ async fn strip_prefix<'a, U, E>( } if let Some(dynamic_prefix) = framework.options.prefix_options.stripped_dynamic_prefix { - match dynamic_prefix(ctx, msg, framework.user_data).await { + match dynamic_prefix(framework.serenity_context, msg, framework.user_data).await { Ok(result) => { if let Some((prefix, content)) = result { return Some((prefix, content)); @@ -91,7 +88,7 @@ async fn strip_prefix<'a, U, E>( msg.content .strip_prefix("<@")? .trim_start_matches('!') - .strip_prefix(&framework.bot_id.to_string())? + .strip_prefix(&framework.bot_id().to_string())? .strip_prefix('>') })() { let mention_prefix = &msg.content[..(msg.content.len() - stripped_content.len())]; @@ -186,21 +183,13 @@ pub fn find_command<'a, U, E>( /// Manually dispatches a message with the prefix framework pub async fn dispatch_message<'a, U: Send + Sync, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, msg: &'a serenity::Message, trigger: crate::MessageDispatchTrigger, invocation_data: &'a tokio::sync::Mutex>, parent_commands: &'a mut Vec<&'a crate::Command>, ) -> Result<(), crate::FrameworkError<'a, U, E>> { - if let Some(ctx) = parse_invocation( - framework, - ctx, - msg, - trigger, - invocation_data, - parent_commands, - ) - .await? + if let Some(ctx) = + parse_invocation(framework, msg, trigger, invocation_data, parent_commands).await? { crate::catch_unwind_maybe(run_invocation(ctx)) .await @@ -209,14 +198,13 @@ pub async fn dispatch_message<'a, U: Send + Sync, E>( ctx: ctx.into(), })??; } else if let Some(non_command_message) = framework.options.prefix_options.non_command_message { - non_command_message(&framework, ctx, msg) - .await - .map_err(|e| crate::FrameworkError::NonCommandMessage { + non_command_message(&framework, msg).await.map_err(|e| { + crate::FrameworkError::NonCommandMessage { error: e, - ctx, framework, msg, - })?; + } + })?; } Ok(()) } @@ -229,7 +217,6 @@ pub async fn dispatch_message<'a, U: Send + Sync, E>( /// fully parsed. pub async fn parse_invocation<'a, U: Send + Sync, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, msg: &'a serenity::Message, trigger: crate::MessageDispatchTrigger, invocation_data: &'a tokio::sync::Mutex>, @@ -241,7 +228,8 @@ pub async fn parse_invocation<'a, U: Send + Sync, E>( } // Check if we're allowed to execute our own messages - if framework.bot_id == msg.author.id && !framework.options.prefix_options.execute_self_messages + if framework.bot_id() == msg.author.id + && !framework.options.prefix_options.execute_self_messages { return Ok(None); } @@ -254,7 +242,7 @@ pub async fn parse_invocation<'a, U: Send + Sync, E>( } // Strip prefix, trim whitespace between prefix and rest, split rest into command name and args - let (prefix, msg_content) = match strip_prefix(framework, ctx, msg).await { + let (prefix, msg_content) = match strip_prefix(framework, msg).await { Some(x) => x, None => return Ok(None), }; @@ -267,7 +255,6 @@ pub async fn parse_invocation<'a, U: Send + Sync, E>( parent_commands, ) .ok_or(crate::FrameworkError::UnknownCommand { - ctx, msg, prefix, msg_content, @@ -283,13 +270,11 @@ pub async fn parse_invocation<'a, U: Send + Sync, E>( }; Ok(Some(crate::PrefixContext { - serenity_context: ctx, msg, prefix, invoked_command_name, args, framework, - data: framework.user_data, parent_commands, command, invocation_data, @@ -326,7 +311,11 @@ pub async fn run_invocation( // Typing is broadcasted as long as this object is alive let _typing_broadcaster = if ctx.command.broadcast_typing { - Some(ctx.msg.channel_id.start_typing(&ctx.serenity_context.http)) + Some( + ctx.msg + .channel_id + .start_typing(&ctx.framework.serenity_context.http), + ) } else { None }; diff --git a/src/dispatch/slash.rs b/src/dispatch/slash.rs index dd20995f20be..fd63dfe2ea36 100644 --- a/src/dispatch/slash.rs +++ b/src/dispatch/slash.rs @@ -40,7 +40,6 @@ fn find_matching_command<'a, 'b, U, E>( #[allow(clippy::too_many_arguments)] // We need to pass them all in to create Context. fn extract_command<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, interaction: &'a serenity::CommandInteraction, interaction_type: crate::CommandInteractionType, has_sent_initial_response: &'a std::sync::atomic::AtomicBool, @@ -56,14 +55,11 @@ fn extract_command<'a, U, E>( ); let (command, leaf_interaction_options) = search_result.ok_or(crate::FrameworkError::UnknownInteraction { - ctx, framework, interaction, })?; Ok(crate::ApplicationContext { - data: framework.user_data, - serenity_context: ctx, framework, interaction, interaction_type, @@ -80,7 +76,6 @@ fn extract_command<'a, U, E>( #[allow(clippy::too_many_arguments)] // We need to pass them all in to create Context. pub async fn extract_command_and_run_checks<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, interaction: &'a serenity::CommandInteraction, interaction_type: crate::CommandInteractionType, has_sent_initial_response: &'a std::sync::atomic::AtomicBool, @@ -90,7 +85,6 @@ pub async fn extract_command_and_run_checks<'a, U, E>( ) -> Result, crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, - ctx, interaction, interaction_type, has_sent_initial_response, @@ -165,7 +159,6 @@ async fn run_command( /// Dispatches this interaction onto framework commands, i.e. runs the associated command pub async fn dispatch_interaction<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, interaction: &'a serenity::CommandInteraction, // Need to pass this in from outside because of lifetime issues has_sent_initial_response: &'a std::sync::atomic::AtomicBool, @@ -177,7 +170,6 @@ pub async fn dispatch_interaction<'a, U, E>( ) -> Result<(), crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, - ctx, interaction, crate::CommandInteractionType::Command, has_sent_initial_response, @@ -247,7 +239,7 @@ async fn run_autocomplete( if let Err(e) = ctx .interaction .create_response( - &ctx.serenity_context, + &ctx.framework.serenity_context, serenity::CreateInteractionResponse::Autocomplete(autocomplete_response), ) .await @@ -262,7 +254,6 @@ async fn run_autocomplete( /// callback pub async fn dispatch_autocomplete<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, - ctx: &'a serenity::Context, interaction: &'a serenity::CommandInteraction, // Need to pass the following in from outside because of lifetime issues has_sent_initial_response: &'a std::sync::atomic::AtomicBool, @@ -272,7 +263,6 @@ pub async fn dispatch_autocomplete<'a, U, E>( ) -> Result<(), crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, - ctx, interaction, crate::CommandInteractionType::Autocomplete, has_sent_initial_response, diff --git a/src/framework/mod.rs b/src/framework/mod.rs index 5a3531f6b346..8adea58bbc63 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -191,17 +191,20 @@ async fn raw_dispatch_event( } let user_data = framework.user_data().await; + #[cfg(not(feature = "cache"))] let bot_id = *framework .bot_id .get() .expect("bot ID not set even though we awaited Ready"); let framework = crate::FrameworkContext { + #[cfg(not(feature = "cache"))] bot_id, + serenity_context: &ctx, options: &framework.options, user_data, shard_manager: framework.shard_manager(), }; - crate::dispatch_event(framework, &ctx, event).await; + crate::dispatch_event(framework, event).await; } /// Traverses commands recursively and sets [`crate::Command::qualified_name`] to its actual value diff --git a/src/modal.rs b/src/modal.rs index 0ad4c78ac4a5..8d9269be7973 100644 --- a/src/modal.rs +++ b/src/modal.rs @@ -95,7 +95,7 @@ pub async fn execute_modal( ) -> Result, serenity::Error> { let interaction = ctx.interaction; let response = execute_modal_generic( - ctx.serenity_context, + ctx.serenity_context(), |resp| interaction.create_response(ctx, resp), interaction.id.to_string(), defaults, diff --git a/src/reply/send_reply.rs b/src/reply/send_reply.rs index 5308e825da69..b10b1e8a7716 100644 --- a/src/reply/send_reply.rs +++ b/src/reply/send_reply.rs @@ -71,7 +71,7 @@ pub async fn send_application_reply( .to_slash_followup_response(serenity::CreateInteractionResponseFollowup::new()); ctx.interaction - .create_followup(ctx.serenity_context, builder) + .create_followup(ctx.serenity_context(), builder) .await? })) } else { @@ -80,7 +80,7 @@ pub async fn send_application_reply( ctx.interaction .create_response( - ctx.serenity_context, + ctx.serenity_context(), serenity::CreateInteractionResponse::Message(builder), ) .await?; @@ -91,7 +91,7 @@ pub async fn send_application_reply( }; Ok(super::ReplyHandle(super::ReplyHandleInner::Application { - http: &ctx.serenity_context.http, + http: &ctx.serenity_context().http, interaction: ctx.interaction, followup, })) @@ -124,7 +124,7 @@ pub async fn send_prefix_reply( Ok(Box::new(if let Some(mut response) = existing_response { response - .edit(ctx.serenity_context, { + .edit(ctx.serenity_context(), { // Reset the message. We don't want leftovers of the previous message (e.g. user // sends a message with `.content("abc")` in a track_edits command, and the edited // message happens to contain embeds, we don't want to keep those embeds) @@ -150,7 +150,7 @@ pub async fn send_prefix_reply( let new_response = ctx .msg .channel_id - .send_message(ctx.serenity_context, builder.to_prefix(ctx.msg.into())) + .send_message(ctx.serenity_context(), builder.to_prefix(ctx.msg.into())) .await?; // We don't check ctx.command.reuse_response because we need to store bot responses for // track_deletion too diff --git a/src/structs/context.rs b/src/structs/context.rs index a9f0d99ff3f8..74a5dadd3f96 100644 --- a/src/structs/context.rs +++ b/src/structs/context.rs @@ -116,7 +116,7 @@ context_methods! { Self::Prefix(ctx) => Some( ctx.msg .channel_id - .start_typing(&ctx.serenity_context.http), + .start_typing(&ctx.serenity_context().http), ), }) } @@ -163,10 +163,7 @@ context_methods! { /// Return the stored [`serenity::Context`] within the underlying context type. (serenity_context self) (pub fn serenity_context(self) -> &'a serenity::Context) { - match self { - Self::Application(ctx) => ctx.serenity_context, - Self::Prefix(ctx) => ctx.serenity_context, - } + self.framework().serenity_context } /// Create a [`crate::CooldownContext`] based off the underlying context type. @@ -199,10 +196,7 @@ context_methods! { /// Return a reference to your custom user data (data self) (pub fn data(self) -> &'a U) { - match self { - Self::Application(ctx) => ctx.data, - Self::Prefix(ctx) => ctx.data, - } + self.framework().user_data } /// Return the channel ID of this context @@ -653,13 +647,8 @@ pub struct PartialContext<'a, U, E> { pub channel_id: serenity::ChannelId, /// ID of the invocation author pub author: &'a serenity::User, - /// Serenity's context, like HTTP or cache - pub serenity_context: &'a serenity::Context, /// Useful if you need the list of commands, for example for a custom help command pub framework: crate::FrameworkContext<'a, U, E>, - /// Your custom user data - // TODO: redundant with framework - pub data: &'a U, #[doc(hidden)] pub __non_exhaustive: (), } @@ -677,9 +666,7 @@ impl<'a, U, E> From> for PartialContext<'a, U, E> { guild_id: ctx.guild_id(), channel_id: ctx.channel_id(), author: ctx.author(), - serenity_context: ctx.serenity_context(), framework: ctx.framework(), - data: ctx.data(), __non_exhaustive: (), } } diff --git a/src/structs/framework_error.rs b/src/structs/framework_error.rs index 018d40d8a188..441a654b6e48 100644 --- a/src/structs/framework_error.rs +++ b/src/structs/framework_error.rs @@ -28,8 +28,6 @@ pub enum FrameworkError<'a, U, E> { EventHandler { /// Error which was thrown in the event handler code error: E, - /// The serenity context passed to the event handler - ctx: &'a serenity::Context, /// Which event was being processed when the error occurred event: &'a serenity::FullEvent, /// The Framework passed to the event @@ -164,9 +162,6 @@ pub enum FrameworkError<'a, U, E> { /// A message had the correct prefix but the following string was not a recognized command #[non_exhaustive] UnknownCommand { - /// Serenity's Context - #[derivative(Debug = "ignore")] - ctx: &'a serenity::Context, /// The message in question msg: &'a serenity::Message, /// The prefix that was recognized @@ -187,9 +182,6 @@ pub enum FrameworkError<'a, U, E> { /// The command name from the interaction is unrecognized #[non_exhaustive] UnknownInteraction { - #[derivative(Debug = "ignore")] - /// Serenity's Context - ctx: &'a serenity::Context, /// Framework context #[derivative(Debug = "ignore")] framework: crate::FrameworkContext<'a, U, E>, @@ -201,9 +193,6 @@ pub enum FrameworkError<'a, U, E> { NonCommandMessage { /// The error thrown by user code error: E, - #[derivative(Debug = "ignore")] - /// Serenity's Context - ctx: &'a serenity::Context, /// Framework context #[derivative(Debug = "ignore")] framework: crate::FrameworkContext<'a, U, E>, @@ -220,12 +209,12 @@ impl<'a, U, E> FrameworkError<'a, U, E> { pub fn serenity_context(&self) -> &'a serenity::Context { match *self { Self::Setup { ctx, .. } => ctx, - Self::EventHandler { ctx, .. } => ctx, + Self::EventHandler { framework, .. } => framework.serenity_context, Self::Command { ctx, .. } => ctx.serenity_context(), Self::SubcommandRequired { ctx } => ctx.serenity_context(), Self::CommandPanic { ctx, .. } => ctx.serenity_context(), Self::ArgumentParse { ctx, .. } => ctx.serenity_context(), - Self::CommandStructureMismatch { ctx, .. } => ctx.serenity_context, + Self::CommandStructureMismatch { ctx, .. } => ctx.framework.serenity_context, Self::CooldownHit { ctx, .. } => ctx.serenity_context(), Self::MissingBotPermissions { ctx, .. } => ctx.serenity_context(), Self::MissingUserPermissions { ctx, .. } => ctx.serenity_context(), @@ -234,10 +223,10 @@ impl<'a, U, E> FrameworkError<'a, U, E> { Self::DmOnly { ctx, .. } => ctx.serenity_context(), Self::NsfwOnly { ctx, .. } => ctx.serenity_context(), Self::CommandCheckFailed { ctx, .. } => ctx.serenity_context(), - Self::DynamicPrefix { ctx, .. } => ctx.serenity_context, - Self::UnknownCommand { ctx, .. } => ctx, - Self::UnknownInteraction { ctx, .. } => ctx, - Self::NonCommandMessage { ctx, .. } => ctx, + Self::DynamicPrefix { ctx, .. } => ctx.framework.serenity_context, + Self::UnknownCommand { framework, .. } => framework.serenity_context, + Self::UnknownInteraction { framework, .. } => framework.serenity_context, + Self::NonCommandMessage { framework, .. } => framework.serenity_context, Self::__NonExhaustive(unreachable) => match unreachable {}, } } diff --git a/src/structs/framework_options.rs b/src/structs/framework_options.rs index 3ec310e9c3c5..60c003f953a6 100644 --- a/src/structs/framework_options.rs +++ b/src/structs/framework_options.rs @@ -49,11 +49,8 @@ pub struct FrameworkOptions { /// deletions or guild updates. #[derivative(Debug = "ignore")] pub event_handler: for<'a> fn( - &'a serenity::Context, - &'a serenity::FullEvent, crate::FrameworkContext<'a, U, E>, - // TODO: redundant with framework - &'a U, + &'a serenity::FullEvent, ) -> BoxFuture<'a, Result<(), E>>, /// Renamed to [`Self::event_handler`]! #[deprecated = "renamed to event_handler"] @@ -101,7 +98,7 @@ where } }) }, - event_handler: |_, _, _, _| Box::pin(async { Ok(()) }), + event_handler: |_, _| Box::pin(async { Ok(()) }), listener: (), pre_command: |_| Box::pin(async {}), post_command: |_| Box::pin(async {}), diff --git a/src/structs/prefix.rs b/src/structs/prefix.rs index f7615b885e1f..afcf37b35bde 100644 --- a/src/structs/prefix.rs +++ b/src/structs/prefix.rs @@ -22,9 +22,6 @@ pub enum MessageDispatchTrigger { #[derive(derivative::Derivative)] #[derivative(Debug(bound = ""))] pub struct PrefixContext<'a, U, E> { - /// Serenity's context, like HTTP or cache - #[derivative(Debug = "ignore")] - pub serenity_context: &'a serenity::Context, /// The invoking user message pub msg: &'a serenity::Message, /// Prefix used by the user to invoke this command @@ -42,10 +39,6 @@ pub struct PrefixContext<'a, U, E> { pub parent_commands: &'a [&'a crate::Command], /// The command object which is the current command pub command: &'a crate::Command, - /// Your custom user data - // TODO: redundant with framework - #[derivative(Debug = "ignore")] - pub data: &'a U, /// Custom user data carried across a single command invocation pub invocation_data: &'a tokio::sync::Mutex>, /// How this command invocation was triggered @@ -157,7 +150,6 @@ pub struct PrefixFrameworkOptions { pub non_command_message: Option< for<'a> fn( &'a crate::FrameworkContext<'a, U, E>, - &'a serenity::Context, &'a serenity::Message, ) -> crate::BoxFuture<'a, Result<(), E>>, >, diff --git a/src/structs/slash.rs b/src/structs/slash.rs index 9419bc923437..3036c65dc136 100644 --- a/src/structs/slash.rs +++ b/src/structs/slash.rs @@ -15,9 +15,6 @@ pub enum CommandInteractionType { #[derive(derivative::Derivative)] #[derivative(Debug(bound = ""))] pub struct ApplicationContext<'a, U, E> { - /// Serenity's context, like HTTP or cache - #[derivative(Debug = "ignore")] - pub serenity_context: &'a serenity::Context, /// The interaction which triggered this command execution. pub interaction: &'a serenity::CommandInteraction, /// The type of the interaction which triggered this command execution. @@ -41,10 +38,6 @@ pub struct ApplicationContext<'a, U, E> { pub parent_commands: &'a [&'a crate::Command], /// The command object which is the current command pub command: &'a crate::Command, - /// Your custom user data - // TODO: redundant with framework - #[derivative(Debug = "ignore")] - pub data: &'a U, /// Custom user data carried across a single command invocation pub invocation_data: &'a tokio::sync::Mutex>, // #[non_exhaustive] forbids struct update syntax for ?? reason @@ -74,7 +67,7 @@ impl ApplicationContext<'_, U, E> { ); self.interaction - .create_response(self.serenity_context, response) + .create_response(self.framework.serenity_context, response) .await?; self.has_sent_initial_response From 13d0f15bd3af1b929503f00d50299eb24b8fe984 Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Tue, 23 Jan 2024 21:49:44 +0000 Subject: [PATCH 2/8] Make CreateReply fields private to match serenity --- src/reply/builder.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/reply/builder.rs b/src/reply/builder.rs index 980db844bf7a..8610c3f2cd2d 100644 --- a/src/reply/builder.rs +++ b/src/reply/builder.rs @@ -4,26 +4,23 @@ use crate::serenity_prelude as serenity; /// Message builder that abstracts over prefix and application command responses #[derive(Default, Clone)] +#[allow(clippy::missing_docs_in_private_items)] // docs on setters pub struct CreateReply { - /// Message content. - pub content: Option, - /// Embeds, if present. - pub embeds: Vec, - /// Message attachments. - pub attachments: Vec, - /// Whether the message is ephemeral (only has an effect in application commands) - pub ephemeral: Option, - /// Message components, that is, buttons and select menus. - pub components: Option>, - /// The allowed mentions for the message. - pub allowed_mentions: Option, - /// Whether this message is an inline reply. - pub reply: bool, - #[doc(hidden)] - pub __non_exhaustive: (), + content: Option, + embeds: Vec, + attachments: Vec, + pub(crate) ephemeral: Option, + components: Option>, + pub(crate) allowed_mentions: Option, + reply: bool, } impl CreateReply { + /// Creates a blank CreateReply. Equivalent to [`Self::default`]. + pub fn new() -> Self { + Self::default() + } + /// Set the content of the message. pub fn content(mut self, content: impl Into) -> Self { self.content = Some(content.into()); @@ -97,7 +94,6 @@ impl CreateReply { ephemeral, allowed_mentions, reply: _, // can't reply to a message in interactions - __non_exhaustive: (), } = self; if let Some(content) = content { @@ -129,7 +125,6 @@ impl CreateReply { ephemeral, allowed_mentions, reply: _, - __non_exhaustive: (), } = self; if let Some(content) = content { @@ -162,7 +157,6 @@ impl CreateReply { ephemeral: _, // can't edit ephemerality in retrospect allowed_mentions, reply: _, - __non_exhaustive: (), } = self; if let Some(content) = content { @@ -188,7 +182,6 @@ impl CreateReply { ephemeral: _, // not supported in prefix allowed_mentions, reply: _, // can't edit reference message afterwards - __non_exhaustive: (), } = self; let mut attachments_builder = serenity::EditAttachments::new(); @@ -222,7 +215,6 @@ impl CreateReply { ephemeral: _, // not supported in prefix allowed_mentions, reply, - __non_exhaustive: (), } = self; let mut builder = serenity::CreateMessage::new(); From 264bbafb478524c56618d173c30a44072a1ab520 Mon Sep 17 00:00:00 2001 From: Gnome! Date: Tue, 30 Jan 2024 12:26:14 +0000 Subject: [PATCH 3/8] Take prefix as Cow<'static, str> (#242) --- src/builtins/help.rs | 8 +++++--- src/builtins/pretty_help.rs | 14 +++++++------- src/dispatch/prefix.rs | 4 ++-- src/structs/prefix.rs | 9 ++++++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/builtins/help.rs b/src/builtins/help.rs index 9cc007910fed..b247533e2dac 100644 --- a/src/builtins/help.rs +++ b/src/builtins/help.rs @@ -1,7 +1,7 @@ //! Contains the built-in help command and surrounding infrastructure use crate::{serenity_prelude as serenity, CreateReply}; -use std::fmt::Write as _; +use std::{borrow::Cow, fmt::Write as _}; /// Optional configuration for how the help message from [`help()`] looks pub struct HelpConfiguration<'a> { @@ -85,7 +85,9 @@ impl TwoColumnList { } /// Get the prefix from options -pub(super) async fn get_prefix_from_options(ctx: crate::Context<'_, U, E>) -> Option { +pub(super) async fn get_prefix_from_options( + ctx: crate::Context<'_, U, E>, +) -> Option> { let options = &ctx.framework().options().prefix_options; match &options.prefix { Some(fixed_prefix) => Some(fixed_prefix.clone()), @@ -155,7 +157,7 @@ async fn help_single_command( // None can happen if the prefix is dynamic, and the callback // fails due to help being invoked with slash or context menu // commands. Not sure there's a better way to handle this. - None => String::from(""), + None => Cow::Borrowed(""), }; invocations.push(format!("`{}{}`", prefix, command.name)); if subprefix.is_none() { diff --git a/src/builtins/pretty_help.rs b/src/builtins/pretty_help.rs index 9d4dd7553bfa..08f844846cae 100644 --- a/src/builtins/pretty_help.rs +++ b/src/builtins/pretty_help.rs @@ -1,7 +1,7 @@ //! Contains a built-in help command and surrounding infrastructure that uses embeds. use crate::{serenity_prelude as serenity, CreateReply}; -use std::fmt::Write as _; +use std::{borrow::Cow, fmt::Write as _}; /// Optional configuration for how the help message from [`pretty_help()`] looks pub struct PrettyHelpConfiguration<'a> { @@ -82,7 +82,7 @@ async fn pretty_help_all_commands( for cmd in cmds { let name = cmd.context_menu_name.as_deref().unwrap_or(&cmd.name); - let prefix = format_cmd_prefix(cmd, &options_prefix); + let prefix = format_cmd_prefix(cmd, options_prefix.as_deref()); if let Some(description) = cmd.description.as_deref() { writeln!(buffer, "{}{}`: *{}*", prefix, name, description).ok(); @@ -93,7 +93,7 @@ async fn pretty_help_all_commands( if config.show_subcommands { for sbcmd in &cmd.subcommands { let name = sbcmd.context_menu_name.as_deref().unwrap_or(&sbcmd.name); - let prefix = format_cmd_prefix(sbcmd, &options_prefix); + let prefix = format_cmd_prefix(sbcmd, options_prefix.as_deref()); if let Some(description) = sbcmd.description.as_deref() { writeln!(buffer, "> {}{}`: *{}*", prefix, name, description).ok(); @@ -128,11 +128,11 @@ async fn pretty_help_all_commands( } /// Figures out which prefix a command should have -fn format_cmd_prefix(cmd: &crate::Command, options_prefix: &Option) -> String { +fn format_cmd_prefix(cmd: &crate::Command, options_prefix: Option<&str>) -> String { if cmd.slash_action.is_some() { "`/".into() } else if cmd.prefix_action.is_some() { - format!("`{}", options_prefix.as_deref().unwrap_or_default()) + format!("`{}", options_prefix.unwrap_or_default()) } else if cmd.context_menu_action.is_some() { match cmd.context_menu_action { Some(crate::ContextMenuCommandAction::Message(_)) => "Message menu: `".into(), @@ -188,7 +188,7 @@ async fn pretty_help_single_command( .await // This can happen if the prefix is dynamic, and the callback fails // due to help being invoked with slash or context menu commands. - .unwrap_or_else(|| String::from("")); + .unwrap_or(Cow::Borrowed("")); invocations.push(format!("`{}{}`", prefix, command.name)); subprefix = subprefix.or(Some(format!("> `{}{}`", prefix, command.name))); } @@ -248,7 +248,7 @@ async fn pretty_help_single_command( .subcommands .iter() .map(|sbcmd| { - let prefix = format_cmd_prefix(sbcmd, &subprefix); // i have no idea about this really + let prefix = format_cmd_prefix(sbcmd, subprefix.as_deref()); // i have no idea about this really let name = sbcmd.context_menu_name.as_deref().unwrap_or(&sbcmd.name); if let Some(description) = sbcmd.description.as_deref() { format!("> {}{}`: *{} *", prefix, name, description) diff --git a/src/dispatch/prefix.rs b/src/dispatch/prefix.rs index a73ef0cb84e1..1cbc6d8ab78f 100644 --- a/src/dispatch/prefix.rs +++ b/src/dispatch/prefix.rs @@ -21,7 +21,7 @@ async fn strip_prefix<'a, U, E>( match dynamic_prefix(partial_ctx).await { Ok(prefix) => { if let Some(prefix) = prefix { - if msg.content.starts_with(&prefix) { + if msg.content.starts_with(prefix.as_ref()) { return Some(msg.content.split_at(prefix.len())); } } @@ -37,7 +37,7 @@ async fn strip_prefix<'a, U, E>( } } - if let Some(prefix) = &framework.options.prefix_options.prefix { + if let Some(prefix) = framework.options.prefix_options.prefix.as_deref() { if let Some(content) = msg.content.strip_prefix(prefix) { return Some((prefix, content)); } diff --git a/src/structs/prefix.rs b/src/structs/prefix.rs index afcf37b35bde..8a754b6250fe 100644 --- a/src/structs/prefix.rs +++ b/src/structs/prefix.rs @@ -1,5 +1,7 @@ //! Holds prefix-command definition structs. +use std::borrow::Cow; + use crate::{serenity_prelude as serenity, BoxFuture}; /// The event that triggered a prefix command execution @@ -82,7 +84,7 @@ pub enum Prefix { pub struct PrefixFrameworkOptions { /// The main bot prefix. Can be set to None if the bot supports only /// [dynamic prefixes](Self::dynamic_prefix). - pub prefix: Option, + pub prefix: Option>, /// List of additional bot prefixes // TODO: maybe it would be nicer to have separate fields for literal and regex prefixes // That way, you don't need to wrap every single literal prefix in a long path which looks ugly @@ -93,8 +95,9 @@ pub struct PrefixFrameworkOptions { /// /// For more advanced dynamic prefixes, see [`Self::stripped_dynamic_prefix`] #[derivative(Debug = "ignore")] - pub dynamic_prefix: - Option) -> BoxFuture<'_, Result, E>>>, + pub dynamic_prefix: Option< + fn(crate::PartialContext<'_, U, E>) -> BoxFuture<'_, Result>, E>>, + >, /// Callback invoked on every message to strip the prefix off an incoming message. /// /// Override this field for advanced dynamic prefixes which change depending on guild or user. From 6c26be9d3fa6ee092b9e418618596dfdf443097b Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Fri, 10 May 2024 21:42:48 +0100 Subject: [PATCH 4/8] Remove impl AsRef --- examples/feature_showcase/modal.rs | 6 ++++-- src/modal.rs | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/feature_showcase/modal.rs b/examples/feature_showcase/modal.rs index 99a7fbaef046..e4f1dbf25c5b 100644 --- a/examples/feature_showcase/modal.rs +++ b/examples/feature_showcase/modal.rs @@ -37,13 +37,15 @@ pub async fn component_modal(ctx: crate::Context<'_>) -> Result<(), Error> { ctx.send(reply).await?; - while let Some(mci) = serenity::ComponentInteractionCollector::new(ctx.serenity_context()) + let serenity_ctx = ctx.serenity_context(); + while let Some(mci) = serenity::ComponentInteractionCollector::new(serenity_ctx) .timeout(std::time::Duration::from_secs(120)) .filter(move |mci| mci.data.custom_id == "open_modal") .await { let data = - poise::execute_modal_on_component_interaction::(ctx, mci, None, None).await?; + poise::execute_modal_on_component_interaction::(serenity_ctx, mci, None, None) + .await?; println!("Got data: {:?}", data); } Ok(()) diff --git a/src/modal.rs b/src/modal.rs index 8d9269be7973..d1edeb69f8e2 100644 --- a/src/modal.rs +++ b/src/modal.rs @@ -120,14 +120,14 @@ pub async fn execute_modal( /// If you need more specialized behavior, you can copy paste the implementation of this function /// and adjust to your needs. The code of this function is just a starting point. pub async fn execute_modal_on_component_interaction( - ctx: impl AsRef, + ctx: &serenity::Context, interaction: serenity::ComponentInteraction, defaults: Option, timeout: Option, ) -> Result, serenity::Error> { execute_modal_generic( - ctx.as_ref(), - |resp| interaction.create_response(ctx.as_ref(), resp), + ctx, + |resp| interaction.create_response(ctx, resp), interaction.id.to_string(), defaults, timeout, From b45fe30ca5f68f70ba83f3c5be5b1b7640dff682 Mon Sep 17 00:00:00 2001 From: Michael Ripley Date: Mon, 12 Aug 2024 04:29:46 -0500 Subject: [PATCH 5/8] create_application_commands can now take any IntoIterator: not just &[] --- src/builtins/register.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/builtins/register.rs b/src/builtins/register.rs index 8caa9a2f2b1b..0c9b23122461 100644 --- a/src/builtins/register.rs +++ b/src/builtins/register.rs @@ -16,8 +16,8 @@ use crate::serenity_prelude as serenity; /// serenity::Command::set_global_commands(ctx, create_commands).await?; /// # Ok(()) } /// ``` -pub fn create_application_commands( - commands: &[crate::Command], +pub fn create_application_commands<'a, U:'a , E: 'a>( + commands: impl IntoIterator>, ) -> Vec { /// We decided to extract context menu commands recursively, despite the subcommand hierarchy /// not being preserved. Because it's more confusing to just silently discard context menu @@ -35,7 +35,7 @@ pub fn create_application_commands( } } - let mut commands_builder = Vec::with_capacity(commands.len()); + let mut commands_builder = Vec::new(); for command in commands { if let Some(slash_command) = command.create_as_slash_command() { commands_builder.push(slash_command); From 18083985e3e319fe0fa8eadcddb5e98fd8eb5737 Mon Sep 17 00:00:00 2001 From: Michael Ripley Date: Mon, 12 Aug 2024 05:01:00 -0500 Subject: [PATCH 6/8] also update functions that call create_application_commands --- src/builtins/register.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builtins/register.rs b/src/builtins/register.rs index 0c9b23122461..95ad7bbd4e74 100644 --- a/src/builtins/register.rs +++ b/src/builtins/register.rs @@ -49,9 +49,9 @@ pub fn create_application_commands<'a, U:'a , E: 'a>( /// /// Thin wrapper around [`create_application_commands`] that funnels the returned builder into /// [`serenity::Command::set_global_commands`]. -pub async fn register_globally( +pub async fn register_globally<'a, U: 'a, E: 'a>( http: impl AsRef, - commands: &[crate::Command], + commands: impl IntoIterator>, ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); serenity::Command::set_global_commands(http, builder).await?; @@ -62,9 +62,9 @@ pub async fn register_globally( /// /// Thin wrapper around [`create_application_commands`] that funnels the returned builder into /// [`serenity::GuildId::set_commands`]. -pub async fn register_in_guild( +pub async fn register_in_guild<'a, U: 'a, E: 'a>( http: impl AsRef, - commands: &[crate::Command], + commands: impl IntoIterator>, guild_id: serenity::GuildId, ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); From bb3758b458c361cc60474052552913b96cb8bc9c Mon Sep 17 00:00:00 2001 From: Michael Ripley Date: Mon, 12 Aug 2024 05:13:26 -0500 Subject: [PATCH 7/8] Fix rustfmt errors --- src/builtins/register.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builtins/register.rs b/src/builtins/register.rs index 95ad7bbd4e74..1271b7b1ebec 100644 --- a/src/builtins/register.rs +++ b/src/builtins/register.rs @@ -16,8 +16,8 @@ use crate::serenity_prelude as serenity; /// serenity::Command::set_global_commands(ctx, create_commands).await?; /// # Ok(()) } /// ``` -pub fn create_application_commands<'a, U:'a , E: 'a>( - commands: impl IntoIterator>, +pub fn create_application_commands<'a, U: 'a, E: 'a>( + commands: impl IntoIterator>, ) -> Vec { /// We decided to extract context menu commands recursively, despite the subcommand hierarchy /// not being preserved. Because it's more confusing to just silently discard context menu @@ -51,7 +51,7 @@ pub fn create_application_commands<'a, U:'a , E: 'a>( /// [`serenity::Command::set_global_commands`]. pub async fn register_globally<'a, U: 'a, E: 'a>( http: impl AsRef, - commands: impl IntoIterator>, + commands: impl IntoIterator>, ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); serenity::Command::set_global_commands(http, builder).await?; @@ -64,7 +64,7 @@ pub async fn register_globally<'a, U: 'a, E: 'a>( /// [`serenity::GuildId::set_commands`]. pub async fn register_in_guild<'a, U: 'a, E: 'a>( http: impl AsRef, - commands: impl IntoIterator>, + commands: impl IntoIterator>, guild_id: serenity::GuildId, ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); From b9edb995c841f084c5644fb6e6b3565855ef7ae6 Mon Sep 17 00:00:00 2001 From: Michael Ripley Date: Mon, 12 Aug 2024 05:24:40 -0500 Subject: [PATCH 8/8] Restore with_capacity optimization --- src/builtins/register.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/builtins/register.rs b/src/builtins/register.rs index 1271b7b1ebec..dd6e9315b613 100644 --- a/src/builtins/register.rs +++ b/src/builtins/register.rs @@ -35,8 +35,9 @@ pub fn create_application_commands<'a, U: 'a, E: 'a>( } } - let mut commands_builder = Vec::new(); - for command in commands { + let command_iter = commands.into_iter(); + let mut commands_builder = Vec::with_capacity(command_iter.size_hint().0); + for command in command_iter { if let Some(slash_command) = command.create_as_slash_command() { commands_builder.push(slash_command); }