Add about command
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -351,6 +351,7 @@ dependencies = [
|
|||||||
"serenity",
|
"serenity",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ serenity = "0.12.4"
|
|||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
url = "2.5.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mysql", "postgres", "sqlite"]
|
default = ["mysql", "postgres", "sqlite"]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use poise::Framework;
|
|||||||
use poise::FrameworkOptions;
|
use poise::FrameworkOptions;
|
||||||
use cipher_core::repository::RepositoryProvider;
|
use cipher_core::repository::RepositoryProvider;
|
||||||
|
|
||||||
|
use crate::cli::AppInfo;
|
||||||
use crate::commands;
|
use crate::commands;
|
||||||
|
|
||||||
use super::event_handler;
|
use super::event_handler;
|
||||||
@@ -9,7 +10,7 @@ use super::on_error;
|
|||||||
use super::AppData;
|
use super::AppData;
|
||||||
use super::AppError;
|
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
|
where
|
||||||
R: RepositoryProvider + Send + Sync + 'static,
|
R: RepositoryProvider + Send + Sync + 'static,
|
||||||
R::BackendError: Send + Sync,
|
R::BackendError: Send + Sync,
|
||||||
@@ -20,6 +21,7 @@ where
|
|||||||
let app_data = AppData {
|
let app_data = AppData {
|
||||||
repository_provider,
|
repository_provider,
|
||||||
qualified_command_names: commands::qualified_command_names(&commands),
|
qualified_command_names: commands::qualified_command_names(&commands),
|
||||||
|
info,
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = FrameworkOptions::<AppData<R>, AppError<R::BackendError>> {
|
let options = FrameworkOptions::<AppData<R>, AppError<R::BackendError>> {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use secrecy::ExposeSecret;
|
|||||||
use serenity::all::GatewayIntents;
|
use serenity::all::GatewayIntents;
|
||||||
use serenity::Client;
|
use serenity::Client;
|
||||||
|
|
||||||
|
use crate::cli::AppInfo;
|
||||||
use crate::cli::DiscordCredentials;
|
use crate::cli::DiscordCredentials;
|
||||||
|
|
||||||
mod event_handler;
|
mod event_handler;
|
||||||
@@ -19,6 +20,7 @@ pub enum AppStartError {
|
|||||||
pub struct AppData<R> {
|
pub struct AppData<R> {
|
||||||
repository_provider: R,
|
repository_provider: R,
|
||||||
qualified_command_names: Vec<String>,
|
qualified_command_names: Vec<String>,
|
||||||
|
info: AppInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[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 AppContext<'a, R, E> = poise::ApplicationContext<'a, AppData<R>, AppError<E>>;
|
||||||
pub type AppCommand<R, E> = poise::Command<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
|
where
|
||||||
R: RepositoryProvider + Send + Sync + 'static,
|
R: RepositoryProvider + Send + Sync + 'static,
|
||||||
R::BackendError: Send + Sync,
|
R::BackendError: Send + Sync,
|
||||||
for<'a> R::Repository<'a>: Send + Sync,
|
for<'a> R::Repository<'a>: Send + Sync,
|
||||||
{
|
{
|
||||||
let mut client = Client::builder(credentials.bot_token.expose_secret(), GatewayIntents::all())
|
let mut client = Client::builder(credentials.bot_token.expose_secret(), GatewayIntents::all())
|
||||||
.framework(framework::framework(repository_provider))
|
.framework(framework::framework(repository_provider, info))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let shard_manager = client.shard_manager.clone();
|
let shard_manager = client.shard_manager.clone();
|
||||||
@@ -67,4 +69,8 @@ impl<R> AppData<R> {
|
|||||||
pub fn qualified_command_names(&self) -> &[String] {
|
pub fn qualified_command_names(&self) -> &[String] {
|
||||||
&self.qualified_command_names
|
&self.qualified_command_names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn info(&self) -> &AppInfo {
|
||||||
|
&self.info
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use clap::Parser;
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use command::Command;
|
use command::Command;
|
||||||
use secrecy::SecretString;
|
use secrecy::SecretString;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod start;
|
pub mod start;
|
||||||
@@ -146,3 +147,58 @@ pub struct DiscordCredentials {
|
|||||||
)]
|
)]
|
||||||
pub bot_token: SecretString,
|
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 clap::Parser;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
|
|
||||||
|
use super::AppInfo;
|
||||||
use super::DatabaseCredentials;
|
use super::DatabaseCredentials;
|
||||||
use super::DiscordCredentials;
|
use super::DiscordCredentials;
|
||||||
|
|
||||||
@@ -14,6 +15,10 @@ pub struct Start {
|
|||||||
/// Credentials required to authenticate a bot with Discord.
|
/// Credentials required to authenticate a bot with Discord.
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub discord: DiscordCredentials,
|
pub discord: DiscordCredentials,
|
||||||
|
|
||||||
|
/// Information about the application
|
||||||
|
#[command(flatten)]
|
||||||
|
pub info: AppInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@@ -37,7 +42,7 @@ impl Start {
|
|||||||
cipher_database::mysql::run_pending_migrations(database_url)?;
|
cipher_database::mysql::run_pending_migrations(database_url)?;
|
||||||
let repository_provider = cipher_database::mysql::repository_provider(database_url).await?;
|
let repository_provider = cipher_database::mysql::repository_provider(database_url).await?;
|
||||||
log::info!("Starting discord application.");
|
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")]
|
#[cfg(feature = "postgres")]
|
||||||
crate::cli::DatabaseDialect::Postgres => {
|
crate::cli::DatabaseDialect::Postgres => {
|
||||||
@@ -45,7 +50,7 @@ impl Start {
|
|||||||
cipher_database::postgres::run_pending_migrations(database_url)?;
|
cipher_database::postgres::run_pending_migrations(database_url)?;
|
||||||
let repository_provider = cipher_database::postgres::repository_provider(database_url).await?;
|
let repository_provider = cipher_database::postgres::repository_provider(database_url).await?;
|
||||||
log::info!("Starting discord application.");
|
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")]
|
#[cfg(feature = "sqlite")]
|
||||||
crate::cli::DatabaseDialect::Sqlite => {
|
crate::cli::DatabaseDialect::Sqlite => {
|
||||||
@@ -53,7 +58,7 @@ impl Start {
|
|||||||
cipher_database::sqlite::run_pending_migrations(database_url)?;
|
cipher_database::sqlite::run_pending_migrations(database_url)?;
|
||||||
let repository_provider = cipher_database::sqlite::repository_provider(database_url).await?;
|
let repository_provider = cipher_database::sqlite::repository_provider(database_url).await?;
|
||||||
log::info!("Starting discord application.");
|
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;
|
use crate::app::AppCommand;
|
||||||
|
|
||||||
|
mod about;
|
||||||
mod help;
|
mod help;
|
||||||
mod profile;
|
mod profile;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ where
|
|||||||
R: RepositoryProvider + Send + Sync + 'static,
|
R: RepositoryProvider + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
vec![
|
vec![
|
||||||
|
about::about(),
|
||||||
help::help(),
|
help::help(),
|
||||||
profile::profile(),
|
profile::profile(),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use cipher_core::repository::RepositoryProvider;
|
use cipher_core::repository::RepositoryProvider;
|
||||||
use serenity::all::{Color, GuildId};
|
use serenity::all::{Color, GuildId};
|
||||||
|
|
||||||
use crate::app::{AppCommand, AppContext};
|
use crate::app::{AppCommand, AppContext, AppError};
|
||||||
|
|
||||||
pub async fn register_in_guilds<R>(
|
pub async fn register_in_guilds<R>(
|
||||||
serenity_ctx: &serenity::client::Context,
|
serenity_ctx: &serenity::client::Context,
|
||||||
@@ -33,3 +33,25 @@ where
|
|||||||
|
|
||||||
member.and_then(|m| m.colour(ctx)).unwrap_or(Color::BLURPLE)
|
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