Add prefix customization command

This commit is contained in:
2025-08-14 18:30:33 -03:00
parent eb6dcb4604
commit 53d61cfbbb
7 changed files with 143 additions and 7 deletions
+30
View File
@@ -253,6 +253,36 @@ impl Server {
} }
} }
pub async fn get_or_create_by_id(
id: u64,
session: &Session,
http: &Client,
) -> Result<Self, DatabaseOrDiscordError> {
let server = Server::get_by_id(id, session)
.await
.map_err(DatabaseOrDiscordError::Database)?;
if let Some(server) = server {
Ok(server)
} else {
let server: Server = http
.guild(Id::new(id))
.await
.map_err(DatabaseOrDiscordError::Discord)?
.model()
.await
.map_err(|_| DatabaseOrDiscordError::DiscordDeserializationFailed)?
.into();
server
.insert(session)
.await
.map_err(DatabaseOrDiscordError::Database)?;
Ok(server)
}
}
pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> { pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> {
session session
.query_unpaged( .query_unpaged(
+4
View File
@@ -31,6 +31,10 @@ impl FromArguments for String {
} }
} }
/// Consumes the entire remaining string as a single argument. If no string is remaining, it
/// returns an empty string.
///
/// Parsing this argument type never fails.
pub struct GreedyString(pub String); pub struct GreedyString(pub String);
impl FromArguments for GreedyString { impl FromArguments for GreedyString {
+7 -1
View File
@@ -20,6 +20,9 @@ pub enum CommandError {
#[error("A database operation failed due to an inconsistent state")] #[error("A database operation failed due to an inconsistent state")]
DatabaseInconsistentState, DatabaseInconsistentState,
#[error("The command was used in DMs, but it can only be used in servers")]
ServerCommandRunInDMs,
} }
#[async_trait] #[async_trait]
@@ -66,7 +69,10 @@ pub fn get_command_parts<'a>(prefix: &str, message: &'a str) -> Option<CommandPa
.to_ascii_lowercase() .to_ascii_lowercase()
.starts_with(&prefix.to_ascii_lowercase()) .starts_with(&prefix.to_ascii_lowercase())
{ {
let message = message_trimmed_start.split_at(prefix.len()).1.trim_ascii_start(); let message = message_trimmed_start
.split_at(prefix.len())
.1
.trim_ascii_start();
match message.split_once(" ") { match message.split_once(" ") {
Some((name, args)) => Some(CommandParts::new(name, args)), Some((name, args)) => Some(CommandParts::new(name, args)),
+4 -1
View File
@@ -32,7 +32,10 @@ impl<T: Send + Sync + 'static> OnMessageHandler<T> for CommandRunner {
if let Some(command) = command { if let Some(command) = command {
command command
.execute(CommandContext::new(event, rt.clone(), &prefixes[0]), parts.args) .execute(
CommandContext::new(event, rt.clone(), &prefixes[0]),
parts.args,
)
.await?; .await?;
break; break;
+83
View File
@@ -0,0 +1,83 @@
use async_trait::async_trait;
use crate::{
constants::DEFAULT_PREFIX,
database::models::Server,
discord::sanitizing::SanitizeDiscordMarkdown,
framework::commands::{
Command, CommandError,
arguments::{Argument, GreedyString, ParseArguments},
context::CommandContext,
},
state::State,
};
#[derive(Debug)]
pub struct PrefixCommand;
#[async_trait]
impl Command<State> for PrefixCommand {
fn name(&self) -> &'static str {
"prefix"
}
fn aliases(&self) -> &'static [&'static str] {
&["p"]
}
fn description(&self) -> &'static str {
"Sets the command prefix for the server."
}
fn arguments(&self) -> &'static [Argument] {
&[Argument {
name: "prefix",
is_optional: true,
is_greedy: true,
}]
}
async fn execute(
&self,
ctx: CommandContext<'_, State>,
args: &str,
) -> Result<(), CommandError> {
let prefix: GreedyString = ParseArguments::parse(args).unwrap();
let mut prefix = prefix.trim();
let mut server = Server::get_or_create_by_id(
ctx.event
.guild_id
.ok_or(CommandError::ServerCommandRunInDMs)?
.get(),
&ctx.rt.state.db,
&ctx.rt.http,
)
.await
.inspect_err(|e| log::error!("Failed to get or create server: {e}"))
.map_err(|_| CommandError::DatabaseQueryFailed)?;
if prefix.is_empty() {
prefix = DEFAULT_PREFIX;
}
server.prefix = prefix.to_string();
server
.upsert(&ctx.rt.state.db)
.await
.map_err(|_| CommandError::DatabaseQueryFailed)?;
ctx.send()
.content(format!(
"✅ Prefix updated! Run `{}help` to see the new prefix.",
prefix.sanitize(false)
))
.await
.map_err(|e| {
log::error!("Failed to send prefix update message: {e}");
CommandError::MessageSendingFailed
})?;
Ok(())
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
//! Adding, updating, and removing servers from the database. //! Syncing servers with the database and server configuration commands.
//! //!
//! This sub-module is responsible for registering servers in the database, updating them when they //! This sub-module is responsible for registering servers in the database, updating them when they
//! update, and removing them when they get deleted or Payphone is kicked. //! update, and removing them when they get deleted or Payphone is kicked.
pub mod commands;
pub mod events; pub mod events;
+13 -4
View File
@@ -116,18 +116,26 @@ use tower_http::services::ServeDir;
use twilight_gateway::Intents; use twilight_gateway::Intents;
use crate::{ use crate::{
cli::{Args, Commands}, constants::DEFAULT_PREFIX, database::models::Server, framework::{ cli::{Args, Commands},
constants::DEFAULT_PREFIX,
database::models::Server,
framework::{
bot::{Bot, PrefixGetter}, bot::{Bot, PrefixGetter},
runtime::Runtime, runtime::Runtime,
}, groups::{ },
groups::{
about::{commands::HelpCommand, components::HelpGoBackButton}, about::{commands::HelpCommand, components::HelpGoBackButton},
calls::{ calls::{
commands::{CallCommand, HangupCommand, SkipCommand}, commands::{CallCommand, HangupCommand, SkipCommand},
events::MessageRedirector, events::MessageRedirector,
}, },
gateway::events::{ReadyHandler, ReconnectHandler}, gateway::events::{ReadyHandler, ReconnectHandler},
servers::events::{ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler}, servers::{
}, state::State commands::PrefixCommand,
events::{ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler},
},
},
state::State,
}; };
mod cli; mod cli;
@@ -217,6 +225,7 @@ async fn start(token: String, database_addr: String) {
.command(CallCommand::new()) .command(CallCommand::new())
.command(SkipCommand) .command(SkipCommand)
.command(HangupCommand) .command(HangupCommand)
.command(PrefixCommand)
.component(HelpGoBackButton) .component(HelpGoBackButton)
.on_ready(ReadyHandler) .on_ready(ReadyHandler)
.on_reconnect(ReconnectHandler) .on_reconnect(ReconnectHandler)