From ae85cc9fb60853a187247b601f4bc447b431a652 Mon Sep 17 00:00:00 2001 From: Kappeh Date: Thu, 30 Jan 2025 03:28:45 +0000 Subject: [PATCH] Add about command --- Cargo.lock | 1 + cipher_discord_bot/Cargo.toml | 1 + cipher_discord_bot/src/app/framework.rs | 4 +- cipher_discord_bot/src/app/mod.rs | 10 ++++- cipher_discord_bot/src/cli/mod.rs | 56 ++++++++++++++++++++++++ cipher_discord_bot/src/cli/start.rs | 11 +++-- cipher_discord_bot/src/commands/about.rs | 33 ++++++++++++++ cipher_discord_bot/src/commands/mod.rs | 2 + cipher_discord_bot/src/utils.rs | 24 +++++++++- 9 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 cipher_discord_bot/src/commands/about.rs diff --git a/Cargo.lock b/Cargo.lock index 3bf3f12..0f4d35a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "serenity", "thiserror 2.0.11", "tokio", + "url", ] [[package]] diff --git a/cipher_discord_bot/Cargo.toml b/cipher_discord_bot/Cargo.toml index f9065a0..c7617bb 100644 --- a/cipher_discord_bot/Cargo.toml +++ b/cipher_discord_bot/Cargo.toml @@ -17,6 +17,7 @@ serenity = "0.12.4" thiserror = "2.0.11" tokio = { version = "1.43.0", features = ["full"] } futures = "0.3.31" +url = "2.5.4" [features] default = ["mysql", "postgres", "sqlite"] diff --git a/cipher_discord_bot/src/app/framework.rs b/cipher_discord_bot/src/app/framework.rs index 19efc09..e6aa3b9 100644 --- a/cipher_discord_bot/src/app/framework.rs +++ b/cipher_discord_bot/src/app/framework.rs @@ -2,6 +2,7 @@ use poise::Framework; use poise::FrameworkOptions; use cipher_core::repository::RepositoryProvider; +use crate::cli::AppInfo; use crate::commands; use super::event_handler; @@ -9,7 +10,7 @@ use super::on_error; use super::AppData; use super::AppError; -pub fn framework(repository_provider: R) -> Framework, AppError> +pub fn framework(repository_provider: R, info: AppInfo) -> Framework, AppError> where R: RepositoryProvider + Send + Sync + 'static, R::BackendError: Send + Sync, @@ -20,6 +21,7 @@ where let app_data = AppData { repository_provider, qualified_command_names: commands::qualified_command_names(&commands), + info, }; let options = FrameworkOptions::, AppError> { diff --git a/cipher_discord_bot/src/app/mod.rs b/cipher_discord_bot/src/app/mod.rs index bae00d0..8259f62 100644 --- a/cipher_discord_bot/src/app/mod.rs +++ b/cipher_discord_bot/src/app/mod.rs @@ -4,6 +4,7 @@ use secrecy::ExposeSecret; use serenity::all::GatewayIntents; use serenity::Client; +use crate::cli::AppInfo; use crate::cli::DiscordCredentials; mod event_handler; @@ -19,6 +20,7 @@ pub enum AppStartError { pub struct AppData { repository_provider: R, qualified_command_names: Vec, + info: AppInfo, } #[derive(Debug, thiserror::Error)] @@ -32,14 +34,14 @@ pub enum AppError { pub type AppContext<'a, R, E> = poise::ApplicationContext<'a, AppData, AppError>; pub type AppCommand = poise::Command, AppError>; -pub async fn start(credentials: DiscordCredentials, repository_provider: R) -> Result<(), AppStartError> +pub async fn start(credentials: DiscordCredentials, info: AppInfo, repository_provider: R) -> Result<(), AppStartError> where R: RepositoryProvider + Send + Sync + 'static, R::BackendError: Send + Sync, for<'a> R::Repository<'a>: Send + Sync, { let mut client = Client::builder(credentials.bot_token.expose_secret(), GatewayIntents::all()) - .framework(framework::framework(repository_provider)) + .framework(framework::framework(repository_provider, info)) .await?; let shard_manager = client.shard_manager.clone(); @@ -67,4 +69,8 @@ impl AppData { pub fn qualified_command_names(&self) -> &[String] { &self.qualified_command_names } + + pub fn info(&self) -> &AppInfo { + &self.info + } } diff --git a/cipher_discord_bot/src/cli/mod.rs b/cipher_discord_bot/src/cli/mod.rs index 2dbb63c..41ef033 100644 --- a/cipher_discord_bot/src/cli/mod.rs +++ b/cipher_discord_bot/src/cli/mod.rs @@ -5,6 +5,7 @@ use clap::Parser; use clap::ValueEnum; use command::Command; use secrecy::SecretString; +use url::Url; pub mod command; pub mod start; @@ -146,3 +147,58 @@ pub struct DiscordCredentials { )] pub bot_token: SecretString, } + +/// Information about the application +#[derive(Clone, Debug, Parser)] +pub struct AppInfo { + /// The displayed name of the application + #[arg( + short = None, + long = "app-name", + env = "APP_NAME", + default_value = env!("CARGO_PKG_NAME"), + )] + pub name: String, + + // The displayed version of the application + #[arg( + short = None, + long = "app-version", + env = "APP_VERSION", + default_value = env!("CARGO_PKG_VERSION"), + )] + pub version: String, + + /// The displayed description of the application + #[arg( + short = None, + long = "app-description", + env = "APP_DESCRIPTION", + default_value = env!("CARGO_PKG_DESCRIPTION"), + )] + pub description: String, + + /// The displayed about title of the application + #[arg( + short = None, + long = "about-title", + env = "ABOUT_TITLE", + )] + pub about_title: String, + + /// The displayed about description of the application + #[arg( + short = None, + long = "about-description", + env = "ABOUT_DESCRIPTION", + )] + pub about_description: String, + + /// The url used to direct users to the source code + #[arg( + short = None, + long = "source-code-url", + env = "SOURCE_CODE_URL", + )] + pub source_code_url: Url, +} diff --git a/cipher_discord_bot/src/cli/start.rs b/cipher_discord_bot/src/cli/start.rs index b725e0d..e6a6b98 100644 --- a/cipher_discord_bot/src/cli/start.rs +++ b/cipher_discord_bot/src/cli/start.rs @@ -1,6 +1,7 @@ use clap::Parser; use secrecy::ExposeSecret; +use super::AppInfo; use super::DatabaseCredentials; use super::DiscordCredentials; @@ -14,6 +15,10 @@ pub struct Start { /// Credentials required to authenticate a bot with Discord. #[command(flatten)] pub discord: DiscordCredentials, + + /// Information about the application + #[command(flatten)] + pub info: AppInfo, } #[derive(Debug, thiserror::Error)] @@ -37,7 +42,7 @@ impl Start { cipher_database::mysql::run_pending_migrations(database_url)?; let repository_provider = cipher_database::mysql::repository_provider(database_url).await?; log::info!("Starting discord application."); - crate::app::start(self.discord, repository_provider).await?; + crate::app::start(self.discord, self.info, repository_provider).await?; }, #[cfg(feature = "postgres")] crate::cli::DatabaseDialect::Postgres => { @@ -45,7 +50,7 @@ impl Start { cipher_database::postgres::run_pending_migrations(database_url)?; let repository_provider = cipher_database::postgres::repository_provider(database_url).await?; log::info!("Starting discord application."); - crate::app::start(self.discord, repository_provider).await?; + crate::app::start(self.discord, self.info, repository_provider).await?; }, #[cfg(feature = "sqlite")] crate::cli::DatabaseDialect::Sqlite => { @@ -53,7 +58,7 @@ impl Start { cipher_database::sqlite::run_pending_migrations(database_url)?; let repository_provider = cipher_database::sqlite::repository_provider(database_url).await?; log::info!("Starting discord application."); - crate::app::start(self.discord, repository_provider).await?; + crate::app::start(self.discord, self.info, repository_provider).await?; }, } diff --git a/cipher_discord_bot/src/commands/about.rs b/cipher_discord_bot/src/commands/about.rs new file mode 100644 index 0000000..10e61c2 --- /dev/null +++ b/cipher_discord_bot/src/commands/about.rs @@ -0,0 +1,33 @@ +use cipher_core::repository::RepositoryProvider; +use poise::CreateReply; +use serenity::all::CreateEmbed; + +use crate::app::AppContext; +use crate::app::AppError; +use crate::utils; + +/// Show information about the bot. +#[poise::command(slash_command)] +pub async fn about( + ctx: AppContext<'_, R, R::BackendError>, + #[description = "Hide reply from other users. Defaults to True."] ephemeral: Option, +) -> Result<(), AppError> { + let info = ctx.data().info(); + + let avatar_url = utils::bot_avatar_url(&ctx).await?; + + let embed = CreateEmbed::new() + .title(&info.about_title) + .description(&info.about_description) + .thumbnail(avatar_url) + .field("Source Code", info.source_code_url.as_str(), false) + .color(utils::bot_color(&ctx).await); + + let reply = CreateReply::default() + .embed(embed) + .ephemeral(ephemeral.unwrap_or(true)); + + ctx.send(reply).await?; + + Ok(()) +} diff --git a/cipher_discord_bot/src/commands/mod.rs b/cipher_discord_bot/src/commands/mod.rs index 7a0486a..514229c 100644 --- a/cipher_discord_bot/src/commands/mod.rs +++ b/cipher_discord_bot/src/commands/mod.rs @@ -2,6 +2,7 @@ use cipher_core::repository::RepositoryProvider; use crate::app::AppCommand; +mod about; mod help; mod profile; @@ -10,6 +11,7 @@ where R: RepositoryProvider + Send + Sync + 'static, { vec![ + about::about(), help::help(), profile::profile(), ] diff --git a/cipher_discord_bot/src/utils.rs b/cipher_discord_bot/src/utils.rs index 4eeccf6..25cdd66 100644 --- a/cipher_discord_bot/src/utils.rs +++ b/cipher_discord_bot/src/utils.rs @@ -1,7 +1,7 @@ use cipher_core::repository::RepositoryProvider; use serenity::all::{Color, GuildId}; -use crate::app::{AppCommand, AppContext}; +use crate::app::{AppCommand, AppContext, AppError}; pub async fn register_in_guilds( serenity_ctx: &serenity::client::Context, @@ -33,3 +33,25 @@ where member.and_then(|m| m.colour(ctx)).unwrap_or(Color::BLURPLE) } + +pub async fn bot_avatar_url(ctx: &AppContext<'_, R, R::BackendError>) -> Result> +where + R: RepositoryProvider + Send + Sync, +{ + let member_avatar_url = match ctx.guild_id() { + Some(guild) => guild.member(ctx, ctx.framework.bot_id).await?.avatar_url(), + None => None, + }; + + let avatar_url = match member_avatar_url { + Some(url) => url, + None => { + let user = ctx.framework.bot_id.to_user(ctx).await?; + user.avatar_url() + .or_else(|| user.static_avatar_url()) + .unwrap_or_else(|| user.default_avatar_url()) + }, + }; + + Ok(avatar_url) +}