From 71575d12609c75e71021ad6c6146163674ee28fd Mon Sep 17 00:00:00 2001 From: Calli Date: Sun, 7 Jan 2024 16:08:46 +0100 Subject: [PATCH] chore: update serenity (#48) * chore: update serenity * fix: make clippy happy --- Cargo.toml | 2 +- okto_framework/Cargo.toml | 2 +- okto_framework/src/handler.rs | 6 +- okto_framework/src/lib.rs | 59 +-- okto_framework/src/structs.rs | 4 +- src/commands/general.rs | 196 +++++----- src/commands/help.rs | 112 +++--- src/commands/launches.rs | 509 ++++++++++++-------------- src/commands/pictures.rs | 222 +++++------ src/commands/reminders/mod.rs | 95 +++-- src/commands/reminders/pages.rs | 151 ++++---- src/commands/reminders/settings.rs | 36 +- src/events/event_handling.rs | 39 +- src/events/interaction_handler.rs | 130 ++++--- src/events/modal.rs | 92 +++-- src/events/select_menu.rs | 80 ++-- src/events/statefulembed.rs | 157 ++++---- src/events/time_embed.rs | 27 +- src/main.rs | 25 +- src/reminders/change_notifications.rs | 55 +-- src/reminders/filtering.rs | 16 +- src/reminders/reminder_tracking.rs | 50 ++- src/utils/constants.rs | 14 +- src/utils/default_select_menus.rs | 33 +- src/utils/interaction_builder.rs | 119 +++--- src/utils/launches.rs | 18 +- src/utils/other.rs | 41 +-- 27 files changed, 1126 insertions(+), 1164 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70581fb..846c083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ version = "0.1.0" rustflags = ["--cfg", "tokio_unstable"] [dependencies] -serenity = {version="0.11.5", features=["unstable_discord_api"]} +serenity = {version="0.12.0"} lazy_static = "1.4" reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } diff --git a/okto_framework/Cargo.toml b/okto_framework/Cargo.toml index 482db93..c435666 100644 --- a/okto_framework/Cargo.toml +++ b/okto_framework/Cargo.toml @@ -13,6 +13,6 @@ version = "0.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" -serenity = {version="^0.11.6"} +serenity = {version="^0.12.0"} futures = "0.3" paste = "1.0" diff --git a/okto_framework/src/handler.rs b/okto_framework/src/handler.rs index 943e519..90f519c 100644 --- a/okto_framework/src/handler.rs +++ b/okto_framework/src/handler.rs @@ -4,7 +4,7 @@ use serenity::{ client::Context, framework::standard::CommandResult, http::Http, - model::application::interaction::Interaction, + model::application::Interaction, Result, }; @@ -72,7 +72,7 @@ impl Handler { ctx: &Context, interaction: &Interaction, ) -> CommandResult { - if let Interaction::ApplicationCommand(cmd_interaction) = interaction { + if let Interaction::Command(cmd_interaction) = interaction { if let Some(cmd) = self .cmds .get( @@ -102,7 +102,7 @@ impl Handler { )?; http.as_ref() - .create_global_application_commands(&body) + .create_global_commands(&body) .await?; Ok(()) diff --git a/okto_framework/src/lib.rs b/okto_framework/src/lib.rs index afc0944..b54533c 100644 --- a/okto_framework/src/lib.rs +++ b/okto_framework/src/lib.rs @@ -1,29 +1,30 @@ -mod handler; -pub mod structs; - -pub mod macros { - pub use slash_command_macros::command; -} - -pub use handler::Handler; - -#[macro_export] -macro_rules! create_framework { - ($token:expr, $id:expr $(, $c:ident )*) => { - { - okto_framework::paste_expr! { - let mut fr = okto_framework::Handler::new(); - $( - fr.add_command(&[<$c _COMMAND>]).unwrap(); - )* - let mut http = serenity::http::Http::new_with_application_id($token, $id); - fr.upload_commands(&http).await.expect("Can't upload commands"); - fr - } - } - } -} - -#[doc(hidden)] -#[allow(unused_imports)] -pub use paste::expr as paste_expr; +mod handler; +pub mod structs; + +pub mod macros { + pub use slash_command_macros::command; +} + +pub use handler::Handler; + +#[macro_export] +macro_rules! create_framework { + ($token:expr, $id:expr $(, $c:ident )*) => { + { + okto_framework::paste_expr! { + let mut fr = okto_framework::Handler::new(); + $( + fr.add_command(&[<$c _COMMAND>]).unwrap(); + )* + let mut http = serenity::http::Http::new($token); + http.set_application_id($id); + fr.upload_commands(&http).await.expect("Can't upload commands"); + fr + } + } + } +} + +#[doc(hidden)] +#[allow(unused_imports)] +pub use paste::expr as paste_expr; diff --git a/okto_framework/src/structs.rs b/okto_framework/src/structs.rs index 068a3ae..8be26ff 100644 --- a/okto_framework/src/structs.rs +++ b/okto_framework/src/structs.rs @@ -7,7 +7,7 @@ use serenity::{ client::Context, framework::standard::CommandResult, model::{ - application::interaction::application_command::ApplicationCommandInteraction, + application::CommandInteraction, channel::ChannelType, Permissions, }, @@ -22,7 +22,7 @@ pub struct Command { pub type CommandFunc = for<'fut> fn( &'fut Context, - &'fut ApplicationCommandInteraction, + &'fut CommandInteraction, ) -> BoxFuture<'fut, CommandResult>; #[derive(Debug, Clone)] diff --git a/src/commands/general.rs b/src/commands/general.rs index 4024415..4fc7b7c 100644 --- a/src/commands/general.rs +++ b/src/commands/general.rs @@ -14,15 +14,15 @@ use serenity::{ CreateEmbedAuthor, CreateEmbedFooter, CreateInteractionResponse, + CreateInteractionResponseMessage, EditInteractionResponse, }, framework::standard::CommandResult, - model::application::interaction::{ - application_command::ApplicationCommandInteraction, - InteractionResponseType, + model::{ + application::CommandInteraction, + Colour, }, prelude::Context, - utils::Colour, }; use crate::{ @@ -32,16 +32,15 @@ use crate::{ #[command] /// Get the ping of the bot -async fn ping(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn ping(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let start = Utc::now(); interaction - .create_interaction_response( + .create_response( &ctx, - |c: &mut CreateInteractionResponse| { - c.interaction_response_data(|d| { - d.embed(|e: &mut CreateEmbed| e.description("\u{1f3d3} pong...")) - }) - }, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .embed(CreateEmbed::new().description("\u{1f3d3} pong...")), + ), ) .await?; let end = Utc::now(); @@ -53,18 +52,17 @@ async fn ping(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com .created_at(); interaction - .edit_original_interaction_response( + .edit_response( ctx, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.title("Pong!") - .description(format!( - "\u{1f3d3}\nws delay: {}ms\napi ping: {}ms", - ws_delay.num_milliseconds(), - round_trip.num_milliseconds() - )) - }) - }, + EditInteractionResponse::new().embed( + CreateEmbed::new() + .title("Pong!") + .description(format!( + "\u{1f3d3}\nws delay: {}ms\napi ping: {}ms", + ws_delay.num_milliseconds(), + round_trip.num_milliseconds() + )), + ), ) .await?; @@ -73,14 +71,13 @@ async fn ping(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com #[command] /// Get some general information about the bot -async fn info(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn info(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let user_id = ctx .cache .current_user() .id; - interaction.create_interaction_response(&ctx.http, |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| {c.embed(|e: &mut CreateEmbed| { - e.title("OKTO") + interaction.create_response(&ctx.http, CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().embed(CreateEmbed::new() + .title("OKTO") .description( format!( "This is a bot to show upcoming launches and provide additional information on everything to do with spaceflight\n\ @@ -100,27 +97,26 @@ async fn info(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com ctx.cache.guild_count(), user_id ) ) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Bot Information") + .author(CreateEmbedAuthor::new("Bot Information") .icon_url(DEFAULT_ICON) - }) + ) .thumbnail(DEFAULT_ICON) .color(DEFAULT_COLOR) - })}) - }).await?; + )) + ).await?; Ok(()) } #[command] /// Have some helpful websites -async fn websites(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn websites(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.embed(|e: &mut CreateEmbed| { - e.field( + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new().embed( + CreateEmbed::new() + .field( "General launch information:", "**Spaceflight Insider:** http://www.spaceflightinsider.com/ **Rocket Watch:** https://rocket.watch/ @@ -139,14 +135,13 @@ async fn websites(ctx: &Context, interaction: &ApplicationCommandInteraction) -> **NASA:** https://www.nasa.gov/", false, ) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Some websites with more information") - .icon_url(DEFAULT_ICON) - }) - .color(DEFAULT_COLOR) - }) - }) - }, + .author( + CreateEmbedAuthor::new("Some websites with more information") + .icon_url(DEFAULT_ICON), + ) + .color(DEFAULT_COLOR), + ), + ), ) .await?; Ok(()) @@ -166,10 +161,7 @@ struct PeopleInSpaceResp { #[command] /// Get a list of all humans currently in space -async fn peopleinspace( - ctx: &Context, - interaction: &ApplicationCommandInteraction, -) -> CommandResult { +async fn peopleinspace(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let pis: PeopleInSpaceResp = DEFAULT_CLIENT .get("http://api.open-notify.org/astros.json") .send() @@ -190,12 +182,12 @@ async fn peopleinspace( } interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.embed(|e: &mut CreateEmbed| { - e.title(format!( + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new().embed( + CreateEmbed::new() + .title(format!( "There are currently {} people in space", pis.number )) @@ -205,15 +197,11 @@ async fn peopleinspace( .map(std::string::String::as_str) .collect::(), ) - .author(|a: &mut CreateEmbedAuthor| { - a.name("People in space") - .icon_url(DEFAULT_ICON) - }) + .author(CreateEmbedAuthor::new("People in space").icon_url(DEFAULT_ICON)) .timestamp(Utc::now()) - .color(DEFAULT_COLOR) - }) - }) - }, + .color(DEFAULT_COLOR), + ), + ), ) .await?; @@ -230,11 +218,12 @@ struct ISSLocation { #[command] /// Get the current location of the ISS -async fn iss(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn iss(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let iss_pos: ISSLocation = DEFAULT_CLIENT @@ -271,20 +260,18 @@ async fn iss(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Comm GOOGLE_KEY.as_str() ); - interaction.edit_original_interaction_response(&ctx.http, |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.description(format!( + interaction.edit_response(&ctx.http, EditInteractionResponse::new().embed(CreateEmbed::new().description(format!( "**Latitude:** {0:.5}\n**Longitude:** {1:.5}\n**Altitude:** {2:.3}km\n**Velocity:** {3:.3}km/h", iss_pos.latitude, iss_pos.longitude, iss_pos.altitude, iss_pos.velocity )) - .author(|a: &mut CreateEmbedAuthor| a.name("Position ISS").icon_url(DEFAULT_ICON)) + .author(CreateEmbedAuthor::new("Position ISS").icon_url(DEFAULT_ICON)) .image(detail_url) .thumbnail(global_url) - .footer(|f: &mut CreateEmbedFooter| f.text("source: wheretheiss.at")) + .footer(CreateEmbedFooter::new("source: wheretheiss.at")) .timestamp(Utc::now()) .color(DEFAULT_COLOR) - }) - }) + ) + ) .await?; Ok(()) @@ -306,11 +293,12 @@ async fn iss(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Comm } )] /// Get information about an exoplanet or star -async fn exoplanet(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn exoplanet(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let planet_name: Option = interaction @@ -320,10 +308,7 @@ async fn exoplanet(ctx: &Context, interaction: &ApplicationCommandInteraction) - .find(|o| o.name == "exoplanet") .and_then(|o| { o.value - .clone() - }) - .and_then(|v| { - v.as_str() + .as_str() .map(ToOwned::to_owned) }); let star_name = interaction @@ -333,10 +318,7 @@ async fn exoplanet(ctx: &Context, interaction: &ApplicationCommandInteraction) - .find(|o| o.name == "star") .and_then(|o| { o.value - .clone() - }) - .and_then(|v| { - v.as_str() + .as_str() .map(ToOwned::to_owned) }); @@ -384,9 +366,7 @@ async fn exoplanet(ctx: &Context, interaction: &ApplicationCommandInteraction) - .await? }, Some(_) if planet_name.is_some() || star_name.is_some() => { - interaction.edit_original_interaction_response(&ctx.http, |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.description( + interaction.edit_response(&ctx.http, EditInteractionResponse::new().embed(CreateEmbed::new().description( "The name you gave isn't in the NASA Exoplanet Archive <:kia:367734893796655113> Please understand that NASA has a 'weird' way of naming the stars in their archive Here is a link to the list of all the stars in the archive: \ @@ -394,8 +374,8 @@ async fn exoplanet(ctx: &Context, interaction: &ApplicationCommandInteraction) - ) .title("planet/star not found!") .color(Colour::RED) - }) - }).await?; + ) + ).await?; return Ok(()); }, Some(p) => { @@ -461,7 +441,7 @@ impl StarInfo { async fn get_star( ctx: &Context, - interaction: &ApplicationCommandInteraction, + interaction: &CommandInteraction, star_name: &str, ) -> CommandResult { let mut params = HashMap::new(); @@ -495,14 +475,11 @@ async fn get_star( let star = &res[0]; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Star Information") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author(CreateEmbedAuthor::new("Star Information").icon_url(DEFAULT_ICON)) .color(DEFAULT_COLOR) .title(star_name) .timestamp(Utc::now()) @@ -551,9 +528,8 @@ async fn get_star( ), ), false, - ) - }) - }, + ), + ), ) .await?; @@ -582,7 +558,7 @@ struct PlanetInfo { async fn get_planet( ctx: &Context, - interaction: &ApplicationCommandInteraction, + interaction: &CommandInteraction, planet_name: &str, ) -> CommandResult { let mut params = HashMap::new(); @@ -606,14 +582,11 @@ async fn get_planet( .ok_or("No planet like this found")?; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Planet Information") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author(CreateEmbedAuthor::new("Planet Information").icon_url(DEFAULT_ICON)) .color(DEFAULT_COLOR) .title(planet_name) .timestamp(Utc::now()) @@ -735,9 +708,8 @@ async fn get_planet( .unwrap_or_else(|| "unknown".to_owned()), ), false, - ) - }) - }, + ), + ), ) .await?; diff --git a/src/commands/help.rs b/src/commands/help.rs index 8888b76..bfc5223 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -26,8 +26,8 @@ use serenity::{ }, model::{ application::{ - component::ButtonStyle, - interaction::application_command::ApplicationCommandInteraction, + ButtonStyle, + CommandInteraction, }, prelude::{ Channel, @@ -78,7 +78,7 @@ use crate::{ } )] /// Get information about the commands within the bot -async fn help(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn help(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let ses = EmbedSession::new(ctx, interaction.clone(), false).await?; if let Some(command_name) = interaction @@ -88,10 +88,7 @@ async fn help(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com .find(|o| o.name == "command") .and_then(|o| { o.value - .clone() - }) - .and_then(|v| { - v.as_str() + .as_str() .map(ToOwned::to_owned) }) { @@ -112,33 +109,33 @@ async fn help(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com return Ok(()); }; + let args = command + .options + .options + .iter() + .fold(String::new(), |acc, opt| { + let name = if opt.required { + format!("<{}> ", opt.name) + } else { + format!("[{}] ", opt.name) + }; + acc + &name + }); + interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |i: &mut EditInteractionResponse| { - let args = command - .options - .options - .iter() - .fold(String::new(), |acc, opt| { - let name = if opt.required { - format!("<{}> ", opt.name) - } else { - format!("[{}] ", opt.name) - }; - acc + &name - }); - - i.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name(format!( + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new(format!( "Help /{}", command .options .name )) - .icon_url(DEFAULT_ICON) - }) + .icon_url(DEFAULT_ICON), + ) .color(DEFAULT_COLOR) .description(format!( "**Description:** {}{}", @@ -150,9 +147,8 @@ async fn help(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com } else { format!("\n**Arguments:** {args}") } - )) - }) - }, + )), + ), ) .await?; return Ok(()); @@ -166,15 +162,14 @@ async fn help(ctx: &Context, interaction: &ApplicationCommandInteraction) -> Com fn help_menu( ses: Arc>, ctx: Context, - interaction: ApplicationCommandInteraction, + interaction: CommandInteraction, ) -> futures::future::BoxFuture<'static, ()> { Box::pin(async move { - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed(ses.clone(), CreateEmbed::new().color(DEFAULT_COLOR) .author( - |a: &mut CreateEmbedAuthor| a.name("Help Menu").icon_url(DEFAULT_ICON) + CreateEmbedAuthor::new("Help Menu").icon_url(DEFAULT_ICON) ).description("Use the buttons to get the descriptions for the commands in that group.\nCurrently available commands:") - }); + ); let grouped = ctx .data @@ -198,7 +193,8 @@ fn help_menu( |mut acc, (k, g)| { acc.push(( k, - g.into_iter().collect(), + g.into_iter() + .collect(), )); acc }, @@ -249,7 +245,7 @@ fn help_menu( let details_ses = ses.clone(); let details_ctx = ctx.clone(); let details_interaction = interaction.clone(); - em.add_field( + em = em.add_field( &group_name.clone(), &cmds, true, @@ -293,7 +289,7 @@ fn help_menu( .await; let _ = lock .interaction - .delete_original_interaction_response(&lock.http) + .delete_response(&lock.http) .await; }) }, @@ -311,19 +307,21 @@ fn help_menu( fn command_details( ses: Arc>, ctx: Context, - interaction: ApplicationCommandInteraction, + interaction: CommandInteraction, selected_group: Vec<&'static Command>, group_name: String, ) -> futures::future::BoxFuture<'static, ()> { Box::pin(async move { - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.name(format!("{} Commands", &group_name)) - .icon_url(DEFAULT_ICON) - }) - .description("More Detailed information about the commands in this group") - }); + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) + .author( + CreateEmbedAuthor::new(format!("{} Commands", &group_name)) + .icon_url(DEFAULT_ICON), + ) + .description("More Detailed information about the commands in this group"), + ); for command in &selected_group { if allowed(&ctx, &[command], &interaction) @@ -343,7 +341,8 @@ fn command_details( acc + &name }); - em.inner + em.inner = em + .inner .field( format!( "/{}", @@ -367,7 +366,7 @@ fn command_details( } } - em.add_field( + em = em.add_field( "Back", "Back to help menu", false, @@ -396,7 +395,7 @@ fn command_details( async fn allowed( ctx: &Context, cmds: &[&'static Command], - interaction: &ApplicationCommandInteraction, + interaction: &CommandInteraction, ) -> Result { if OWNERS.contains( &interaction @@ -439,13 +438,10 @@ async fn allowed( return Ok(false); }; - if let Ok(perms) = guild.user_permissions_in(channel, member) { - if !(perms.contains(Permissions::ADMINISTRATOR) - || perms.contains(Permissions::MANAGE_GUILD)) - { - return Ok(false); - } - } else { + let perms = guild.user_permissions_in(channel, member); + if !(perms.contains(Permissions::ADMINISTRATOR) + || perms.contains(Permissions::MANAGE_GUILD)) + { return Ok(false); } } else { @@ -480,7 +476,7 @@ pub async fn calc_prefix(ctx: &Context, msg: &Message) -> String { let res = db .collection::("general_settings") .find_one( - doc! { "guild": msg.guild_id.unwrap().0 as i64 }, + doc! { "guild": msg.guild_id.unwrap().get() as i64 }, None, ) .await; diff --git a/src/commands/launches.rs b/src/commands/launches.rs index dea8e24..e8c547a 100644 --- a/src/commands/launches.rs +++ b/src/commands/launches.rs @@ -4,20 +4,19 @@ use chrono::Utc; use itertools::Itertools; use okto_framework::macros::command; use serenity::{ + all::InteractionResponseFlags, builder::{ CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, CreateInteractionResponse, + CreateInteractionResponseMessage, }, framework::standard::CommandResult, model::{ application::{ - component::ButtonStyle, - interaction::{ - application_command::ApplicationCommandInteraction, - MessageFlags, - }, + ButtonStyle, + CommandInteraction, }, channel::ReactionType, id::EmojiId, @@ -68,7 +67,7 @@ use crate::{ required: false } )] -async fn nextlaunch(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn nextlaunch(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let mut launches: Vec = { if let Some(launch_cache) = ctx .data @@ -90,20 +89,16 @@ async fn nextlaunch(ctx: &Context, interaction: &ApplicationCommandInteraction) if launches.is_empty() { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.flags(MessageFlags::EPHEMERAL) - .embed(|e: &mut CreateEmbed| { - default_embed( - e, - "I found no upcoming launches that have been marked as certain :(", - false, - ) - }) - }) - }, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .flags(InteractionResponseFlags::EPHEMERAL) + .embed(default_embed( + "I found no upcoming launches that have been marked as certain :(", + false, + )), + ), ) .await?; return Ok(()); @@ -113,13 +108,10 @@ async fn nextlaunch(ctx: &Context, interaction: &ApplicationCommandInteraction) Ok(ls) => ls, Err(err) => { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.flags(MessageFlags::EPHEMERAL) - .embed(|e: &mut CreateEmbed| default_embed( - e, + CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().flags(InteractionResponseFlags::EPHEMERAL) + .embed(default_embed( &if let FilterErrorType::Invalid = err { "This is not a valid filter, please take a look at those listed in `/filtersinfo`".to_owned() } else { @@ -127,8 +119,8 @@ async fn nextlaunch(ctx: &Context, interaction: &ApplicationCommandInteraction) }, false )) - }) - }, + ) + , ) .await?; return Ok(()); @@ -137,72 +129,64 @@ async fn nextlaunch(ctx: &Context, interaction: &ApplicationCommandInteraction) let launch = &launches[0]; + let mut window = format_duration(launch.launch_window, true); + if window.is_empty() { + window.push_str("instantaneous") + } + + let mut em = CreateEmbed::new() + .color(DEFAULT_COLOR) + .author(CreateEmbedAuthor::new("Next Launch").icon_url(DEFAULT_ICON)) + .timestamp( + Timestamp::from_unix_timestamp( + launch + .net + .timestamp(), + ) + .expect("Invalid timestamp"), + ) + .title(format!( + "{}\nStatus: {}", + &launch.vehicle, + launch + .status + .as_str() + )) + .description(format!( + "**Payload:** {}\n\ +**NET:** \n\ +**Provider:** {}\n\ +**Location:** {}\n\ +**Launch Window:** {}", + &launch.payload, + launch + .net + .timestamp(), + &launch.lsp, + &launch.location, + window + )) + .field( + "Time until launch:", + format_duration( + launch.net - Utc::now().naive_utc(), + true, + ), + false, + ); + + if let Some(img) = &launch.rocket_img { + em = em.thumbnail(img); + } + + if let Some(links) = format_links(&launch.vid_urls) { + em = em.field("links", links, false); + } + interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.embed(|e: &mut CreateEmbed| { - let mut window = format_duration(launch.launch_window, true); - if window.is_empty() { - window.push_str("instantaneous") - } - - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Next Launch") - .icon_url(DEFAULT_ICON) - }) - .timestamp( - Timestamp::from_unix_timestamp( - launch - .net - .timestamp(), - ) - .expect("Invalid timestamp"), - ) - .title(format!( - "{}\nStatus: {}", - &launch.vehicle, - launch - .status - .as_str() - )) - .description(format!( - "**Payload:** {}\n\ - **NET:** \n\ - **Provider:** {}\n\ - **Location:** {}\n\ - **Launch Window:** {}", - &launch.payload, - launch - .net - .timestamp(), - &launch.lsp, - &launch.location, - window - )) - .field( - "Time until launch:", - format_duration( - launch.net - Utc::now().naive_utc(), - true, - ), - false, - ); - - if let Some(img) = &launch.rocket_img { - e.thumbnail(img); - } - - if let Some(links) = format_links(&launch.vid_urls) { - e.field("links", links, false); - } - - e - }) - }) - }, + CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().embed(em)), ) .await?; @@ -234,56 +218,36 @@ fn list_page( launches.len() }; - let mut em = StatefulEmbed::new_with( + let mut em = StatefulEmbed::new_with_embed( session.clone(), - |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.icon_url(DEFAULT_ICON) - .name("List of upcoming launches") - }) + CreateEmbed::new().color(DEFAULT_COLOR) + .author(CreateEmbedAuthor::new("List of upcoming launches").icon_url(DEFAULT_ICON)) .timestamp(Utc::now()) - .footer(|f: &mut CreateEmbedFooter| { - f.text(format!("Source: {LAUNCH_LIBRARY_URL}",)) - }); - - if all { - e.description(" + .footer(CreateEmbedFooter::new(format!("Source: {LAUNCH_LIBRARY_URL}",)) + ).description(if all {" This list shows the upcoming launches (max 100), both certain and uncertain.\n\ Use the arrow reactions to get to other pages and the green reaction to filter on only the launches that are certain. - "); - } else { - e.description(" + "} else {" This list shows upcoming launches that are certain.\n\ Use the arrow reactions to get to other pages and the red reaction to get all the launches. - "); - } - - #[allow(clippy::needless_range_loop)] - for launch in &launches[min..top] { - e.field( - format!( - "{}: {} - {}", - launch.id, - &launch.vehicle, - launch - .status - .as_str() - ), - format!( - "**Payload:** {}\n**NET:** \n**Provider:** {}\n**Location:** {}", - &launch.payload, - launch - .net - .timestamp(), - &launch.lsp, - &launch.location - ), - false, - ); - } - e - }, + "}).fields(launches[min..top].iter().map(|launch| (format!( + "{}: {} - {}", + launch.id, + &launch.vehicle, + launch + .status + .as_str() + ), + format!( + "**Payload:** {}\n**NET:** \n**Provider:** {}\n**Location:** {}", + &launch.payload, + launch + .net + .timestamp(), + &launch.lsp, + &launch.location + ), + false,))) ); if page_num > 0 { @@ -440,7 +404,7 @@ fn list_page( .await; let _ = lock .interaction - .delete_original_interaction_response(&lock.http) + .delete_response(&lock.http) .await; }) }, @@ -469,7 +433,7 @@ fn list_page( description: "Rocket name to filter the launches on" } )] -async fn listlaunches(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn listlaunches(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let mut launches: Vec = { if let Some(launch_cache) = ctx .data @@ -494,13 +458,10 @@ async fn listlaunches(ctx: &Context, interaction: &ApplicationCommandInteraction Ok(ls) => ls, Err(err) => { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.flags(MessageFlags::EPHEMERAL) - .embed(|e: &mut CreateEmbed| default_embed( - e, + CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().flags(InteractionResponseFlags::EPHEMERAL) + .embed(default_embed( &if let FilterErrorType::Invalid = err { "This is not a valid filter, please take a look at those listed in `/filtersinfo`".to_owned() } else { @@ -508,8 +469,8 @@ async fn listlaunches(ctx: &Context, interaction: &ApplicationCommandInteraction }, false )) - }) - }, + ) + , ) .await?; return Ok(()); @@ -531,7 +492,7 @@ async fn listlaunches(ctx: &Context, interaction: &ApplicationCommandInteraction description: "The number of the launch to get more information about", required: true, })] -async fn launchinfo(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn launchinfo(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let launches: Vec = { if let Some(launch_cache) = ctx .data @@ -559,10 +520,7 @@ async fn launchinfo(ctx: &Context, interaction: &ApplicationCommandInteraction) .find(|o| o.name == "launch") .and_then(|o| { o.value - .clone() - }) - .and_then(|v| { - v.as_i64() + .as_i64() .map(|i| { let o: i32 = i .try_into() @@ -577,103 +535,125 @@ async fn launchinfo(ctx: &Context, interaction: &ApplicationCommandInteraction) .find(|l| l.id == launch_id) else { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.flags(MessageFlags::EPHEMERAL) - .embed(|e: &mut CreateEmbed| { - default_embed( - e, - "No launch was found with that ID :(", - false, - ) - }) - }) - }, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .flags(InteractionResponseFlags::EPHEMERAL) + .embed(default_embed( + "No launch was found with that ID :(", + false, + )), + ), ) .await?; return Ok(()); }; + let mut window = format_duration(launch.launch_window, true); + if window.is_empty() { + window.push_str("instantaneous") + } - interaction.create_interaction_response(&ctx.http, |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| {c.embed(|e: &mut CreateEmbed| { - let mut window = format_duration(launch.launch_window, true); - if window.is_empty() { - window.push_str("instantaneous") - } - - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Detailed info").icon_url(DEFAULT_ICON) - }) - .timestamp(Timestamp::from_unix_timestamp(launch - .net.timestamp() - ).expect("Invalid timestamp")) - .title(format!( - "{}\nStatus: {}", - &launch.vehicle, - launch.status.as_str() - )) - .field("NET:", format!("", launch.net.timestamp()), false) - .field( - "General information", - format!( - "**Payload:** {}\n\ + let mut em = CreateEmbed::new() + .color(DEFAULT_COLOR) + .author(CreateEmbedAuthor::new("Detailed info").icon_url(DEFAULT_ICON)) + .timestamp( + Timestamp::from_unix_timestamp( + launch + .net + .timestamp(), + ) + .expect("Invalid timestamp"), + ) + .title(format!( + "{}\nStatus: {}", + &launch.vehicle, + launch + .status + .as_str() + )) + .field( + "NET:", + format!( + "", + launch + .net + .timestamp() + ), + false, + ) + .field( + "General information", + format!( + "**Payload:** {}\n\ **Provider:** {}\n\ **Location:** {}\n\ **Launch Window:** {}", - &launch.payload, - &launch.lsp, - &launch.location, - window - ), - false, - ); - - if launch.net > Utc::now().naive_utc() { - e.field( - "Time until launch:", - format_duration(launch.net - Utc::now().naive_utc(), true), - false, - ); - } - - let description = if launch.mission_description.len() > 1024 { - format!("{} ...\nlength is too long for discord", cutoff_on_last_dot(&launch.mission_description, 980)) - } else { - launch.mission_description.clone() - }; - - e.field("Desciption:", description, false); - - if let Some(img) = &launch.rocket_img { - e.thumbnail(img); - } - - if let Some(links) = format_links(&launch.vid_urls) { - e.field("vids", links, false); - } - - e.field( + &launch.payload, &launch.lsp, &launch.location, window + ), + false, + ); + + if launch.net > Utc::now().naive_utc() { + em = em.field( + "Time until launch:", + format_duration( + launch.net - Utc::now().naive_utc(), + true, + ), + false, + ); + } + + let description = if launch + .mission_description + .len() + > 1024 + { + format!( + "{} ...\nlength is too long for discord", + cutoff_on_last_dot(&launch.mission_description, 980) + ) + } else { + launch + .mission_description + .clone() + }; + + em = em.field("Desciption:", description, false); + + if let Some(img) = &launch.rocket_img { + em = em.thumbnail(img); + } + + if let Some(links) = format_links(&launch.vid_urls) { + em = em.field("vids", links, false); + } + + em = em.field( "links", - &format!( + format!( "**My Source:** [The Space Devs]({0})\n\ **Rocket Watch:** [rocket.watch](https://rocket.watch/#id={1})\n\ **Go4Liftoff:** [go4liftoff.com](https://go4liftoff.com/#page=singleLaunch?filters=launchID={1})", LAUNCH_LIBRARY_URL, launch.id, ), false, - ) - })}) - }).await?; + ); + + interaction + .create_response( + &ctx.http, + CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().embed(em)), + ) + .await?; Ok(()) } #[command] /// Get a list of all things you can filter launches on -async fn filtersinfo(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn filtersinfo(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let sorted_agencies = LAUNCH_AGENCIES .iter() .sorted() @@ -682,48 +662,45 @@ async fn filtersinfo(ctx: &Context, interaction: &ApplicationCommandInteraction) let half_agency_count = sorted_agencies.len() / 2; interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.embed(|e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Filters Info") - .icon_url(DEFAULT_ICON) - }) - .timestamp(Utc::now()) - .title("The following filters can be used to filter launches:") - .field( - "Vehicles:", - LAUNCH_VEHICLES - .keys() - .sorted() - .join(", "), - false, - ) - .field( - "Launch Service Provider abbreviations with their full names (part 1):", - sorted_agencies - .iter() - .take(half_agency_count) - .map(|(k, v)| format!("**{k}**: {v}")) - .collect::>() - .join("\n"), - false, - ).field( - "Launch Service Provider abbreviations with their full names (part 2):", - sorted_agencies - .iter() - .skip(half_agency_count) - .map(|(k, v)| format!("**{k}**: {v}")) - .collect::>() - .join("\n"), - false, - ) - }) - }) - }, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new().embed( + CreateEmbed::new() + .color(DEFAULT_COLOR) + .author(CreateEmbedAuthor::new("Filters Info").icon_url(DEFAULT_ICON)) + .timestamp(Utc::now()) + .title("The following filters can be used to filter launches:") + .field( + "Vehicles:", + LAUNCH_VEHICLES + .keys() + .sorted() + .join(", "), + false, + ) + .field( + "Launch Service Provider abbreviations with their full names (part 1):", + sorted_agencies + .iter() + .take(half_agency_count) + .map(|(k, v)| format!("**{k}**: {v}")) + .collect::>() + .join("\n"), + false, + ) + .field( + "Launch Service Provider abbreviations with their full names (part 2):", + sorted_agencies + .iter() + .skip(half_agency_count) + .map(|(k, v)| format!("**{k}**: {v}")) + .collect::>() + .join("\n"), + false, + ), + ), + ), ) .await?; diff --git a/src/commands/pictures.rs b/src/commands/pictures.rs index 1fe71ca..7beaa08 100644 --- a/src/commands/pictures.rs +++ b/src/commands/pictures.rs @@ -16,13 +16,12 @@ use serenity::{ CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, + CreateInteractionResponse, + CreateInteractionResponseMessage, EditInteractionResponse, }, framework::standard::CommandResult, - model::application::interaction::{ - application_command::ApplicationCommandInteraction, - InteractionResponseType, - }, + model::application::CommandInteraction, prelude::Context, }; @@ -56,11 +55,12 @@ use crate::{ ] } )] -async fn earthpic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn earthpic(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let image_type = interaction .data @@ -69,10 +69,7 @@ async fn earthpic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> .find(|o| o.name == "image-version") .and_then(|o| { o.value - .clone() - }) - .and_then(|v| { - v.as_str() + .as_str() .map(ToOwned::to_owned) }) .unwrap_or_else(|| "natural".to_owned()); @@ -95,24 +92,20 @@ async fn earthpic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> .ok_or("No image received from the EPIC image api")?; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Earth Picture") + EditInteractionResponse::new().embed(CreateEmbed::new().author(CreateEmbedAuthor::new("Earth Picture") .icon_url(DEFAULT_ICON) - }) + ) .color(DEFAULT_COLOR) .description(format!( "Most recent {image_type} image from the EPIC camera onboard the NOAA DSCOVR spacecraft" )) - .footer(|f: &mut CreateEmbedFooter| { - f.text(format!( + .footer(CreateEmbedFooter::new(format!( "Taken on: {}\nRun this command again with the {} argument!", epic_image_data.date, opposite )) - }) + ) .image(format!( "https://epic.gsfc.nasa.gov/archive/{}/{}/png/{}.png", image_type, @@ -120,8 +113,7 @@ async fn earthpic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> epic_image_data.image )) .timestamp(Utc::now()) - }) - }, + ) ) .await?; @@ -138,11 +130,12 @@ async fn earthpic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> required: false, } )] -async fn spacepic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn spacepic(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let now = Utc::now() - Duration::hours(6); @@ -154,9 +147,8 @@ async fn spacepic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> .find(|o| o.name == "today") .and_then(|o| { o.value - .clone() + .as_bool() }) - .and_then(|v| v.as_bool()) .unwrap_or(false) { now @@ -192,13 +184,11 @@ async fn spacepic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> if let Err(err) = apod_image_req { interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - default_embed(e, "The APOD API returned an error so I couldn't get an image :c\nUnfortunately this happens quite often as that api is pretty unstable.", false) - }) - } + EditInteractionResponse::new().embed( + default_embed("The APOD API returned an error so I couldn't get an image :c\nUnfortunately this happens quite often as that api is pretty unstable.", false) + ) ).await?; error_log( @@ -234,27 +224,23 @@ async fn spacepic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> ); interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Astronomy Picture of Today") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new("Astronomy Picture of Today").icon_url(DEFAULT_ICON), + ) .title(&apod_image.title) .color(DEFAULT_COLOR) .description(explanation) - .footer(|f: &mut CreateEmbedFooter| { - f.text(format!( - "APOD of {}", - date.format("%Y-%m-%d") - )) - }) + .footer(CreateEmbedFooter::new(format!( + "APOD of {}", + date.format("%Y-%m-%d") + ))) .image(apod_image.url) - .timestamp(Utc::now()) - }) - }, + .timestamp(Utc::now()), + ), ) .await?; @@ -264,24 +250,25 @@ async fn spacepic(ctx: &Context, interaction: &ApplicationCommandInteraction) -> #[command] /// Picks a random sol number and then grabs a random picture made by the Spirit /// rover on that sol -async fn spirit(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn spirit(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let (pic, sol) = fetch_rover_camera_picture("spirit", 1..2186).await; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Random Picture made by the Spirit mars rover") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new("Random Picture made by the Spirit mars rover") + .icon_url(DEFAULT_ICON), + ) .color(DEFAULT_COLOR) .description(format!( "**Taken on Sol:** {}\n**Earth Date:** {}\n**Taken by Camera:** {}", @@ -290,11 +277,13 @@ async fn spirit(ctx: &Context, interaction: &ApplicationCommandInteraction) -> C pic.camera .full_name )) - .footer(|f: &mut CreateEmbedFooter| f.text(format!("picture ID: {}", pic.id))) + .footer(CreateEmbedFooter::new(format!( + "picture ID: {}", + pic.id + ))) .image(pic.img_src) - .timestamp(Utc::now()) - }) - }, + .timestamp(Utc::now()), + ), ) .await?; @@ -304,24 +293,25 @@ async fn spirit(ctx: &Context, interaction: &ApplicationCommandInteraction) -> C #[command] /// Picks a random sol number and then grabs a random picture made by the /// Opportunity rover on that sol -async fn opportunity(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn opportunity(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let (pic, sol) = fetch_rover_camera_picture("opportunity", 1..5112).await; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Random Picture made by the Opportunity mars rover") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new("Random Picture made by the Opportunity mars rover") + .icon_url(DEFAULT_ICON), + ) .color(DEFAULT_COLOR) .description(format!( "**Taken on Sol:** {}\n**Earth Date:** {}\n**Taken by Camera:** {}", @@ -330,11 +320,13 @@ async fn opportunity(ctx: &Context, interaction: &ApplicationCommandInteraction) pic.camera .full_name )) - .footer(|f: &mut CreateEmbedFooter| f.text(format!("picture ID: {}", pic.id))) + .footer(CreateEmbedFooter::new(format!( + "picture ID: {}", + pic.id + ))) .image(pic.img_src) - .timestamp(Utc::now()) - }) - }, + .timestamp(Utc::now()), + ), ) .await?; @@ -344,11 +336,12 @@ async fn opportunity(ctx: &Context, interaction: &ApplicationCommandInteraction) #[command] /// Picks a random sol number and grabs a random picture made by the Curiosity /// rover on that sol -async fn curiosity(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn curiosity(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let max_sol = get_max_sol("curiosity").await?; @@ -356,14 +349,14 @@ async fn curiosity(ctx: &Context, interaction: &ApplicationCommandInteraction) - let (pic, sol) = fetch_rover_camera_picture("curiosity", 1..max_sol).await; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Random Picture made by the Curiosity mars rover") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new("Random Picture made by the Curiosity mars rover") + .icon_url(DEFAULT_ICON), + ) .color(DEFAULT_COLOR) .description(format!( "**Taken on Sol:** {}\n**Earth Date:** {}\n**Taken by Camera:** {}", @@ -372,11 +365,13 @@ async fn curiosity(ctx: &Context, interaction: &ApplicationCommandInteraction) - pic.camera .full_name )) - .footer(|f: &mut CreateEmbedFooter| f.text(format!("picture ID: {}", pic.id))) + .footer(CreateEmbedFooter::new(format!( + "picture ID: {}", + pic.id + ))) .image(pic.img_src) - .timestamp(Utc::now()) - }) - }, + .timestamp(Utc::now()), + ), ) .await?; @@ -386,11 +381,12 @@ async fn curiosity(ctx: &Context, interaction: &ApplicationCommandInteraction) - #[command] /// Picks a random sol number and grabs a random picture made by the /// Perseverance rover on that sol. -async fn perseverance(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn perseverance(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) .await?; let max_sol = get_max_sol("perseverance").await?; @@ -398,14 +394,16 @@ async fn perseverance(ctx: &Context, interaction: &ApplicationCommandInteraction let (pic, sol) = fetch_rover_camera_picture("perseverance", 1..max_sol).await; interaction - .edit_original_interaction_response( + .edit_response( &ctx.http, - |e: &mut EditInteractionResponse| { - e.embed(|e: &mut CreateEmbed| { - e.author(|a: &mut CreateEmbedAuthor| { - a.name("Random Picture made by the Perseverance mars rover") - .icon_url(DEFAULT_ICON) - }) + EditInteractionResponse::new().embed( + CreateEmbed::new() + .author( + CreateEmbedAuthor::new( + "Random Picture made by the Perseverance mars rover", + ) + .icon_url(DEFAULT_ICON), + ) .color(DEFAULT_COLOR) .description(format!( "**Taken on Sol:** {}\n**Earth Date:** {}\n**Taken by Camera:** {}", @@ -414,11 +412,13 @@ async fn perseverance(ctx: &Context, interaction: &ApplicationCommandInteraction pic.camera .full_name )) - .footer(|f: &mut CreateEmbedFooter| f.text(format!("picture ID: {}", pic.id))) + .footer(CreateEmbedFooter::new(format!( + "picture ID: {}", + pic.id + ))) .image(pic.img_src) - .timestamp(Utc::now()) - }) - }, + .timestamp(Utc::now()), + ), ) .await?; diff --git a/src/commands/reminders/mod.rs b/src/commands/reminders/mod.rs index 919666d..2193cc2 100644 --- a/src/commands/reminders/mod.rs +++ b/src/commands/reminders/mod.rs @@ -12,21 +12,17 @@ use pages::{ reminders_page, }; use serenity::{ + all::InteractionResponseFlags, builder::{ CreateEmbed, CreateEmbedAuthor, CreateInteractionResponse, + CreateInteractionResponseMessage, }, framework::standard::CommandResult, model::application::{ - component::ButtonStyle, - interaction::{ - application_command::{ - ApplicationCommandInteraction, - CommandDataOptionValue, - }, - MessageFlags, - }, + ButtonStyle, + CommandInteraction, }, prelude::{ Context, @@ -58,29 +54,22 @@ use crate::{ } )] /// Manage the reminders and notifications posted by the bot in this server -async fn notifychannel( - ctx: &Context, - interaction: &ApplicationCommandInteraction, -) -> CommandResult { +async fn notifychannel(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { if interaction .guild_id .is_none() { interaction - .create_interaction_response( + .create_response( &ctx.http, - |m: &mut CreateInteractionResponse| { - m.interaction_response_data(|c| { - c.flags(MessageFlags::EPHEMERAL) - .embed(|e: &mut CreateEmbed| { - default_embed( - e, - "This command can only be ran in a server.", - false, - ) - }) - }) - }, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .flags(InteractionResponseFlags::EPHEMERAL) + .embed(default_embed( + "This command can only be ran in a server.", + false, + )), + ), ) .await?; @@ -94,19 +83,12 @@ async fn notifychannel( .find(|o| o.name == "target_channel") { channel_id - .resolved - .clone() - .and_then(|v| { - if let CommandDataOptionValue::Channel(c) = v { - Some(c.id) - } else { - None - } - }) + .value + .as_channel_id() .ok_or("Invalid argument given")? - .to_channel_cached(ctx) + .to_channel_cached(&ctx.cache) .map_or(interaction.channel_id, |channel| { - channel.id() + channel.id }) } else { interaction.channel_id @@ -130,7 +112,7 @@ async fn notifychannel( #[command] /// Setup reminders and notifications from the bot in your DMs -async fn notifyme(ctx: &Context, interaction: &ApplicationCommandInteraction) -> CommandResult { +async fn notifyme(ctx: &Context, interaction: &CommandInteraction) -> CommandResult { let ses = EmbedSession::new(ctx, interaction.clone(), true).await?; main_menu( @@ -151,14 +133,20 @@ async fn notifyme(ctx: &Context, interaction: &ApplicationCommandInteraction) -> fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFuture<'static, ()> { Box::pin(async move { let name = if let ID::Channel((channel, _)) = id { + let lock = ses + .read() + .await; format!( "Launch Reminder Settings for {}", channel - .name( - &ses.read() - .await + .name(( + &lock .cache - ) + .clone(), + lock.http + .clone() + .as_ref() + )) .await .map_or("guild channel".to_string(), |n| { "#".to_owned() + &n @@ -168,17 +156,16 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu "Launch Reminder Settings for your DMs".to_owned() }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name(name) - .icon_url(DEFAULT_ICON) - }) - }); + .author(CreateEmbedAuthor::new(name).icon_url(DEFAULT_ICON)), + ); let reminder_ses = ses.clone(); - em.add_field( + em = em.add_field( "Reminders", "Set at which times you want to get launch reminders", false, @@ -194,7 +181,7 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu ); let filters_ses = ses.clone(); - em.add_field( + em = em.add_field( "Filters", "Set filters for which launches you do and don't want to see", false, @@ -211,7 +198,7 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu if id.guild_specific() { let mention_ses = ses.clone(); - em.add_field( + em = em.add_field( "Mentions", "Set which roles should be mentioned when posting reminders", false, @@ -228,7 +215,7 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu } let other_ses = ses.clone(); - em.add_field( + em = em.add_field( "Other", "Enable other notifications", false, @@ -245,7 +232,7 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu if id.guild_specific() { let close_ses = ses.clone(); - em.add_field( + em = em.add_field( "Close", "Close this menu", false, @@ -258,7 +245,7 @@ fn main_menu(ses: Arc>, id: ID) -> futures::future::BoxFutu .await; let r = lock .interaction - .delete_original_interaction_response(&lock.http) + .delete_response(&lock.http) .await; if let Err(e) = r { dbg!(e); diff --git a/src/commands/reminders/pages.rs b/src/commands/reminders/pages.rs index 82124e6..0b22992 100644 --- a/src/commands/reminders/pages.rs +++ b/src/commands/reminders/pages.rs @@ -11,11 +11,11 @@ use serenity::{ }, model::{ application::{ - component::ButtonStyle, - interaction::Interaction, + ButtonStyle, + InputTextStyle, + Interaction, }, channel::ReactionType, - prelude::component::InputTextStyle, }, prelude::RwLock, }; @@ -102,15 +102,14 @@ pub fn reminders_page( }, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Launch Reminders") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author(CreateEmbedAuthor::new("Launch Reminders").icon_url(DEFAULT_ICON)) + .description(description), + ); let add_ses = ses.clone(); em.add_option( @@ -196,7 +195,7 @@ pub fn reminders_page( .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -231,17 +230,16 @@ pub fn filters_page( id: ID, ) -> futures::future::BoxFuture<'static, ()> { Box::pin(async move { - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Filters") - .icon_url(DEFAULT_ICON) - }) - }); + .author(CreateEmbedAuthor::new("Filters").icon_url(DEFAULT_ICON)), + ); let filters_ses = ses.clone(); - em.add_field( + em = em.add_field( "Filters", "Set which agencies to filter out of launch reminders, making you not get any reminders for these agencies again", false, @@ -253,7 +251,7 @@ pub fn filters_page( ); let allow_filters_ses = ses.clone(); - em.add_field( + em = em.add_field( "Allow Filters", "Set which agencies to filter launch reminders for, making you get **only** reminders for these agencies", false, @@ -265,7 +263,7 @@ pub fn filters_page( ); let payload_filters_ses = ses.clone(); - em.add_field( + em = em.add_field( "Payload Filters", "Add word or regex filters to filter out launches with specific payloads", false, @@ -387,15 +385,16 @@ fn disallow_filters_page( }, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Launch Agency Disallow Filters") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author( + CreateEmbedAuthor::new("Launch Agency Disallow Filters").icon_url(DEFAULT_ICON), + ) + .description(description), + ); let add_ses = ses.clone(); let filters_clone = filters.clone(); @@ -452,7 +451,7 @@ fn disallow_filters_page( .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -516,7 +515,7 @@ fn disallow_filters_page( .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -634,15 +633,16 @@ fn allow_filters_page( }, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Launch Agency Allow Filters") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author( + CreateEmbedAuthor::new("Launch Agency Allow Filters").icon_url(DEFAULT_ICON), + ) + .description(description), + ); let add_ses = ses.clone(); let allow_filters_clone = allow_filters.clone(); @@ -706,7 +706,7 @@ fn allow_filters_page( .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -767,7 +767,7 @@ fn allow_filters_page( ) .build() .unwrap() - .listen(http, &Interaction::MessageComponent(button_click), data).await; + .listen(http, &Interaction::Component(button_click), data).await; }) }, ); @@ -876,15 +876,16 @@ fn payload_filters_page( }, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Launch Agency Allow Filters") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author( + CreateEmbedAuthor::new("Launch Agency Allow Filters").icon_url(DEFAULT_ICON), + ) + .description(description), + ); let add_ses = ses.clone(); em.add_non_update_option( @@ -938,20 +939,20 @@ fn payload_filters_page( .set_user(user_id) .add_field( Field::new( + InputTextStyle::Short, "added_payload_filter", "New payload filter", ) .set_max_length(20) .set_min_length(3) .set_placeholder("Put in a word or regex to filter out payloads") - .set_style(InputTextStyle::Short) .set_required(), ) .build() .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -1023,7 +1024,7 @@ fn payload_filters_page( .unwrap() .listen( http, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, ) .await; @@ -1105,15 +1106,14 @@ pub fn mentions_page( ID::User(_) => return, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Role Mentions") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author(CreateEmbedAuthor::new("Role Mentions").icon_url(DEFAULT_ICON)) + .description(description), + ); let add_ses = ses.clone(); let mentions_clone = mentions.clone(); @@ -1143,7 +1143,7 @@ pub fn mentions_page( role_select_menu( http, user_id, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, Some(mentions_clone), None, @@ -1189,7 +1189,7 @@ pub fn mentions_page( role_select_menu( http, user_id, - &Interaction::MessageComponent(button_click), + &Interaction::Component(button_click), data, None, Some(mentions_clone), @@ -1282,18 +1282,17 @@ pub fn other_page( }, }; - let mut em = StatefulEmbed::new_with(ses.clone(), |e: &mut CreateEmbed| { - e.color(DEFAULT_COLOR) + let mut em = StatefulEmbed::new_with_embed( + ses.clone(), + CreateEmbed::new() + .color(DEFAULT_COLOR) .timestamp(Utc::now()) - .author(|a: &mut CreateEmbedAuthor| { - a.name("Other Options") - .icon_url(DEFAULT_ICON) - }) - .description(description) - }); + .author(CreateEmbedAuthor::new("Other Options").icon_url(DEFAULT_ICON)) + .description(description), + ); let scrub_ses = ses.clone(); - em.add_field( + em = em.add_field( "Toggle Scrub Notifications", &format!("Toggle scrub notifications on and off\nThese notifications notify you when a launch gets delayed.\nThis is currently **{scrub_notifications}**"), false, @@ -1314,7 +1313,7 @@ pub fn other_page( ); let outcome_ses = ses.clone(); - em.add_field( + em = em.add_field( "Toggle Outcome Notifications", &format!("Toggle outcome notifications on and off\nThese notifications notify you about the outcome of a launch.\nThis is currently **{outcome_notifications}**"), false, @@ -1335,7 +1334,7 @@ pub fn other_page( ); let mentions_ses = ses.clone(); - em.add_field( + em = em.add_field( "Toggle Mentions", &format!( "Toggle mentions for scrub and outcome notifications.\nThis is currently **{mentions}**", @@ -1364,7 +1363,7 @@ pub fn other_page( if id.guild_specific() { let chan_ses = ses.clone(); - em.add_field( + em = em.add_field( "Set Notification Channel", "Set the channel to receive scrub and outcome notifications in, this can only be one per server", false, @@ -1389,7 +1388,7 @@ pub fn other_page( ) }; - channel_select_menu(http, user_id, &Interaction::MessageComponent(button_click), data, move |channel_id| { + channel_select_menu(http, user_id, &Interaction::Component(button_click), data, move |channel_id| { let wait_ses = chan_ses.clone(); Box::pin(async move { set_notification_channel(&wait_ses.clone(), id, channel_id).await; diff --git a/src/commands/reminders/settings.rs b/src/commands/reminders/settings.rs index f6e37a0..82c1557 100644 --- a/src/commands/reminders/settings.rs +++ b/src/commands/reminders/settings.rs @@ -46,7 +46,7 @@ pub async fn get_reminders(ses: &Arc>, id: ID) -> MongoResu match id { ID::User(user_id) => Ok(bson::from_bson( db.collection::("reminders") - .find(doc! { "users": { "$in": [user_id.0 as i64] } }, None).await? + .find(doc! { "users": { "$in": [user_id.get() as i64] } }, None).await? .collect::>>() .await .into_iter() @@ -56,7 +56,7 @@ pub async fn get_reminders(ses: &Arc>, id: ID) -> MongoResu ID::Channel((channel_id, guild_id)) => Ok(bson::from_bson( db.collection::("reminders") .find( - doc! { "channels": { "$in": [{ "channel": channel_id.0 as i64, "guild": guild_id.0 as i64 }] } }, + doc! { "channels": { "$in": [{ "channel": channel_id.get() as i64, "guild": guild_id.get() as i64 }] } }, None, ).await? .collect::>>() @@ -82,7 +82,7 @@ pub async fn add_reminder(ses: &Arc>, id: ID, duration: Dur doc! {"minutes": duration.num_minutes()}, doc! { "$addToSet": { - "users": user_id.0 as i64 + "users": user_id.get() as i64 } }, Some( @@ -96,7 +96,7 @@ pub async fn add_reminder(ses: &Arc>, id: ID, duration: Dur doc! {"minutes": duration.num_minutes()}, doc! { "$addToSet": { - "channels": { "channel": channel_id.0 as i64, "guild": guild_id.0 as i64 } + "channels": { "channel": channel_id.get() as i64, "guild": guild_id.get() as i64 } } }, Some( @@ -128,7 +128,7 @@ pub async fn remove_reminder(ses: &Arc>, id: ID, duration: doc! {"minutes": duration.num_minutes()}, doc! { "$pull": { - "users": user_id.0 as i64 + "users": user_id.get() as i64 } }, None, @@ -138,7 +138,7 @@ pub async fn remove_reminder(ses: &Arc>, id: ID, duration: doc! {"minutes": duration.num_minutes()}, doc! { "$pull": { - "channels": { "channel": channel_id.0 as i64, "guild": guild_id.0 as i64 } + "channels": { "channel": channel_id.get() as i64, "guild": guild_id.get() as i64 } } }, None, @@ -171,7 +171,7 @@ pub async fn add_filter( let result = match id { ID::User(user_id) => { collection.update_one( - doc! {"user": user_id.0 as i64}, + doc! {"user": user_id.get() as i64}, doc! { "$addToSet": { filter_type: filter @@ -186,7 +186,7 @@ pub async fn add_filter( }, ID::Channel((_, guild_id)) => { collection.update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$addToSet": { filter_type: filter @@ -227,7 +227,7 @@ pub async fn remove_filter( let result = match id { ID::User(user_id) => { collection.update_one( - doc! {"user": user_id.0 as i64}, + doc! {"user": user_id.get() as i64}, doc! { "$pull": { filter_type: filter @@ -238,7 +238,7 @@ pub async fn remove_filter( }, ID::Channel((_, guild_id)) => { collection.update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$pull": { filter_type: filter @@ -270,7 +270,7 @@ pub async fn toggle_setting(ses: &Arc>, id: ID, setting: &s let result = match id { ID::User(user_id) => { collection.update_one( - doc! {"user": user_id.0 as i64}, + doc! {"user": user_id.get() as i64}, doc! { "$set": { setting: val @@ -285,7 +285,7 @@ pub async fn toggle_setting(ses: &Arc>, id: ID, setting: &s }, ID::Channel((_, guild_id)) => { collection.update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$set": { setting: val @@ -317,10 +317,10 @@ pub async fn set_notification_channel(ses: &Arc>, id: ID, c let result = match id { ID::Channel((_, guild_id)) => { collection.update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$set": { - "notifications_channel": channel.0 as i64 + "notifications_channel": channel.get() as i64 } }, Some( @@ -352,10 +352,10 @@ pub async fn add_mention(ses: &Arc>, id: ID, role: RoleId) let result = db .collection::("guild_settings") .update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$addToSet": { - "mentions": role.0 as i64 + "mentions": role.get() as i64 } }, Some( @@ -384,10 +384,10 @@ pub async fn remove_mention(ses: &Arc>, id: ID, role: RoleI let result = db .collection::("guild_settings") .update_one( - doc! {"guild": guild_id.0 as i64}, + doc! {"guild": guild_id.get() as i64}, doc! { "$pull": { - "mentions": role.0 as i64 + "mentions": role.get() as i64 } }, None, diff --git a/src/events/event_handling.rs b/src/events/event_handling.rs index 9891b07..cfb75c2 100644 --- a/src/events/event_handling.rs +++ b/src/events/event_handling.rs @@ -7,8 +7,10 @@ use okto_framework::Handler as InteractionHandler; use reqwest::header::AUTHORIZATION; use serenity::{ async_trait, + builder::CreateMessage, + gateway::ActivityData, model::{ - application::interaction::Interaction, + application::Interaction, channel::Message, gateway::Ready, guild::{ @@ -20,7 +22,6 @@ use serenity::{ GuildId, MessageId, }, - prelude::Activity, }, prelude::{ Context, @@ -81,12 +82,16 @@ impl EventHandler for Handler { .guilds .len(), ); - let _ = ChannelId(448224720177856513) - .send_message(&ctx.http, |m| m.content(content)) + let _ = ChannelId::new(448224720177856513) + .send_message( + &ctx.http, + CreateMessage::new().content(content), + ) .await; - ctx.set_activity(Activity::listening("rockets launching")) - .await; + ctx.set_activity(Some(ActivityData::listening( + "rockets launching", + ))); tokio::spawn(async move { loop { @@ -134,18 +139,22 @@ impl EventHandler for Handler { slash_command_message(&ctx, &message).await; } - async fn guild_create(&self, ctx: Context, guild: Guild, is_new: bool) { - if is_new { + async fn guild_create(&self, ctx: Context, guild: Guild, is_new: Option) { + if is_new.unwrap_or(false) { if let Some(channel) = ctx .cache - .guild_channel(755401788294955070) + .channel(755401788294955070) + .map(|c| c.id) { let content = format!( "Joined a new guild: {} ({})\nIt has {} members", guild.name, guild.id, guild.member_count ); let _ = channel - .send_message(&ctx.http, |m| m.content(content)) + .send_message( + &ctx.http, + CreateMessage::new().content(content), + ) .await; } } @@ -155,14 +164,18 @@ impl EventHandler for Handler { if !incomplete.unavailable { if let Some(channel) = ctx .cache - .guild_channel(755401788294955070) + .channel(755401788294955070) + .map(|c| c.id) { let content = format!( "Left the following guild: {}", incomplete.id ); let _ = channel - .send_message(&ctx.http, |m| m.content(content)) + .send_message( + &ctx.http, + CreateMessage::new().content(content), + ) .await; } } @@ -179,7 +192,7 @@ impl EventHandler for Handler { format!( "An error happened in {}:\n```{:?}```", interaction - .application_command() + .as_command() .expect("not a command") .data .name, diff --git a/src/events/interaction_handler.rs b/src/events/interaction_handler.rs index 0667047..f8d1673 100644 --- a/src/events/interaction_handler.rs +++ b/src/events/interaction_handler.rs @@ -8,15 +8,14 @@ use futures::{ }, }; use serenity::{ + all::ComponentInteractionDataKind, client::Context, http::Http, model::{ application::{ - component::ComponentType, - interaction::{ - Interaction, - InteractionType, - }, + ComponentType, + Interaction, + InteractionType, }, id::{ ChannelId, @@ -68,7 +67,7 @@ impl InteractionHandler { } match interaction { - Interaction::MessageComponent(component) => { + Interaction::Component(component) => { if let Some(user) = self.user { if component .user @@ -86,11 +85,12 @@ impl InteractionHandler { } if let Some(component_type) = self.component_type { - if component_type - != component + if !component_type_equals_kind( + component_type, + &component .data - .component_type - { + .kind, + ) { return false; } } @@ -105,7 +105,7 @@ impl InteractionHandler { } } }, - Interaction::ModalSubmit(modal) => { + Interaction::Modal(modal) => { if let Some(user) = self.user { if modal .user @@ -206,7 +206,7 @@ impl InteractionHandlerBuilder { .is_none() { self.inner - .interaction_type = Some(InteractionType::MessageComponent) + .interaction_type = Some(InteractionType::Component) } self @@ -229,7 +229,7 @@ impl InteractionHandlerBuilder { .inner .interaction_type { - if interaction_type != InteractionType::MessageComponent { + if interaction_type != InteractionType::Component { return Err(Error::Other("If the component type has been set, the interaction type must be MessageComponent")); } } @@ -240,19 +240,21 @@ impl InteractionHandlerBuilder { } pub async fn handle_interaction(ctx: &Context, interaction: &Interaction) { - let all = {if let Some(waiting) = ctx - .data - .read() - .await - .get::() - { - waiting - .0 - .clone() - } else { - eprintln!("No waiting interaction cache"); - return; - }}; + let all = { + if let Some(waiting) = ctx + .data + .read() + .await + .get::() + { + waiting + .0 + .clone() + } else { + eprintln!("No waiting interaction cache"); + return; + } + }; let handled = stream::iter(all.iter()) .filter(|h| { @@ -290,45 +292,42 @@ pub async fn respond_to_interaction( ) { if update { match interaction { - Interaction::MessageComponent(comp) => { - comp.edit_original_interaction_response(http, |i| { - *i = resp.into(); - i - }) - .await + Interaction::Component(comp) => { + comp.edit_response(http.as_ref(), resp.into()) + .await }, - Interaction::ApplicationCommand(cmd) => { - cmd.edit_original_interaction_response(http, |i| { - *i = resp.into(); - i - }) - .await + Interaction::Command(cmd) => { + cmd.edit_response(http.as_ref(), resp.into()) + .await }, _ => panic!("Unsupported interaction for sending an update response to"), } .map(|_| ()) } else { match interaction { - Interaction::MessageComponent(comp) => { - comp.create_interaction_response(http, |i| { - *i = resp.into(); - i - }) + Interaction::Component(comp) => { + comp.create_response( + http.as_ref(), + resp.try_into() + .expect("created invalid interaction response"), + ) .await }, - Interaction::ModalSubmit(modal) => { + Interaction::Modal(modal) => { modal - .create_interaction_response(http, |i| { - *i = resp.into(); - i - }) + .create_response( + http.as_ref(), + resp.try_into() + .expect("created invalid interaction response"), + ) .await }, - Interaction::ApplicationCommand(cmd) => { - cmd.create_interaction_response(http, |i| { - *i = resp.into(); - i - }) + Interaction::Command(cmd) => { + cmd.create_response( + http.as_ref(), + resp.try_into() + .expect("created invalid interaction response"), + ) .await }, _ => panic!("Unsupported interaction for sending a response to"), @@ -336,3 +335,28 @@ pub async fn respond_to_interaction( } .expect("Interaction response failed"); } + +fn component_type_equals_kind( + component_type: ComponentType, + component_kind: &ComponentInteractionDataKind, +) -> bool { + match component_kind { + ComponentInteractionDataKind::Button => component_type == ComponentType::Button, + ComponentInteractionDataKind::StringSelect { + .. + } => component_type == ComponentType::StringSelect, + ComponentInteractionDataKind::UserSelect { + .. + } => component_type == ComponentType::UserSelect, + ComponentInteractionDataKind::ChannelSelect { + .. + } => component_type == ComponentType::ChannelSelect, + ComponentInteractionDataKind::RoleSelect { + .. + } => component_type == ComponentType::RoleSelect, + ComponentInteractionDataKind::MentionableSelect { + .. + } => component_type == ComponentType::MentionableSelect, + ComponentInteractionDataKind::Unknown(_) => false, + } +} diff --git a/src/events/modal.rs b/src/events/modal.rs index 8b3cafa..8dfc875 100644 --- a/src/events/modal.rs +++ b/src/events/modal.rs @@ -1,23 +1,21 @@ -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::sync::Arc; use futures::future::BoxFuture; use serenity::{ - builder::CreateInputText, + builder::{ + CreateActionRow, + CreateInputText, + CreateInteractionResponse, + }, http::Http, model::{ - application::interaction::{ + application::{ + ActionRowComponent, + InputTextStyle, Interaction, - InteractionResponseType, InteractionType, }, id::UserId, - prelude::component::{ - ActionRowComponent, - InputTextStyle, - }, }, prelude::{ RwLock, @@ -32,7 +30,10 @@ use super::interaction_handler::{ }; use crate::{ models::caches::InteractionKey, - utils::interaction_builder::InteractionResponseBuilder, + utils::interaction_builder::{ + InteractionBuilderKind, + InteractionResponseBuilder, + }, }; type Handler = Arc) -> BoxFuture<'static, ()> + Send + Sync>>; @@ -77,7 +78,7 @@ impl Modal { .filter_map(|comp| { match comp { ActionRowComponent::InputText(input) => { - Some((input.custom_id, input.value)) + Some((input.custom_id, input.value?)) }, _ => None, } @@ -89,15 +90,16 @@ impl Modal { let handler_clone = handler.clone(); Box::pin(async move { let _ = data - .create_interaction_response(http_clone, |c| { - c.kind(InteractionResponseType::DeferredUpdateMessage) - }) + .create_response( + http_clone, + CreateInteractionResponse::Acknowledge, + ) .await; handler_clone(values).await }) }) - .set_interaction_type(InteractionType::ModalSubmit); + .set_interaction_type(InteractionType::Modal); if let Some(user_id) = self.user_id { interaction_handler = interaction_handler.set_user(user_id); @@ -124,25 +126,24 @@ impl Modal { async fn send(&self, http: impl AsRef, interaction: &Interaction) { let mut resp = InteractionResponseBuilder::default() - .kind(InteractionResponseType::Modal) + .kind(InteractionBuilderKind::Modal) .content( self.title .clone() .unwrap_or_else(|| "Modal".to_owned()), ) - .components(|comps| { - comps.create_action_row(|row| { - for field in &self.fields { - row.add_input_text( + .components( + self.fields + .iter() + .map(|field| { + CreateActionRow::InputText( field .inner .clone(), - ); - } - row - }); - comps - }); + ) + }) + .collect(), + ); if let Some(custom_id) = self .custom_id @@ -227,43 +228,40 @@ pub struct Field { } impl Field { - pub fn new(custom_id: &T, label: &T) -> Self { - let mut inner = CreateInputText(HashMap::new()); - inner - .custom_id(custom_id.to_string()) - .label(label.to_string()); - + pub fn new(style: InputTextStyle, custom_id: &T, label: &T) -> Self { Self { - inner, + inner: CreateInputText::new( + style, + label.to_string(), + custom_id.to_string(), + ), } } - pub fn set_style(mut self, style: InputTextStyle) -> Self { - self.inner - .style(style); - self - } - pub fn set_required(mut self) -> Self { - self.inner + self.inner = self + .inner .required(true); self } - pub fn set_min_length(mut self, min_length: u64) -> Self { - self.inner + pub fn set_min_length(mut self, min_length: u16) -> Self { + self.inner = self + .inner .min_length(min_length); self } - pub fn set_max_length(mut self, max_length: u64) -> Self { - self.inner + pub fn set_max_length(mut self, max_length: u16) -> Self { + self.inner = self + .inner .max_length(max_length); self } pub fn set_placeholder(mut self, placeholder: &T) -> Self { - self.inner + self.inner = self + .inner .placeholder(placeholder.to_string()); self } diff --git a/src/events/select_menu.rs b/src/events/select_menu.rs index d4f4c85..5a3ed56 100644 --- a/src/events/select_menu.rs +++ b/src/events/select_menu.rs @@ -6,14 +6,19 @@ use std::{ use futures::future::BoxFuture; use itertools::Itertools; use serenity::{ + all::ComponentInteractionDataKind, + builder::{ + CreateActionRow, + CreateInteractionResponse, + CreateSelectMenu, + CreateSelectMenuKind, + CreateSelectMenuOption, + }, http::Http, model::{ application::{ - component::ComponentType, - interaction::{ - Interaction, - InteractionResponseType, - }, + ComponentType, + Interaction, }, id::UserId, }, @@ -67,12 +72,20 @@ impl SelectMenu { .message_component() .expect("Didn't get a message component in select menu"); - let key = data + let key = if let ComponentInteractionDataKind::StringSelect { + values, + } = data .data - .values - .first() - .cloned() - .expect("No values returned from select menu"); + .kind + .clone() + { + values + .first() + .cloned() + .expect("No values returned from select menu") + } else { + panic!("Got a non-string value from the select menu") + }; let chosen = options .get(&key) @@ -83,15 +96,16 @@ impl SelectMenu { let handler_clone = handler.clone(); Box::pin(async move { let _ = data - .create_interaction_response(http_clone, |c| { - c.kind(InteractionResponseType::DeferredUpdateMessage) - }) + .create_response( + http_clone, + CreateInteractionResponse::Acknowledge, + ) .await; handler_clone((key, chosen.clone())).await }) }) - .set_component_type(ComponentType::SelectMenu); + .set_component_type(ComponentType::StringSelect); if let Some(user_id) = self.user_id { interaction_handler = interaction_handler.set_user(user_id); @@ -123,40 +137,32 @@ impl SelectMenu { .clone() .unwrap_or_else(|| "Select an option".to_owned()), ) - .components(|comps| { - for (i, chunk) in self - .options + .components( + self.options .iter() .sorted_by_key(|t| t.1) .chunks(25) .into_iter() .take(5) .enumerate() - { - comps.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id(format!( + .map(|(i, chunk)| { + CreateActionRow::SelectMenu(CreateSelectMenu::new( + format!( "{}-{}", self.custom_id .as_ref() .map_or("select-row", |s| s.as_str()), i - )) - .max_values(1) - .options(|options| { - for (key, value) in chunk { - options.create_option(|opt| { - opt.value(key) - .label(value) - }); - } - options - }) - }) - }); - } - comps - }); + ), + CreateSelectMenuKind::String { + options: chunk + .map(|(key, value)| CreateSelectMenuOption::new(value, key)) + .collect(), + }, + )) + }) + .collect(), + ); if self.ephemeral { resp = resp.make_ephemeral(); diff --git a/src/events/statefulembed.rs b/src/events/statefulembed.rs index 5795b97..0be27c4 100644 --- a/src/events/statefulembed.rs +++ b/src/events/statefulembed.rs @@ -6,22 +6,19 @@ use serenity::{ builder::{ CreateActionRow, CreateButton, - CreateComponents, CreateEmbed, + CreateInteractionResponse, + CreateInteractionResponseMessage, EditInteractionResponse, }, cache::Cache, http::Http, model::{ application::{ - component::ButtonStyle, - interaction::{ - application_command::ApplicationCommandInteraction, - message_component::MessageComponentInteraction, - Interaction, - InteractionResponseType, - MessageFlags, - }, + ButtonStyle, + CommandInteraction, + ComponentInteraction, + Interaction, }, channel::ReactionType, id::{ @@ -42,7 +39,7 @@ use crate::{ utils::error_log, }; -type Handler = dyn Fn(MessageComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync; +type Handler = dyn Fn(ComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync; #[derive(Debug, Clone)] pub struct ButtonType { @@ -75,13 +72,7 @@ impl StatefulEmbed { } } - pub fn new_with(session: Arc>, f: F) -> Self - where - F: FnOnce(&mut CreateEmbed) -> &mut CreateEmbed, - { - let mut em = CreateEmbed::default(); - f(&mut em); - + pub fn new_with_embed(session: Arc>, em: CreateEmbed) -> Self { Self { inner: em, session, @@ -90,22 +81,23 @@ impl StatefulEmbed { } pub fn add_field( - &mut self, + mut self, name: &str, value: &str, inline: bool, button: &ButtonType, handler: F, - ) -> &mut Self + ) -> Self where - F: Fn(MessageComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, + F: Fn(ComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, { let full_name = if let Some(e) = &button.emoji { format!("{e} {name}") } else { name.to_owned() }; - self.inner + self.inner = self + .inner .field(full_name, value, inline); self.options .push(StatefulOption { @@ -119,7 +111,7 @@ impl StatefulEmbed { pub fn add_option(&mut self, button: &ButtonType, handler: F) -> &mut Self where - F: Fn(MessageComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, + F: Fn(ComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, { self.options .push(StatefulOption { @@ -133,7 +125,7 @@ impl StatefulEmbed { pub fn add_non_update_option(&mut self, button: &ButtonType, handler: F) -> &mut Self where - F: Fn(MessageComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, + F: Fn(ComponentInteraction) -> BoxFuture<'static, ()> + Send + Sync + 'static, { self.options .push(StatefulOption { @@ -145,47 +137,44 @@ impl StatefulEmbed { self } - fn get_components(&self) -> CreateComponents { - let mut components = CreateComponents::default(); + fn get_components(&self) -> Vec { + let mut components = Vec::new(); for option_batch in &self .options .iter() .chunks(5) { - components.create_action_row(|r: &mut CreateActionRow| { - for option in option_batch { - r.create_button(|b: &mut CreateButton| { - b.style( - option - .button - .style, - ) - .label( - option - .button - .label - .to_string(), - ) - .custom_id( - option - .button - .label - .to_string(), - ); - - if let Some(e) = &option - .button - .emoji - { - b.emoji(e.clone()); - } - - b - }); + let mut row = Vec::new(); + for option in option_batch { + let mut button = CreateButton::new( + option + .button + .label + .to_string(), + ) + .style( + option + .button + .style, + ) + .label( + option + .button + .label + .to_string(), + ); + + if let Some(e) = &option + .button + .emoji + { + button = button.emoji(e.clone()); } - r - }); + + row.push(button); + } + components.push(CreateActionRow::Buttons(row)) } components @@ -204,29 +193,21 @@ impl StatefulEmbed { session .interaction - .edit_original_interaction_response( + .edit_response( &http, - |e: &mut EditInteractionResponse| { - e.components(|c: &mut CreateComponents| { - *c = self.get_components(); - - c - }) + EditInteractionResponse::new() + .components(self.get_components()) .content("") - .embed(|e: &mut CreateEmbed| { - e.0 = self - .inner - .0 - .clone(); - e - }) - }, + .embed( + self.inner + .clone(), + ), ) .await?; let msg = session .interaction - .get_interaction_response(&http) + .get_response(&http) .await?; if let Some(embeds) = session @@ -250,7 +231,7 @@ impl StatefulEmbed { #[derive(Clone)] pub struct EmbedSession { pub current_state: Option, - pub interaction: ApplicationCommandInteraction, + pub interaction: CommandInteraction, pub http: Arc, pub data: Arc>, pub cache: Arc, @@ -260,19 +241,20 @@ pub struct EmbedSession { impl EmbedSession { pub async fn new( ctx: &Context, - interaction: ApplicationCommandInteraction, + interaction: CommandInteraction, ephemeral: bool, ) -> Result>> { interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredChannelMessageWithSource); - + .create_response( + &ctx.http, if ephemeral { - c.interaction_response_data(|d| d.flags(MessageFlags::EPHEMERAL)); - } - - c - }) + CreateInteractionResponse::Defer( + CreateInteractionResponseMessage::new().ephemeral(true), + ) + } else { + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()) + }, + ) .await?; Ok(Arc::new(RwLock::new(Self { @@ -299,7 +281,7 @@ impl EmbedSession { } pub async fn on_button_click(ctx: &Context, full_interaction: &Interaction) { - if let Interaction::MessageComponent(interaction) = full_interaction { + if let Interaction::Component(interaction) = full_interaction { let handler = { let embed_session = ctx .data @@ -358,9 +340,10 @@ pub async fn on_button_click(ctx: &Context, full_interaction: &Interaction) { if let Some((handler, true)) = handler { let r = interaction - .create_interaction_response(&ctx.http, |c| { - c.kind(InteractionResponseType::DeferredUpdateMessage) - }) + .create_response( + &ctx.http, + CreateInteractionResponse::Acknowledge, + ) .await; if let Err(e) = r { diff --git a/src/events/time_embed.rs b/src/events/time_embed.rs index 17e4ae4..47fae9f 100644 --- a/src/events/time_embed.rs +++ b/src/events/time_embed.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use chrono::Duration; use futures::future::BoxFuture; use serenity::{ + builder::CreateEmbed, model::{ - application::component::ButtonStyle, + application::ButtonStyle, channel::ReactionType, }, prelude::RwLock, @@ -48,21 +49,19 @@ impl TimeEmbed { fn show_embed(self) -> BoxFuture<'static, ()> { Box::pin(async move { - let mut embed = StatefulEmbed::new_with( + let mut embed = StatefulEmbed::new_with_embed( self.session .clone(), - |em| { - em.description( - if self.duration > Duration::zero() { - format!( - "Setting a reminder for {} before the moment of launch.", - format_duration(self.duration, false) - ) - } else { - "Please start specifying a duration using the buttons below:".to_owned() - }, - ) - }, + CreateEmbed::new().description( + if self.duration > Duration::zero() { + format!( + "Setting a reminder for {} before the moment of launch.", + format_duration(self.duration, false) + ) + } else { + "Please start specifying a duration using the buttons below:".to_owned() + }, + ), ); let self_1_day = self.clone(); diff --git a/src/main.rs b/src/main.rs index c8ecdba..a0b10dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,8 @@ use models::caches::{ }; use mongodb::Client as MongoClient; use serenity::{ + all::ApplicationId, + builder::CreateMessage, client::Client, framework::standard::StandardFramework, model::gateway::GatewayIntents, @@ -54,10 +56,10 @@ use utils::{ #[tokio::main] async fn main() { console_subscriber::init(); - + // Login with a bot token from the environment let token = env::var("DISCORD_TOKEN").expect("no bot token"); - let application_id: u64 = env::var("DISCORD_ID") + let application_id: ApplicationId = env::var("DISCORD_ID") .expect("no application id") .parse() .expect("provided application id is not an integer"); @@ -79,9 +81,11 @@ async fn main() { if let Err(why) = error { let _ = msg .channel_id - .send_message(&ctx.http, |m| { - m.content("Oh no, an error happened.\nPlease try again at a later time") - }) + .send_message( + &ctx.http, + CreateMessage::new() + .content("Oh no, an error happened.\nPlease try again at a later time"), + ) .await; error_log( &ctx.http, @@ -148,9 +152,7 @@ async fn main() { .await .expect("Error creating client"); - let (http_clone, - launches_cache_clone, - db_clone,) = if let Some(launches_cache) = client + let (http_clone, launches_cache_clone, db_clone) = if let Some(launches_cache) = client .data .read() .await @@ -164,13 +166,14 @@ async fn main() { { let launches_cache_clone = launches_cache.clone(); let http_clone = client - .cache_and_http .http .clone(); let db_clone = db.clone(); - (http_clone, + ( + http_clone, launches_cache_clone, - db_clone) + db_clone, + ) } else { panic!("No database key or connection") } diff --git a/src/reminders/change_notifications.rs b/src/reminders/change_notifications.rs index f2dcdba..a7df56f 100644 --- a/src/reminders/change_notifications.rs +++ b/src/reminders/change_notifications.rs @@ -27,9 +27,9 @@ use serenity::{ model::{ channel::Message, id::ChannelId, + Colour, Timestamp, }, - utils::Colour, Error as SerenityError, }; @@ -85,7 +85,7 @@ fn get_mentions(settings: &GuildSettings) -> Option { .mentions .iter() .fold(String::new(), |acc, mention| { - acc + &format!(" <@&{}>", mention.as_u64()) + acc + &format!(" <@&{}>", mention.get()) }); (settings.mention_others && !mentions.is_empty()).then_some(mentions) @@ -109,7 +109,7 @@ async fn send_user_notification<'r>( .ok() } }) - .map(|channel| send_message(http, channel.id, None, embed)) + .map(|channel| send_message(http, channel.id, None, embed.clone())) .collect::>() .await .collect::>() @@ -132,7 +132,7 @@ async fn send_guild_notification<'r>( ) }) .map(|(c, settings)| (c, get_mentions(&settings))) - .map(|(channel, mentions)| send_message(http, channel, mentions, embed)) + .map(|(channel, mentions)| send_message(http, channel, mentions, embed.clone())) .collect::>() .await .collect::>() @@ -161,33 +161,25 @@ pub async fn notify_scrub(http: Arc, db: Database, old: LaunchData, new: L send_guild_notification(&http, guild_settings, &new, &embed).await; } -async fn send_message<'r>( - http: &'r Arc, +async fn send_message( + http: &Arc, channel: ChannelId, mentions_opt: Option, - embed: &'r CreateEmbed, + embed: CreateEmbed, ) -> Result { - channel - .send_message(http, |m: &mut CreateMessage| { - m.embed(|e: &mut CreateEmbed| { - *e = embed.clone(); - e - }); + let mut message = CreateMessage::new().embed(embed); - if let Some(mentions) = mentions_opt { - m.content(mentions); - } + if let Some(mentions) = mentions_opt { + message = message.content(mentions); + } - m - }) + channel + .send_message(http, message) .await } fn scrub_embed<'r>(old: &'r LaunchData, new: &'r LaunchData) -> CreateEmbed { - let mut e = CreateEmbed::default(); - default_embed( - &mut e, &format!( "The launch of {} on a **{}** is now scheduled for {} instead of {}", new.payload, @@ -208,17 +200,14 @@ fn scrub_embed<'r>(old: &'r LaunchData, new: &'r LaunchData) -> CreateEmbed { }, ), false, - ); - - e.timestamp( + ) + .timestamp( Timestamp::from_unix_timestamp( new.net .timestamp(), ) .expect("Invalid timestamp"), - ); - - e + ) } pub async fn notify_outcome(http: Arc, db: Database, finished: LaunchData) { @@ -244,10 +233,7 @@ pub async fn notify_outcome(http: Arc, db: Database, finished: LaunchData) } fn outcome_embed(finished: &LaunchData) -> CreateEmbed { - let mut e = CreateEmbed::default(); - default_embed( - &mut e, &format!( "The launch of {} on a {} has completed with a status of **{}**!", &finished.payload, @@ -257,9 +243,8 @@ fn outcome_embed(finished: &LaunchData) -> CreateEmbed { .as_str() ), true, - ); - - e.color( + ) + .color( if matches!(finished.status, LaunchStatus::Success) { Colour::FOOYOO } else if matches!( @@ -270,7 +255,5 @@ fn outcome_embed(finished: &LaunchData) -> CreateEmbed { } else { Colour::RED }, - ); - - e + ) } diff --git a/src/reminders/filtering.rs b/src/reminders/filtering.rs index 5b89deb..595dbec 100644 --- a/src/reminders/filtering.rs +++ b/src/reminders/filtering.rs @@ -92,7 +92,7 @@ mod tests { fn no_filters() { let launches = create_fake_launches(); let settings = GuildSettings { - guild: GuildId(429307670730637312), + guild: GuildId::new(429307670730637312), filters: vec![], allow_filters: vec![], payload_filters: vec![], @@ -100,7 +100,7 @@ mod tests { scrub_notifications: true, outcome_notifications: true, mention_others: true, - notifications_channel: Some(ChannelId(429307774804033536)), + notifications_channel: Some(ChannelId::new(429307774804033536)), }; assert!(passes_filters(&settings, &launches[0])); @@ -111,7 +111,7 @@ mod tests { fn block_filters() { let launches = create_fake_launches(); let settings = GuildSettings { - guild: GuildId(429307670730637312), + guild: GuildId::new(429307670730637312), filters: vec!["ula".into()], allow_filters: vec![], payload_filters: vec![], @@ -119,7 +119,7 @@ mod tests { scrub_notifications: true, outcome_notifications: true, mention_others: true, - notifications_channel: Some(ChannelId(429307774804033536)), + notifications_channel: Some(ChannelId::new(429307774804033536)), }; assert!(passes_filters(&settings, &launches[0])); @@ -130,7 +130,7 @@ mod tests { fn allow_filters() { let launches = create_fake_launches(); let settings = GuildSettings { - guild: GuildId(429307670730637312), + guild: GuildId::new(429307670730637312), filters: vec![], allow_filters: vec!["ula".into()], payload_filters: vec![], @@ -138,7 +138,7 @@ mod tests { scrub_notifications: true, outcome_notifications: true, mention_others: true, - notifications_channel: Some(ChannelId(429307774804033536)), + notifications_channel: Some(ChannelId::new(429307774804033536)), }; assert!(!passes_filters(&settings, &launches[0])); @@ -149,7 +149,7 @@ mod tests { fn payload_filters() { let launches = create_fake_launches(); let settings = GuildSettings { - guild: GuildId(429307670730637312), + guild: GuildId::new(429307670730637312), filters: vec![], allow_filters: vec![], payload_filters: vec![Regex::new(r"(?im)\bstarlink\b").unwrap()], @@ -157,7 +157,7 @@ mod tests { scrub_notifications: true, outcome_notifications: true, mention_others: true, - notifications_channel: Some(ChannelId(429307774804033536)), + notifications_channel: Some(ChannelId::new(429307774804033536)), }; assert!(!passes_filters(&settings, &launches[0])); diff --git a/src/reminders/reminder_tracking.rs b/src/reminders/reminder_tracking.rs index 598bcb2..bd6c3af 100644 --- a/src/reminders/reminder_tracking.rs +++ b/src/reminders/reminder_tracking.rs @@ -17,6 +17,7 @@ use futures::{ StreamExt, }, }; +use itertools::Itertools; use mongodb::{ bson::{ self, @@ -32,7 +33,7 @@ use serenity::{ CreateEmbedAuthor, CreateMessage, }, - http::client::Http, + http::Http, model::Timestamp, prelude::RwLock, }; @@ -177,22 +178,20 @@ async fn execute_reminder( .mentions .iter() .fold(String::new(), |acc, mention| { - acc + &format!(" <@&{}>", mention.as_u64()) + acc + &format!(" <@&{}>", mention.get()) }); (c, mentions) }) .map(|(c, mentions)| { - c.channel - .send_message(&http, |m: &mut CreateMessage| { - m.embed(|e: &mut CreateEmbed| reminder_embed(e, &l, difference)); + let mut m = CreateMessage::new().embed(reminder_embed(&l, difference)); - if !mentions.is_empty() { - m.content(mentions); - } + if !mentions.is_empty() { + m = m.content(mentions); + } - m - }) + c.channel + .send_message(&http, m) }) .collect::>() .await @@ -203,7 +202,7 @@ async fn execute_reminder( .filter_map(|u| { let db = db.clone(); async move { - get_user_settings(&db, u.0) + get_user_settings(&db, u.get()) .await .ok() .map(|s| (u, s)) @@ -219,9 +218,10 @@ async fn execute_reminder( } }) .map(|c| { - c.id.send_message(&http, |m: &mut CreateMessage| { - m.embed(|e: &mut CreateEmbed| reminder_embed(e, &l, difference)) - }) + c.id.send_message( + &http, + CreateMessage::new().embed(reminder_embed(&l, difference)), + ) }) .collect::>() .await @@ -229,28 +229,24 @@ async fn execute_reminder( .await; } -fn reminder_embed<'a>( - e: &'a mut CreateEmbed, - l: &LaunchData, - diff: Duration, -) -> &'a mut CreateEmbed { +fn reminder_embed(l: &LaunchData, diff: Duration) -> CreateEmbed { let live = if let Some(link) = l - .vid_urls - .first() + .vid_urls.iter().find_or_first(|v| v.url.contains("youtube.com")) { format!("**Live at:** {}", format_url(&link.url)) } else { String::new() }; - e.color(DEFAULT_COLOR) - .author(|a: &mut CreateEmbedAuthor| { - a.name(format!( + let mut e = CreateEmbed::new() + .color(DEFAULT_COLOR) + .author( + CreateEmbedAuthor::new(format!( "{} till launch", &format_duration(diff, false) )) - .icon_url(DEFAULT_ICON) - }) + .icon_url(DEFAULT_ICON), + ) .description(format!( "**Payload:** {}\n\ **Vehicle:** {}\n\ @@ -271,7 +267,7 @@ fn reminder_embed<'a>( ); if let Some(img) = &l.rocket_img { - e.thumbnail(img); + e = e.thumbnail(img); } e diff --git a/src/utils/constants.rs b/src/utils/constants.rs index 4da4984..ac6a515 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -72,12 +72,12 @@ lazy_static! { pub static ref PROGRADE: ReactionType = ReactionType::Custom { animated: false, name: Some("Prograde".to_owned()), - id: EmojiId(433308892584476674), + id: EmojiId::new(433308892584476674), }; pub static ref RETROGRADE: ReactionType = ReactionType::Custom { animated: false, name: Some("Retrograde".to_owned()), - id: EmojiId(433308874343448576), + id: EmojiId::new(433308874343448576), }; pub static ref MENTION_REGEX: Regex = Regex::new("<[@#][!&]?([0-9]{17,20})>").unwrap(); pub static ref ID_REGEX: Regex = Regex::new("^[0-9]{17,20}$").unwrap(); @@ -315,7 +315,15 @@ fn vehicle_map() -> HashMap<&'static str, Vec<&'static str>> { res.insert("long-march11", vec!["Long March 11"]); res.insert("firefly", vec!["Firefly Alpha"]); res.insert("starship", vec!["Starship"]); - res.insert("vulcan", vec!["Vulcan","Vulcan VC6L","Vulcan VC4L", "Vulcan VC2S"]); + res.insert( + "vulcan", + vec![ + "Vulcan", + "Vulcan VC6L", + "Vulcan VC4L", + "Vulcan VC2S", + ], + ); res } diff --git a/src/utils/default_select_menus.rs b/src/utils/default_select_menus.rs index 0234c39..475f755 100644 --- a/src/utils/default_select_menus.rs +++ b/src/utils/default_select_menus.rs @@ -4,7 +4,7 @@ use futures::future::BoxFuture; use serenity::{ http::Http, model::{ - application::interaction::Interaction, + application::Interaction, id::{ ChannelId, GuildId, @@ -46,10 +46,9 @@ pub async fn role_select_menu( } SelectMenu::builder(move |(id, _)| { - let id = RoleId( - id.parse() - .expect("Got invalid role id from role select"), - ); + let id: RoleId = id + .parse() + .expect("Got invalid role id from role select"); callback(id) }) .set_description("Select a role") @@ -60,7 +59,13 @@ pub async fn role_select_menu( roles .into_iter() .take(125) - .map(|(k, v)| (k.0.to_string(), v.name)) + .map(|(k, v)| { + ( + k.get() + .to_string(), + v.name, + ) + }) .collect(), ) .build() @@ -85,7 +90,7 @@ pub async fn channel_select_menu( .expect("Can't get channels from guild"); SelectMenu::builder(move |(id, _)| { - let id = ChannelId( + let id = ChannelId::new( id.parse() .expect("Got invalid channel id from channel select"), ); @@ -99,7 +104,13 @@ pub async fn channel_select_menu( channels .into_iter() .take(125) - .map(|(k, v)| (k.0.to_string(), v.name)) + .map(|(k, v)| { + ( + k.get() + .to_string(), + v.name, + ) + }) .collect(), ) .build() @@ -110,16 +121,16 @@ pub async fn channel_select_menu( fn get_guild(interaction: &Interaction) -> GuildId { match interaction { - Interaction::MessageComponent(comp) => { + Interaction::Component(comp) => { comp.guild_id .expect("Trying to get channels in a non-guild menu") }, - Interaction::ModalSubmit(modal) => { + Interaction::Modal(modal) => { modal .guild_id .expect("Trying to get channels in a non-guild modal") }, - Interaction::ApplicationCommand(cmd) => { + Interaction::Command(cmd) => { cmd.guild_id .expect("Trying to get channels in a non-guild command") }, diff --git a/src/utils/interaction_builder.rs b/src/utils/interaction_builder.rs index 9ed3bcf..b2c24aa 100644 --- a/src/utils/interaction_builder.rs +++ b/src/utils/interaction_builder.rs @@ -1,19 +1,27 @@ use serenity::{ builder::{ - CreateComponents, + CreateActionRow, CreateEmbed, CreateInteractionResponse, + CreateInteractionResponseMessage, + CreateModal, EditInteractionResponse, }, - model::application::interaction::InteractionResponseType, + framework::standard::CommandError, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InteractionBuilderKind { + Message, + Modal, +} + pub struct InteractionResponseBuilder { custom_id: Option, content: Option, embed: Option, - components: Option, - kind: InteractionResponseType, + components: Vec, + kind: InteractionBuilderKind, ephemeral: bool, } @@ -23,8 +31,8 @@ impl InteractionResponseBuilder { custom_id: None, content: None, embed: None, - components: None, - kind: InteractionResponseType::ChannelMessageWithSource, + components: Vec::new(), + kind: InteractionBuilderKind::Message, ephemeral: false, } } @@ -40,7 +48,7 @@ impl InteractionResponseBuilder { } #[allow(dead_code)] - pub fn kind(mut self, t: InteractionResponseType) -> Self { + pub fn kind(mut self, t: InteractionBuilderKind) -> Self { self.kind = t; self } @@ -53,13 +61,8 @@ impl InteractionResponseBuilder { self } - pub fn components( - mut self, - f: impl FnOnce(&mut CreateComponents) -> &mut CreateComponents, - ) -> Self { - let mut comps = CreateComponents::default(); - f(&mut comps); - self.components = Some(comps); + pub fn components(mut self, rows: Vec) -> Self { + self.components = rows; self } @@ -68,60 +71,70 @@ impl InteractionResponseBuilder { self } - pub fn into_create_response(self) -> CreateInteractionResponse<'static> { - let mut resp = CreateInteractionResponse::default(); - - resp.kind(self.kind) - .interaction_response_data(|d| { - if let Some(embed) = self.embed { - d.set_embed(embed); - } - - if self.kind == InteractionResponseType::Modal { - if let Some(content) = self.content { - d.title(content); - } - } else if let Some(content) = self.content { - d.content(content); - } - - if let Some(components) = self.components { - d.components(|c| { - *c = components; - c - }); - } - if let Some(id) = self.custom_id { - d.custom_id(id); - } - d.ephemeral(self.ephemeral) - }); + pub fn into_create_response(self) -> Result { + if self.kind == InteractionBuilderKind::Modal { + let id = if let Some(id) = self.custom_id { + Ok(id) + } else { + Err(CommandError::from( + "A custom id is required for a modal", + )) + }?; + let title = if let Some(content) = self.content { + Ok(content) + } else { + Err(CommandError::from( + "content is required in a modal", + )) + }?; + return Ok(CreateInteractionResponse::Modal( + CreateModal::new(id, title).components(self.components), + )); + } - resp + let mut resp = CreateInteractionResponseMessage::new(); + if let Some(embed) = self.embed { + resp = resp.embed(embed); + } + + if let Some(content) = self.content { + resp = resp.content(content); + } + if !self + .components + .is_empty() + { + resp = resp.components(self.components); + } + resp = resp.ephemeral(self.ephemeral); + + Ok(CreateInteractionResponse::Message(resp)) } pub fn into_edit_response(self) -> EditInteractionResponse { - let mut resp = EditInteractionResponse::default(); + let mut resp = EditInteractionResponse::new(); if let Some(embed) = self.embed { - resp.set_embed(embed); + resp = resp.embed(embed); } if let Some(content) = self.content { - resp.content(content); + resp = resp.content(content); } - if let Some(components) = self.components { - resp.components(|c| { - *c = components; - c - }); + if !self + .components + .is_empty() + { + resp = resp.components(self.components); } resp } } -impl From for CreateInteractionResponse<'_> { - fn from(other: InteractionResponseBuilder) -> CreateInteractionResponse<'static> { +impl TryFrom for CreateInteractionResponse { + type Error = CommandError; + + fn try_from(other: InteractionResponseBuilder) -> Result { other.into_create_response() } } diff --git a/src/utils/launches.rs b/src/utils/launches.rs index eb80d94..dbbfce6 100644 --- a/src/utils/launches.rs +++ b/src/utils/launches.rs @@ -7,7 +7,7 @@ use std::{ str::FromStr, }; -use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; +use serenity::model::application::CommandInteraction; use crate::{ models::launches::{ @@ -52,7 +52,7 @@ pub fn format_links(links: &[VidURL]) -> Option { pub fn filter_launches( launches: Vec, - interaction: &ApplicationCommandInteraction, + interaction: &CommandInteraction, ) -> Result, FilterErrorType> { let agency_filter = interaction .data @@ -61,12 +61,9 @@ pub fn filter_launches( .find(|o| o.name == "lsp") .and_then(|o| { o.value - .clone() + .as_str() }) - .and_then(|v| { - v.as_str() - .map(str::to_lowercase) - }); + .map(str::to_lowercase); if let Some(lsp) = agency_filter { if let Some(filter) = LAUNCH_AGENCIES.get(&lsp.as_str()) { @@ -90,12 +87,9 @@ pub fn filter_launches( .find(|o| o.name == "rocket") .and_then(|o| { o.value - .clone() + .as_str() }) - .and_then(|v| { - v.as_str() - .map(ToOwned::to_owned) - }); + .map(ToOwned::to_owned); if let Some(rocket) = rocket_filter { if let Some(filter) = LAUNCH_VEHICLES.get(rocket.as_str()) { diff --git a/src/utils/other.rs b/src/utils/other.rs index f5019b9..b6d0b7c 100644 --- a/src/utils/other.rs +++ b/src/utils/other.rs @@ -13,14 +13,15 @@ use serenity::{ builder::{ CreateEmbed, CreateEmbedAuthor, + CreateMessage, }, http::Http, model::{ - application::component::ButtonStyle, + application::ButtonStyle, channel::ReactionType, id::ChannelId, + Colour, }, - utils::Colour, }; use super::constants::{ @@ -55,16 +56,9 @@ pub fn cutoff_on_last_dot(text: &str, length: usize) -> &str { text } -pub fn default_embed<'a>( - embed: &'a mut CreateEmbed, - content: &str, - success: bool, -) -> &'a mut CreateEmbed { - embed - .author(|a: &mut CreateEmbedAuthor| { - a.name("OKTO") - .icon_url(DEFAULT_ICON) - }) +pub fn default_embed(content: &str, success: bool) -> CreateEmbed { + CreateEmbed::new() + .author(CreateEmbedAuthor::new("OKTO").icon_url(DEFAULT_ICON)) .color( if success { DEFAULT_COLOR.into() @@ -194,26 +188,31 @@ pub fn parse_duration(input: &str) -> Duration { } #[allow(dead_code)] -const DEBUG_CHANNEL: ChannelId = ChannelId(771669392399532063); -const ERROR_CHANNEL: ChannelId = ChannelId(447876053109702668); +const DEBUG_CHANNEL: ChannelId = ChannelId::new(771669392399532063); +const ERROR_CHANNEL: ChannelId = ChannelId::new(447876053109702668); #[allow(dead_code)] pub async fn debug_log(http: impl AsRef, text: &str) { let _ = DEBUG_CHANNEL - .send_message(&http, |m| m.content(text)) + .send_message( + http.as_ref(), + CreateMessage::new().content(text), + ) .await; } pub async fn error_log(http: impl AsRef, text: impl AsRef) { eprintln!("{}", text.as_ref()); let _ = ERROR_CHANNEL - .send_message(&http, |m| { - m.embed(|em| { - em.description(text.as_ref()) + .send_message( + http.as_ref(), + CreateMessage::new().embed( + CreateEmbed::new() + .description(text.as_ref()) .color(Colour::RED) - .timestamp(Utc::now()) - }) - }) + .timestamp(Utc::now()), + ), + ) .await; }