diff --git a/sample_uberbot.toml b/sample_uberbot.toml index de8b056..52bbdde 100644 --- a/sample_uberbot.toml +++ b/sample_uberbot.toml @@ -5,7 +5,7 @@ tls = true username = "uberbot" channels = ["#main", "#no-normies"] mode = "+B" -prefix = "!" +prefix = "u!" [spotify] spotify_client_id = "" diff --git a/src/bot.rs b/src/bot.rs index 3608dea..ec91358 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -2,6 +2,7 @@ use crate::ExecutorConnection; use async_trait::async_trait; use fancy_regex::{Captures, Regex}; use std::collections::HashMap; +use tokio::sync::{Mutex, RwLock}; fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) { if let Some(o) = str.find(' ') { @@ -13,7 +14,7 @@ fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) { #[async_trait] pub trait Trigger { - async fn execute(&mut self, msg: Message, matches: Captures) -> anyhow::Result; + async fn execute<'a>(&mut self, msg: Message<'a>, matches: Captures<'a>) -> anyhow::Result; } #[async_trait] @@ -23,24 +24,25 @@ pub trait Command { } pub struct Message<'a> { - pub last_msg: &'a HashMap, + pub last_msg: &'a RwLock>, pub author: &'a str, + // in case of triggers, this is always Some(...) pub content: Option<&'a str>, } -pub struct Bot anyhow::Result<()>> { - last_msg: HashMap, +pub struct Bot anyhow::Result<()>> { + last_msg: RwLock>, prefix: String, db: ExecutorConnection, - commands: HashMap>, - triggers: Vec<(Regex, Box)>, + commands: HashMap>>, + triggers: Vec<(Regex, Box>)>, sendmsg: SF, } -impl anyhow::Result<()>> Bot { +impl anyhow::Result<()>> Bot { pub fn new(prefix: String, db: ExecutorConnection, sendmsg: SF) -> Self { Bot { - last_msg: HashMap::new(), + last_msg: RwLock::new(HashMap::new()), commands: HashMap::new(), triggers: Vec::new(), prefix, @@ -50,19 +52,55 @@ impl anyhow::Result<()>> Bot { } pub fn add_command(&mut self, name: String, cmd: C) { - self.commands.insert(name, Box::new(cmd)); + self.commands.insert(name, Box::new(Mutex::new(cmd))); } - pub fn add_regex_command(&mut self, regex: Regex, cmd: C) { - self.triggers.push((regex, Box::new(cmd))); + pub fn add_trigger(&mut self, regex: Regex, cmd: C) { + self.triggers.push((regex, Box::new(Mutex::new(cmd)))); } - pub async fn handle_message( - &mut self, + async fn handle_message_inner( + &self, origin: &str, author: &str, content: &str, ) -> anyhow::Result<()> { + if content.starts_with(&self.prefix) { + let (command, remainder) = separate_to_space(content, self.prefix.len()); + if let Some(handler) = self.commands.get(command) { + let msg = Message { + last_msg: &self.last_msg, + author, + content: remainder + }; + return (self.sendmsg)(origin.into(), handler.lock().await.execute(msg).await?) + } + return (self.sendmsg)(origin.into(), "Unknown command.".into()) + } else { + for trigger in &self.triggers { + let captures = trigger.0.captures(content)?; + if let Some(captures) = captures { + let msg = Message { + last_msg: &self.last_msg, + author, + content: Some(content) + }; + return (self.sendmsg)(origin.into(), trigger.1.lock().await.execute(msg, captures).await?) + } + } + self.last_msg.write().await.insert(author.to_string(), content.to_string()); + } Ok(()) } + + pub async fn handle_message( + &self, + origin: &str, + author: &str, + content: &str, + ) { + if let Err(e) = self.handle_message_inner(origin, author, content).await { + let _ = (self.sendmsg)(origin.into(), format!("Error: {}", e)); + } + } } diff --git a/src/commands/debug.rs b/src/commands/debug.rs new file mode 100644 index 0000000..ccfbed2 --- /dev/null +++ b/src/commands/debug.rs @@ -0,0 +1,14 @@ +use async_trait::async_trait; +use crate::bot::{Command, Message}; + +pub struct LastMsg; + +#[async_trait] +impl Command for LastMsg { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { + let nick = msg.content.unwrap_or(msg.author); + let lastmsg = msg.last_msg.read().await; + Ok(format!("{}: {:?}", nick, lastmsg.get(nick))) + } +} \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b7471c4..c768615 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,6 @@ pub mod help; pub mod leek; pub mod waifu; +pub mod sed; +#[cfg(feature = "debug")] +pub mod debug; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 54b16b0..7613c74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::io::Read; use std::sync::Arc; use std::thread; +use fancy_regex::Regex; use crate::bot::Bot; use crate::commands::waifu::Waifu; @@ -12,12 +13,13 @@ use futures_util::stream::StreamExt; use irc::client::prelude::Config; use irc::client::{Client, ClientStream}; use irc::proto::{ChannelExt, Command, Prefix}; -use rspotify::Credentials; use tokio::select; use tokio::sync::broadcast; use tokio::sync::mpsc::unbounded_channel; use tracing_subscriber::EnvFilter; +use crate::commands::help::Help; use crate::commands::leek::Owo; +use crate::commands::sed::Sed; use crate::config::UberConfig; use crate::database::{DbExecutor, ExecutorConnection}; @@ -34,8 +36,8 @@ async fn terminate_signal() { let mut sigint = signal(SignalKind::interrupt()).unwrap(); tracing::debug!("Installed ctrl+c handler"); select! { - _ = sigterm.recv() => return, - _ = sigint.recv() => return + _ = sigterm.recv() => (), + _ = sigint.recv() => () } } @@ -47,7 +49,7 @@ async fn terminate_signal() { let _ = ctrlc.recv().await; } -pub struct AppState anyhow::Result<()>> { +pub struct AppState anyhow::Result<()>> { client: Arc, stream: ClientStream, bot: Bot, @@ -99,6 +101,7 @@ async fn main() -> anyhow::Result<()> { move |target, msg| Ok(client.send_privmsg(target, msg)?) }); + bot.add_command("help".into(), Help); bot.add_command("waifu".into(), Waifu); bot.add_command("owo".into(), Owo); @@ -142,7 +145,7 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn message_loop anyhow::Result<()>>( +async fn message_loop anyhow::Result<()>>( mut state: AppState, ) -> anyhow::Result<()> { while let Some(message) = state.stream.next().await.transpose()? { @@ -152,11 +155,7 @@ async fn message_loop anyhow::Result<()>>( Prefix::Nickname(name, _, _) => Some(&name[..]), _ => None, }) { - if let Err(e) = state.bot.handle_message(origin, author, &content).await { - state - .client - .send_privmsg(origin, &format!("Error: {}", e))?; - } + state.bot.handle_message(origin, author, &content).await } else { tracing::warn!("Couldn't get the author for a message"); }