use crate::history::MessageHistory; use crate::ExecutorConnection; use async_trait::async_trait; use fancy_regex::{Captures, Regex}; use std::collections::HashMap; use tokio::sync::{mpsc, Mutex}; fn dissect<'a>(prefixes: &[String], str: &'a str) -> Option<(&'a str, Option<&'a str>)> { for prefix in prefixes { if let Some(str) = str.strip_prefix(prefix) { return if let Some(o) = str.find(' ') { Some((&str[..o], Some(&str[o + 1..]))) } else { Some((str, None)) }; } } None } #[async_trait] pub trait Trigger { async fn execute<'a>( &mut self, msg: Context<'a>, captures: Captures<'a>, ) -> anyhow::Result; } #[async_trait] pub trait Command { async fn execute(&mut self, msg: Context<'_>) -> anyhow::Result; } pub struct Context<'a> { pub history: &'a MessageHistory, pub author: &'a str, // in case of triggers, this is always Some(...) pub content: Option<&'a str>, pub db: &'a ExecutorConnection, } pub struct Bot anyhow::Result<()>> { history: MessageHistory, prefixes: Vec, db: ExecutorConnection, commands: HashMap>>, triggers: Vec<(Regex, Box>)>, sendmsg: SF, } impl anyhow::Result<()>> Bot { pub fn new(prefixes: Vec, db: ExecutorConnection, hdepth: usize, sendmsg: SF) -> Self { Bot { history: MessageHistory::new(hdepth), commands: HashMap::new(), triggers: Vec::new(), prefixes, db, sendmsg, } } pub fn add_command(&mut self, name: String, cmd: C) { self.commands.insert(name, Box::new(Mutex::new(cmd))); } pub fn add_trigger(&mut self, regex: Regex, cmd: C) { self.triggers.push((regex, Box::new(Mutex::new(cmd)))); } async fn handle_message_inner( &self, origin: &str, author: &str, content: &str, ) -> anyhow::Result<()> { let content = content.trim(); if let Some((command, remainder)) = dissect(&self.prefixes, content) { tracing::debug!("Got command: {:?} -> {:?}", command, remainder); if command.is_empty() { tracing::debug!("Empty command, skipping..."); return Ok(()); } if let Some(handler) = self.commands.get(command) { let msg = Context { author, content: remainder, db: &self.db, history: &self.history, }; return (self.sendmsg)(origin.into(), handler.lock().await.execute(msg).await?); } return (self.sendmsg)(origin.into(), "Unknown command.".into()); } for trigger in &self.triggers { let captures = trigger.0.captures(content)?; if let Some(captures) = captures { let msg = Context { author, content: Some(content), db: &self.db, history: &self.history, }; return (self.sendmsg)( origin.into(), trigger.1.lock().await.execute(msg, captures).await?, ); } } self.history.add_message(author, content).await; Ok(()) } pub async fn handle_message( &self, origin: String, author: String, content: String, _cancel_handle: mpsc::Sender<()>, ) { if let Err(e) = self.handle_message_inner(&origin, &author, &content).await { let _err = (self.sendmsg)(origin, format!("Error: {}", e)); } } }