diff --git a/examples/feature_showcase/autocomplete.rs b/examples/feature_showcase/autocomplete.rs index 1997686a01b0..20e2b8ba42a7 100644 --- a/examples/feature_showcase/autocomplete.rs +++ b/examples/feature_showcase/autocomplete.rs @@ -1,4 +1,3 @@ -use futures::{Stream, StreamExt}; use std::fmt::Write as _; use poise::serenity_prelude as serenity; @@ -11,40 +10,34 @@ use crate::{Context, Error}; // The first parameter of that function is ApplicationContext or Context, and the second parameter // is a &str of the partial input which the user has typed so far. // -// As the return value of autocomplete functions, you can return a Stream, an Iterator, or an -// IntoIterator like Vec and [T; N]. -// -// The returned collection type must be a &str/String (or number, if you're implementing -// autocomplete on a number type). Wrap the type in serenity::AutocompleteChoice to set a custom label -// for each choice which will be displayed in the Discord UI. -// -// Example function return types (assuming non-number parameter -> autocomplete choices are string): -// - `-> impl Stream` -// - `-> Vec` -// - `-> impl Iterator` -// - `-> impl Iterator<&str>` -// - `-> impl Iterator +// As the return value of autocomplete functions, you must return `serenity::CreateAutocompleteResponse`. async fn autocomplete_name<'a>( _ctx: Context<'_>, partial: &'a str, -) -> impl Stream + 'a { - futures::stream::iter(&["Amanda", "Bob", "Christian", "Danny", "Ester", "Falk"]) - .filter(move |name| futures::future::ready(name.starts_with(partial))) - .map(|name| name.to_string()) +) -> serenity::CreateAutocompleteResponse { + let choices = ["Amanda", "Bob", "Christian", "Danny", "Ester", "Falk"] + .into_iter() + .filter(move |name| name.starts_with(partial)) + .map(serenity::AutocompleteChoice::from) + .collect(); + + serenity::CreateAutocompleteResponse::new().set_choices(choices) } async fn autocomplete_number( _ctx: Context<'_>, _partial: &str, -) -> impl Iterator { +) -> serenity::CreateAutocompleteResponse { // Dummy choices - [1_u32, 2, 3, 4, 5].iter().map(|&n| { + let choices = [1_u32, 2, 3, 4, 5].iter().map(|&n| { serenity::AutocompleteChoice::new( format!("{n} (why did discord even give autocomplete choices separate labels)"), n, ) - }) + }); + + serenity::CreateAutocompleteResponse::new().set_choices(choices.collect()) } /// Greet a user. Showcasing autocomplete! diff --git a/examples/invocation_data/main.rs b/examples/invocation_data/main.rs index 21c18e08600f..751e9ab30a39 100644 --- a/examples/invocation_data/main.rs +++ b/examples/invocation_data/main.rs @@ -18,13 +18,13 @@ async fn my_check(ctx: Context<'_>) -> Result { Ok(true) } -async fn my_autocomplete(ctx: Context<'_>, _: &str) -> impl Iterator { +async fn my_autocomplete(ctx: Context<'_>, _: &str) -> serenity::CreateAutocompleteResponse { println!( "In autocomplete: {:?}", ctx.invocation_data::<&str>().await.as_deref() ); - std::iter::empty() + serenity::CreateAutocompleteResponse::new() } /// Test command to ensure that invocation_data works diff --git a/macros/src/command/slash.rs b/macros/src/command/slash.rs index 5b7ab9f33cd5..a380da024dcd 100644 --- a/macros/src/command/slash.rs +++ b/macros/src/command/slash.rs @@ -35,22 +35,7 @@ pub fn generate_parameters(inv: &Invocation) -> Result, partial: &str, - | Box::pin(async move { - use ::poise::futures_util::{Stream, StreamExt}; - - let choices_stream = ::poise::into_stream!( - #autocomplete_fn(ctx.into(), partial).await - ); - let choices_vec = choices_stream - .take(25) - // T or AutocompleteChoice -> AutocompleteChoice - .map(poise::serenity_prelude::AutocompleteChoice::from) - .collect() - .await; - - let mut response = poise::serenity_prelude::CreateAutocompleteResponse::default(); - Ok(response.set_choices(choices_vec)) - })) } + | Box::pin(#autocomplete_fn(ctx.into(), partial))) } } None => quote::quote! { None }, }; diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 45e8082563f6..c8f4c7c1e4cf 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -230,13 +230,17 @@ pub async fn on_error( pub async fn autocomplete_command<'a, U, E>( ctx: crate::Context<'a, U, E>, partial: &'a str, -) -> impl Iterator + 'a { - ctx.framework() - .options() - .commands - .iter() - .filter(move |cmd| cmd.name.starts_with(partial)) - .map(|cmd| cmd.name.to_string()) +) -> serenity::CreateAutocompleteResponse { + let commands = ctx.framework().options.commands.iter(); + let filtered_commands = commands + .filter(|cmd| cmd.name.starts_with(partial)) + .take(25); + + let choices = filtered_commands + .map(|cmd| serenity::AutocompleteChoice::from(&cmd.name)) + .collect(); + + serenity::CreateAutocompleteResponse::new().set_choices(choices) } /// Lists servers of which the bot is a member of, including their member counts, sorted diff --git a/src/dispatch/slash.rs b/src/dispatch/slash.rs index fd63dfe2ea36..7db8e7ceb261 100644 --- a/src/dispatch/slash.rs +++ b/src/dispatch/slash.rs @@ -227,13 +227,7 @@ async fn run_autocomplete( use ::serenity::json::*; // as_str() access via trait for simd-json // Generate an autocomplete response - let autocomplete_response = match autocomplete_callback(ctx, partial_input).await { - Ok(x) => x, - Err(e) => { - tracing::warn!("couldn't generate autocomplete response: {e}"); - return Ok(()); - } - }; + let autocomplete_response = autocomplete_callback(ctx, partial_input).await; // Send the generates autocomplete response if let Err(e) = ctx diff --git a/src/slash_argument/into_stream.rs b/src/slash_argument/into_stream.rs deleted file mode 100644 index 025207714055..000000000000 --- a/src/slash_argument/into_stream.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Small hacky macro to convert any value into a Stream, where the value can be an `IntoIterator` -//! or a Stream. Used for the return value of autocomplete callbacks - -#[doc(hidden)] -pub struct IntoStreamWrap<'a, T>(pub &'a T); - -#[doc(hidden)] -pub trait IntoStream { - type Output; - // Have to return a callback instead of simply taking a parameter because we're moving T in, - // but self still points into it (`cannot move out of _ because it is borrowed`) - fn converter(self) -> fn(T) -> Self::Output; -} - -impl IntoStream for &IntoStreamWrap<'_, T> { - type Output = futures_util::stream::Iter; - fn converter(self) -> fn(T) -> Self::Output { - |iter| futures_util::stream::iter(iter) - } -} - -impl IntoStream for &&IntoStreamWrap<'_, T> { - type Output = T; - fn converter(self) -> fn(T) -> Self::Output { - |stream| stream - } -} - -/// Takes an expression that is either an IntoIterator or a Stream, and converts it to a Stream -#[doc(hidden)] -#[macro_export] -macro_rules! into_stream { - ($e:expr) => { - match $e { - value => { - use $crate::IntoStream as _; - (&&$crate::IntoStreamWrap(&value)).converter()(value) - } - } - }; -} diff --git a/src/slash_argument/mod.rs b/src/slash_argument/mod.rs index 8911883cd2c1..7b4c58ffd3d4 100644 --- a/src/slash_argument/mod.rs +++ b/src/slash_argument/mod.rs @@ -8,6 +8,3 @@ pub use slash_trait::*; mod context_menu; pub use context_menu::*; - -mod into_stream; -pub use into_stream::*; diff --git a/src/structs/slash.rs b/src/structs/slash.rs index 3036c65dc136..ebbe2a7a9b7f 100644 --- a/src/structs/slash.rs +++ b/src/structs/slash.rs @@ -157,10 +157,7 @@ pub struct CommandParameter { for<'a> fn( crate::ApplicationContext<'a, U, E>, &'a str, - ) -> BoxFuture< - 'a, - Result, - >, + ) -> BoxFuture<'a, serenity::CreateAutocompleteResponse>, >, #[doc(hidden)] pub __non_exhaustive: (),