#[cfg(feature = "self-update")] use { minreq, octocrab, self_replace, std::convert::Infallible, std::io::Write, std::os::unix::process::CommandExt, zip, }; use crate::Context; use crate::Error; /// Print version and build information #[poise::command(slash_command)] pub async fn version(ctx: Context<'_>) -> Result<(), Error> { ctx.say(format!( "Source:\n\ \tPackage version: {}\n\ \tCommit ID: {}\n\ \tCommit date: {}\n\ \tCommit author: {} ({})\n\ \tCommit message: {}\n\ Build:\n\ \tBuild date: {}\n\ \tBuild timestamp: {}\n\ \tTarget triple: {}\n\ \trustc version: {}\n", env!("CARGO_PKG_VERSION"), env!("GIT_COMMIT_ID_SHORT"), env!("GIT_COMMIT_DATE"), env!("GIT_COMMIT_AUTHOR_NAME"), env!("GIT_COMMIT_AUTHOR_EMAIL"), env!("GIT_COMMIT_MSG"), env!("VERGEN_BUILD_DATE"), env!("VERGEN_BUILD_TIMESTAMP"), env!("VERGEN_CARGO_TARGET_TRIPLE"), env!("VERGEN_RUSTC_SEMVER"), )) .await?; Ok(()) } #[cfg(feature = "self-update")] /// Update the bot remotely (requires Github CI) #[poise::command(slash_command, owners_only, hide_in_help)] pub async fn update( ctx: Context<'_>, #[description = "Whether to skip the update check"] override_check: bool, ) -> Result<(), Error> { ctx.defer().await?; // Check if the current commit hash is different from HEAD let head: octocrab::models::repos::Ref = octocrab::instance() .get( "/repos/shibedrill/shibe-bot/git/refs/heads/main", None::<&octocrab::models::Repository>, ) .await?; if let octocrab::models::repos::Object::Commit { sha, url: _ } = head.object { if sha == env!("GIT_COMMIT_ID") { if override_check { info!("Update unnecessary, but check overridden."); ctx.say("Update unecessary, but check overridden. Updating.") .await?; let Err(what) = self_update(); error!("Update failed: {}", what); ctx.say(format!("Error occurred while updating: {}", what)) .await?; } else { info!("Update unnecessary: Commit ID of remote is same as compiled commit."); ctx.say("Update unnecessary.").await?; } } else { info!("Update required, latest commit hash: {}", sha); let Err(what) = self_update(); error!("Update failed: {}", what); ctx.say(format!("Error occurred while updating: {}", what)) .await?; } } else { ctx.say("Update failed: Object field in response is not a Commit.") .await?; error!("Checking for updates failed: Response field incorrect type"); } Ok(()) } /// Shut down the bot remotely #[poise::command(slash_command, owners_only, hide_in_help)] pub async fn shutdown(ctx: Context<'_>) -> Result<(), Error> { ctx.defer_ephemeral().await?; ctx.say("Shutting down...").await?; ctx.framework().shard_manager().shutdown_all().await; info!("Executed command `shutdown` successfully"); Ok(()) } /// Restart the bot remotely #[poise::command(slash_command, owners_only, hide_in_help)] pub async fn restart(ctx: Context<'_>) -> Result<(), Error> { ctx.defer_ephemeral().await?; ctx.say("Restarting...").await?; for shard in ctx.framework().shard_manager().shards_instantiated().await { ctx.framework().shard_manager().restart(shard).await; } info!("Executed command `restart` successfully"); Ok(()) } /// Say a specific message #[poise::command(slash_command, owners_only, hide_in_help)] pub async fn say( ctx: Context<'_>, #[description = "The message content to send"] what: String, #[description = "Whether to make it ephemeral"] ephemeral: Option, ) -> Result<(), Error> { if ephemeral == Some(true) { ctx.defer_ephemeral().await?; } ctx.say(what).await?; Ok(()) } #[cfg(feature = "self-update")] fn self_update() -> Result { let artifact_url = "https://nightly.link/shibedrill/shibe-bot/workflows/rust/main/artifact.zip"; let tempdir = tempfile::Builder::new().prefix("shibe-bot").tempdir()?; trace!("Created tempdir successfully: {}", tempdir.path().display()); let response = minreq::get(artifact_url).send()?; let mut dest = std::fs::File::create_new(tempdir.path().join("artifact.zip"))?; let content = response.as_bytes(); dest.write_all(content)?; trace!("Downloaded latest build artifact successfully"); let mut archive = zip::ZipArchive::new(dest)?; trace!("Created zip archive reader"); let mut zipped_bin = archive.by_index(0)?; let new_bin_path = tempdir.path().join("shibe-bot"); let mut new_bin = std::fs::File::create_new(&new_bin_path)?; trace!("Created new file for binary"); std::io::copy(&mut zipped_bin, &mut new_bin)?; trace!("Extracted binary successfully"); let new_command_path = std::env::current_exe()?; trace!("Testing file prior to replace"); { let _ = std::fs::File::open(&new_command_path)?; } self_replace::self_replace(&new_bin_path)?; trace!("Replaced self with new binary successfully"); trace!("Testing file after replace"); { let _ = std::fs::File::open(&new_command_path)?; } //let new_command_args: Vec<_> = std::env::args_os().skip(1).collect(); trace!( "Got current executable path successfully: {}", new_command_path.display() ); let mut command = std::process::Command::new("systemctl"); let command = command.arg("--user").arg("restart").arg("shibe-bot"); Err(Box::new(command.exec())) } mod test { #[cfg(test)] #[cfg(feature = "self-update")] use {crate::Error, std::convert::Infallible}; #[test] #[cfg(feature = "self-update")] fn test_self_update() -> Result { use crate::command::devel::self_update; use pretty_env_logger; pretty_env_logger::init(); self_update() } }