use std::{collections::HashMap, pin::Pin}; use anyhow::Result; use crate::{ api::FluxerApiHandler, error::{CommandHandlerError, FluxerRsError}, serde::types::message::MessageData, }; pub type Command = Box; pub struct CommandHandler { prefix: String, map: HashMap, } pub struct CommandFeedback<'a> { pub data: &'a MessageData, pub args: Vec<&'a str>, } /// A wrapper trait high level commands must implement to be able to be registered and stored in the command handler's hashmap /// Mainly used as a quality of life so users don't have to manually pin the returned future pub trait CommandTrait: Send + Sync + 'static { fn execute<'a>( &'a self, api: &'a FluxerApiHandler, feedback: &'a CommandFeedback<'a>, ) -> impl Future> + Send + 'a; } /// The main trait that is actually used to store high level commands in the hashmap pub trait PinnedCommandTrait: Send + Sync { fn execute<'a>( &'a self, api: &'a FluxerApiHandler, // TODO // Maybe make it owned instead of a reference feedback: &'a CommandFeedback<'a>, ) -> Pin> + Send + 'a>>; } /// Helper function for people that prefer to use the register command function instead of the macro pub fn cmd(cmd_name: &str, cmd_handler: impl CommandTrait) -> (String, Command) { (cmd_name.into(), Box::new(cmd_handler)) } /// The bridge between the wrapper and the main command trait impl PinnedCommandTrait for C { fn execute<'a>( &'a self, api: &'a FluxerApiHandler, feedback: &'a CommandFeedback<'a>, ) -> Pin> + Send + 'a>> { Box::pin(CommandTrait::execute(self, api, feedback)) } } impl CommandHandler { pub fn init(prefix: impl Into) -> Self { CommandHandler { prefix: prefix.into(), map: HashMap::new(), } } /// Register a single command into the command handler pub fn register_command( &mut self, command_name: impl Into, handler: impl CommandTrait, ) { self.map.insert(command_name.into(), Box::new(handler)); } /// Register a list of commands into the command handler pub fn register_commands(&mut self, commands: Vec<(String, Command)>) { for (command_name, handler) in commands { self.map.insert(command_name, handler); } } pub async fn handle( &self, data: &MessageData, api: &FluxerApiHandler, ) -> Result<(), FluxerRsError> { let content = match &data.content { Some(data) => data, None => return Ok(()), }; // Checking whether the message content really starts with the prefix and if it's not just the prefix itself if !content.starts_with(&self.prefix) || content.len() < 2 { return Ok(()); } let (cmd, args) = Self::command_data(&self.prefix, content)?; let split_args = Self::split_args(args); match self.map.get(cmd) { Some(handler) => { handler .execute( api, &CommandFeedback { data, args: split_args, }, ) .await } None => Err(FluxerRsError::CommandHandlerError( crate::error::CommandHandlerError::UnknownCommand(args.to_string()), )), } } pub fn command_data<'a>( prefix: &str, content: &'a str, ) -> Result<(&'a str, &'a str), FluxerRsError> { content .strip_prefix(prefix) .ok_or_else(|| CommandHandlerError::Custom("Failed to remove command prefix".into())) .map(|stripped_body| match stripped_body.split_once(" ") { Some((cmd, args)) => (cmd, args), None => (stripped_body, ""), }) .map_err(FluxerRsError::from) } /// Helper function for argument parsing fn split_args(body: &str) -> Vec<&str> { let mut args: Vec<&str> = Vec::new(); let mut quote: bool = false; let mut last_char: char = ' '; let body = body.trim(); let mut start: usize = 0; for (i, elem) in body.char_indices() { if !quote && last_char == '\"' && elem != ' ' { start = i; } match elem { ' ' => { if !quote && last_char != '\"' { args.push(&body[start..i]); } if !quote { start = i + 1; } } '\"' => { if !quote { start = i + 1; } else { args.push(&body[start..i]); } quote = !quote; } _ => {} } if i + 1 == body.len() && elem != '\"' { args.push(&body[start..i + 1]); } last_char = elem; } args } }