Add about command
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -351,6 +351,7 @@ dependencies = [
|
||||
"serenity",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
33
cipher_discord_bot/src/commands/about.rs
Normal file
33
cipher_discord_bot/src/commands/about.rs
Normal 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(())
|
||||
}
|
||||
@@ -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(),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user