Add about command

This commit is contained in:
2025-01-30 03:28:45 +00:00
parent 85c93c8683
commit ae85cc9fb6
9 changed files with 135 additions and 7 deletions

View File

@@ -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"]

View File

@@ -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<R>(repository_provider: R) -> Framework<AppData<R>, AppError<R::BackendError>>
pub fn framework<R>(repository_provider: R, info: AppInfo) -> Framework<AppData<R>, AppError<R::BackendError>>
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::<AppData<R>, AppError<R::BackendError>> {

View File

@@ -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<R> {
repository_provider: R,
qualified_command_names: Vec<String>,
info: AppInfo,
}
#[derive(Debug, thiserror::Error)]
@@ -32,14 +34,14 @@ pub enum AppError<E> {
pub type AppContext<'a, R, E> = poise::ApplicationContext<'a, AppData<R>, AppError<E>>;
pub type AppCommand<R, E> = poise::Command<AppData<R>, AppError<E>>;
pub async fn start<R>(credentials: DiscordCredentials, repository_provider: R) -> Result<(), AppStartError>
pub async fn start<R>(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<R> AppData<R> {
pub fn qualified_command_names(&self) -> &[String] {
&self.qualified_command_names
}
pub fn info(&self) -> &AppInfo {
&self.info
}
}

View File

@@ -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,
}

View File

@@ -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?;
},
}

View File

@@ -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<R: RepositoryProvider + Send + Sync>(
ctx: AppContext<'_, R, R::BackendError>,
#[description = "Hide reply from other users. Defaults to True."] ephemeral: Option<bool>,
) -> Result<(), AppError<R::BackendError>> {
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(())
}

View File

@@ -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(),
]

View File

@@ -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<R>(
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<R>(ctx: &AppContext<'_, R, R::BackendError>) -> Result<String, AppError<R::BackendError>>
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)
}