diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a6722239..07b0d98e6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -386,7 +386,7 @@ workflows: - workspace-fmt matrix: parameters: - framework: ["web-actix-web", "web-axum", "web-rocket", "web-poem", "web-thruster", "web-tide", "web-tower","web-warp", "web-salvo", "bot-serenity"] + framework: ["web-actix-web", "web-axum", "web-rocket", "web-poem", "web-thruster", "web-tide", "web-tower","web-warp", "web-salvo", "bot-serenity", "bot-poise"] - check-standalone: matrix: parameters: diff --git a/Cargo.lock b/Cargo.lock index 3c2959bc4..cbf21abc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2178,6 +2178,7 @@ dependencies = [ "hashbrown", "lock_api", "parking_lot_core 0.9.3", + "serde", ] [[package]] @@ -4404,6 +4405,37 @@ dependencies = [ "syn 1.0.104", ] +[[package]] +name = "poise" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca787e4e516076de1995a83ee05fbdfed71d072ea0da3df018318db42a87803" +dependencies = [ + "async-trait", + "derivative", + "futures-core", + "futures-util", + "log", + "once_cell", + "parking_lot 0.12.1", + "poise_macros", + "regex", + "serenity", + "tokio", +] + +[[package]] +name = "poise_macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b80c1f4e04114527f9d41ed6bb31707a095276f51bb0aef3ca11f062b25a67c4" +dependencies = [ + "darling 0.14.1", + "proc-macro2 1.0.47", + "quote 1.0.21", + "syn 1.0.104", +] + [[package]] name = "polling" version = "2.2.0" @@ -5667,12 +5699,16 @@ dependencies = [ "bitflags", "bytes 1.3.0", "cfg-if 1.0.0", + "chrono", + "dashmap", "flate2", "futures", "mime", "mime_guess", + "parking_lot 0.12.1", "percent-encoding", "reqwest", + "rustversion", "serde", "serde-value", "serde_json", @@ -5970,6 +6006,7 @@ dependencies = [ "num_cpus", "pipe", "poem", + "poise", "portpicker", "rocket", "salvo", diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index bae3db8ce..6bf4d522c 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -148,37 +148,40 @@ pub struct RunArgs { #[derive(Parser, Debug)] pub struct InitArgs { /// Initialize with actix-web framework - #[clap(long="actix-web", conflicts_with_all = &["axum", "rocket", "tide", "tower", "poem", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long="actix-web", conflicts_with_all = &["axum", "rocket", "tide", "tower", "poem", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub actix_web: bool, /// Initialize with axum framework - #[clap(long, conflicts_with_all = &["actix-web","rocket", "tide", "tower", "poem", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","rocket", "tide", "tower", "poem", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub axum: bool, /// Initialize with rocket framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "tide", "tower", "poem", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "tide", "tower", "poem", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub rocket: bool, /// Initialize with tide framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tower", "poem", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tower", "poem", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub tide: bool, /// Initialize with tower framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "poem", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "poem", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub tower: bool, /// Initialize with poem framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "serenity", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "serenity", "poise", "warp", "salvo", "thruster", "no-framework"])] pub poem: bool, /// Initialize with salvo framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "serenity", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "serenity", "poise", "thruster", "no-framework"])] pub salvo: bool, /// Initialize with serenity framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "poise", "salvo", "thruster", "no-framework"])] pub serenity: bool, + /// Initialize with poise framework + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "serenity", "salvo", "thruster", "no-framework"])] + pub poise: bool, /// Initialize with warp framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "serenity", "salvo", "thruster", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "serenity", "poise", "salvo", "thruster", "no-framework"])] pub warp: bool, /// Initialize with thruster framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "salvo", "serenity", "no-framework"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "salvo", "serenity", "poise", "no-framework"])] pub thruster: bool, /// Initialize without a framework - #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "salvo", "serenity", "thruster"])] + #[clap(long, conflicts_with_all = &["actix-web","axum", "rocket", "tide", "tower", "poem", "warp", "salvo", "serenity", "poise", "thruster"])] pub no_framework: bool, /// Whether to create the environment for this project on Shuttle #[clap(long)] @@ -209,6 +212,8 @@ impl InitArgs { Some(Framework::Poem) } else if self.salvo { Some(Framework::Salvo) + } else if self.poise { + Some(Framework::Poise) } else if self.serenity { Some(Framework::Serenity) } else if self.warp { @@ -257,6 +262,7 @@ mod tests { poem: false, salvo: false, serenity: false, + poise: false, warp: false, thruster: false, no_framework: false, @@ -274,6 +280,7 @@ mod tests { "poem" => init_args.poem = true, "salvo" => init_args.salvo = true, "serenity" => init_args.serenity = true, + "poise" => init_args.poise = true, "warp" => init_args.warp = true, "thruster" => init_args.thruster = true, "none" => init_args.no_framework = true, diff --git a/cargo-shuttle/src/init.rs b/cargo-shuttle/src/init.rs index ea09f305a..b36c1c52c 100644 --- a/cargo-shuttle/src/init.rs +++ b/cargo-shuttle/src/init.rs @@ -20,6 +20,7 @@ pub enum Framework { Poem, Salvo, Serenity, + Poise, Warp, Thruster, None, @@ -39,6 +40,7 @@ impl Framework { Framework::Poem => Box::new(ShuttleInitPoem), Framework::Salvo => Box::new(ShuttleInitSalvo), Framework::Serenity => Box::new(ShuttleInitSerenity), + Framework::Poise => Box::new(ShuttleInitPoise), Framework::Warp => Box::new(ShuttleInitWarp), Framework::Thruster => Box::new(ShuttleInitThruster), Framework::None => Box::new(ShuttleInitNoOp), @@ -446,6 +448,106 @@ impl ShuttleInit for ShuttleInitSerenity { } } +pub struct ShuttleInitPoise; + +impl ShuttleInit for ShuttleInitPoise { + fn set_cargo_dependencies( + &self, + dependencies: &mut Table, + manifest_path: &Path, + url: &Url, + get_dependency_version_fn: GetDependencyVersionFn, + ) { + set_inline_table_dependency_features( + "shuttle-service", + dependencies, + vec!["bot-poise".to_string()], + ); + + set_key_value_dependency_version( + "anyhow", + dependencies, + manifest_path, + url, + false, + get_dependency_version_fn, + ); + + set_key_value_dependency_version( + "poise", + dependencies, + manifest_path, + url, + false, + get_dependency_version_fn, + ); + + set_key_value_dependency_version( + "shuttle-secrets", + dependencies, + manifest_path, + url, + false, + get_dependency_version_fn, + ); + + set_key_value_dependency_version( + "tracing", + dependencies, + manifest_path, + url, + false, + get_dependency_version_fn, + ); + } + + fn get_boilerplate_code_for_framework(&self) -> &'static str { + indoc! {r#" + use anyhow::Context as _; + use poise::serenity_prelude as serenity; + use shuttle_secrets::SecretStore; + use shuttle_service::ShuttlePoise; + + struct Data {} // User data, which is stored and accessible in all command invocations + type Error = Box; + type Context<'a> = poise::Context<'a, Data, Error>; + + /// Responds with "world!" + #[poise::command(slash_command)] + async fn hello(ctx: Context<'_>) -> Result<(), Error> { + ctx.say("world!").await?; + Ok(()) + } + + #[shuttle_service::main] + async fn poise(#[shuttle_secrets::Secrets] secret_store: SecretStore) -> ShuttlePoise { + // Get the discord token set in `Secrets.toml` + let discord_token = secret_store + .get("DISCORD_TOKEN") + .context("'DISCORD_TOKEN' was not found")?; + + let framework = poise::Framework::builder() + .options(poise::FrameworkOptions { + commands: vec![hello()], + ..Default::default() + }) + .token(discord_token) + .intents(serenity::GatewayIntents::non_privileged()) + .setup(|ctx, _ready, framework| { + Box::pin(async move { + poise::builtins::register_globally(ctx, &framework.options().commands).await?; + Ok(Data {}) + }) + }) + .build() + .await + .map_err(shuttle_service::error::CustomError::new)?; + + Ok(framework) + }"#} + } +} + pub struct ShuttleInitTower; impl ShuttleInit for ShuttleInitTower { @@ -1125,6 +1227,41 @@ mod shuttle_init_tests { assert_eq!(cargo_toml.to_string(), expected); } + #[test] + fn test_set_cargo_dependencies_poise() { + let mut cargo_toml = cargo_toml_factory(); + let dependencies = cargo_toml["dependencies"].as_table_mut().unwrap(); + let manifest_path = PathBuf::new(); + let url = Url::parse("https://shuttle.rs").unwrap(); + + set_inline_table_dependency_version( + "shuttle-service", + dependencies, + &manifest_path, + &url, + false, + mock_get_latest_dependency_version, + ); + + ShuttleInitPoise.set_cargo_dependencies( + dependencies, + &manifest_path, + &url, + mock_get_latest_dependency_version, + ); + + let expected = indoc! {r#" + [dependencies] + shuttle-service = { version = "1.0", features = ["bot-poise"] } + anyhow = "1.0" + poise = "1.0" + shuttle-secrets = "1.0" + tracing = "1.0" + "#}; + + assert_eq!(cargo_toml.to_string(), expected); + } + #[test] fn test_set_cargo_dependencies_warp() { let mut cargo_toml = cargo_toml_factory(); diff --git a/service/Cargo.toml b/service/Cargo.toml index 0fdc04f69..164c561c6 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -30,6 +30,7 @@ rocket = { version = "0.5.0-rc.2", optional = true } salvo = { version = "0.37.5", optional = true } serde_json = { workspace = true } serenity = { version = "0.11.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"], optional = true } +poise = { version = "0.5.2", optional = true } sync_wrapper = { version = "0.1.1", optional = true } thiserror = { workspace = true } thruster = { version = "1.3.0", optional = true } @@ -76,4 +77,5 @@ web-poem = ["poem"] web-salvo = ["salvo"] bot-serenity = ["serenity"] +bot-poise = ["poise"] web-warp = ["warp"] diff --git a/service/src/lib.rs b/service/src/lib.rs index aafe1f887..5f6a3dbb9 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -264,6 +264,7 @@ extern crate shuttle_codegen; /// | `ShuttlePoem` | web-poem | [poem](https://docs.rs/poem/1.3.35) | 1.3.35 | [GitHub](https://github.com/shuttle-hq/examples/tree/main/poem/hello-world) | /// | `Result` | web-tower | [tower](https://docs.rs/tower/0.4.12) | 0.14.12 | [GitHub](https://github.com/shuttle-hq/examples/tree/main/tower/hello-world) | /// | `ShuttleSerenity` | bot-serenity | [serenity](https://docs.rs/serenity/0.11.5) | 0.11.5 | [GitHub](https://github.com/shuttle-hq/examples/tree/main/serenity/hello-world) | +/// | `ShuttlePoise` | bot-poise | [poise](https://docs.rs/poise/0.5.2) | 0.5.2 | [GitHub](https://github.com/shuttle-hq/examples/tree/main/poise/hello-world) | /// | `ShuttleActixWeb` | web-actix-web| [actix-web](https://docs.rs/actix-web/4.2.1)| 4.2.1 | [GitHub](https://github.com/shuttle-hq/examples/tree/main/actix-web/hello-world) | /// @@ -661,5 +662,22 @@ impl Service for serenity::Client { #[cfg(feature = "bot-serenity")] pub type ShuttleSerenity = Result; +#[cfg(feature = "bot-poise")] +#[async_trait] +impl Service for std::sync::Arc> +where + T: std::marker::Send + std::marker::Sync + 'static, + E: std::marker::Send + std::marker::Sync + 'static, +{ + async fn bind(mut self: Box, _addr: SocketAddr) -> Result<(), error::Error> { + self.start().await.map_err(error::CustomError::new)?; + + Ok(()) + } +} + +#[cfg(feature = "bot-poise")] +pub type ShuttlePoise = Result>, Error>; + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const NAME: &str = env!("CARGO_PKG_NAME");