diff options
| -rw-r--r-- | src/lib.rs | 150 | ||||
| -rw-r--r-- | src/plugin.rs | 148 |
2 files changed, 164 insertions, 134 deletions
@@ -24,29 +24,17 @@ extern crate glob; mod plugin; mod plugins; -use std::thread::spawn; -use std::sync::{Arc, Mutex}; -use glob::glob; +use std::sync::Arc; use irc::client::prelude::*; -use irc::proto::Command::PRIVMSG; use irc::error::Error as IrcError; use tokio_core::reactor::Core; use futures::future; +use glob::glob; use plugin::*; -// Lock the mutex and ignore if it is poisoned -macro_rules! lock_plugin { - ($e:expr) => { - match $e.lock() { - Ok(plugin) => plugin, - Err(poisoned) => poisoned.into_inner(), - } - } -} - /// Runs the bot /// /// # Remarks @@ -76,18 +64,10 @@ pub fn run() { } // The list of plugins in use - let plugins: Vec<Arc<Mutex<Plugin>>> = - vec![Arc::new(Mutex::new(plugins::emoji::Emoji::new())), - Arc::new(Mutex::new(plugins::currency::Currency::new()))]; - - // We need the plugins' names to make sure the user gets a response - // if they use an incorrect plugin name - let plugin_names: Vec<String> = plugins - .iter() - .map(|p| lock_plugin!(p).name().to_lowercase()) - .collect(); - - info!("Plugins loaded: {}", plugin_names.join(", ")); + let mut plugins = ThreadedPlugins::new(); + plugins.add(plugins::emoji::Emoji::new()); + plugins.add(plugins::currency::Currency::new()); + info!("Plugins loaded: {}", plugins); // Create an event loop to run the connections on. let mut reactor = Core::new().unwrap(); @@ -110,13 +90,12 @@ pub fn run() { Err(e) => error!("Failed to identify: {}", e), }; - // TODO Duplicate clone... + // TODO Verify if we actually need to clone plugins twice let plugins = plugins.clone(); - let plugin_names = plugin_names.clone(); let task = server .stream() - .for_each(move |message| process_msg(&server, &plugin_names, plugins.clone(), message)) + .for_each(move |message| process_msg(&server, plugins.clone(), message)) .map_err(|e| error!("Failed to process message: {}", e)); reactor.handle().spawn(task); @@ -127,8 +106,7 @@ pub fn run() { } fn process_msg(server: &IrcServer, - plugin_names: &[String], - plugins: Vec<Arc<Mutex<Plugin>>>, + mut plugins: ThreadedPlugins, message: Message) -> Result<(), IrcError> { @@ -138,118 +116,22 @@ fn process_msg(server: &IrcServer, } } - let message = Arc::new(message); // Check for possible command and save the result for later - let command = get_command(&server.current_nickname().to_lowercase(), &message); + let command = PluginCommand::from(&server.current_nickname().to_lowercase(), &message); - // Check if the first token of the command is valid - if let Some(ref c) = command { - if c.tokens.is_empty() { - let help = format!("Use \"{} help\" to get help", server.current_nickname()); - server.send_notice(&c.source, &help)?; - - } else if "help" == &c.tokens[0].to_lowercase() { - send_help_message(server, c)?; - - } else if !plugin_names.contains(&c.tokens[0].to_lowercase()) { - - let help = format!("\"{} {}\" is not a command, \ - try \"{0} help\" instead.", - server.current_nickname(), - c.tokens[0]); + let message = Arc::new(message); + plugins.execute_plugins(server, message); - server.send_notice(&c.source, &help)?; + // If the message contained a command, handle it + if let Some(command) = command { + if let Err(e) = plugins.handle_command(server, command) { + error!("Failed to handle command: {}", e); } } - for plugin in plugins { - // Send the message to the plugin if the plugin needs it - if lock_plugin!(plugin).is_allowed(server, &message) { - - // Clone everything before the move - // The server uses an Arc internally too - let plugin = Arc::clone(&plugin); - let message = Arc::clone(&message); - let server = server.clone(); - - // Execute the plugin in another thread - spawn(move || { - if let Err(e) = lock_plugin!(plugin).execute(&server, &message) { - let name = lock_plugin!(plugin).name().to_string(); - error!("Error in {} - {}", name, e); - }; - }); - } - - // Check if the command is for this plugin - if let Some(mut c) = command.clone() { - - // Skip empty commands - if c.tokens.is_empty() { - continue; - } - - if lock_plugin!(plugin).name().to_lowercase() == c.tokens[0].to_lowercase() { - - // The first token contains the name of the plugin - let name = c.tokens.remove(0); - - // Clone the server for the move - it uses an Arc internally - let server = server.clone(); - spawn(move || { - if let Err(e) = lock_plugin!(plugin).command(&server, c) { - error!("Error in {} - {}", name, e); - }; - }); - } - } - } Ok(()) } -fn send_help_message(server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, "Help has not been added yet.") -} - -fn get_command(nick: &str, message: &Message) -> Option<PluginCommand> { - - // Get the actual message out of PRIVMSG - if let PRIVMSG(_, ref content) = message.command { - - // Split content by spaces and filter empty tokens - let mut tokens: Vec<String> = content.split(' ').map(ToOwned::to_owned).collect(); - - // Commands start with our name - if tokens[0].to_lowercase().starts_with(nick) { - - // Remove the bot's name from the first token - tokens[0].drain(..nick.len()); - - // We assume that only ':' and ',' are used as suffixes on IRC - // If there are any other chars we assume that it is not ment for the bot - tokens[0] = tokens[0] - .chars() - .filter(|&c| !":,".contains(c)) - .collect(); - if !tokens[0].is_empty() { - return None; - } - - // The first token contained the name of the bot - tokens.remove(0); - - Some(PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), - tokens: tokens, - }) - } else { - None - } - } else { - None - } -} #[cfg(test)] mod tests {} diff --git a/src/plugin.rs b/src/plugin.rs index 0a4034d..d1a1f3d 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,4 +1,8 @@ use std::fmt; +use std::collections::HashMap; +use std::thread::spawn; +use std::sync::{Arc, Mutex}; + use irc::client::prelude::*; use irc::error::Error as IrcError; @@ -18,3 +22,147 @@ pub struct PluginCommand { pub target: String, pub tokens: Vec<String>, } + +impl PluginCommand { + pub fn from(nick: &str, message: &Message) -> Option<PluginCommand> { + + // Get the actual message out of PRIVMSG + if let Command::PRIVMSG(_, ref content) = message.command { + + // Split content by spaces and filter empty tokens + let mut tokens: Vec<String> = content.split(' ').map(ToOwned::to_owned).collect(); + + // Commands start with our name + if tokens[0].to_lowercase().starts_with(nick) { + + // Remove the bot's name from the first token + tokens[0].drain(..nick.len()); + + // We assume that only ':' and ',' are used as suffixes on IRC + // If there are any other chars we assume that it is not ment for the bot + tokens[0] = tokens[0] + .chars() + .filter(|&c| !":,".contains(c)) + .collect(); + if !tokens[0].is_empty() { + return None; + } + + // The first token contained the name of the bot + tokens.remove(0); + + Some(PluginCommand { + source: message.source_nickname().unwrap().to_string(), + target: message.response_target().unwrap().to_string(), + tokens: tokens, + }) + } else { + None + } + } else { + None + } + } +} + +// Lock the mutex and ignore if it is poisoned +macro_rules! lock_plugin { + ($e:expr) => { + match $e.lock() { + Ok(plugin) => plugin, + Err(poisoned) => poisoned.into_inner(), + } + } +} + +#[derive(Clone, Debug)] +pub struct ThreadedPlugins { + plugins: HashMap<String, Arc<Mutex<Plugin>>>, +} + +impl ThreadedPlugins { + pub fn new() -> ThreadedPlugins { + ThreadedPlugins { plugins: HashMap::new() } + } + + pub fn add<T: Plugin + 'static>(&mut self, plugin: T) { + let name = plugin.name().to_lowercase(); + let safe_plugin = Arc::new(Mutex::new(plugin)); + + self.plugins.insert(name, safe_plugin); + } + + pub fn execute_plugins(&mut self, server: &IrcServer, message: Arc<Message>) { + + for (name, plugin) in self.plugins.clone() { + // Send the message to the plugin if the plugin needs it + if lock_plugin!(plugin).is_allowed(server, &message) { + + // Clone everything before the move + // The server uses an Arc internally too + let plugin = Arc::clone(&plugin); + let message = Arc::clone(&message); + let server = server.clone(); + + // Execute the plugin in another thread + spawn(move || { + if let Err(e) = lock_plugin!(plugin).execute(&server, &message) { + error!("Error in {} - {}", name, e); + }; + }); + } + } + } + + pub fn handle_command(&mut self, server: &IrcServer, mut command: PluginCommand) -> Result<(), IrcError> { + + if !command.tokens.iter().any(|s| !s.is_empty()) { + let help = format!("Use \"{} help\" to get help", server.current_nickname()); + return server.send_notice(&command.source, &help); + } + + if &command.tokens[0].to_lowercase() == "help" { + return self.send_help_message(server, &command); + } + + // Check if the command is for this plugin + if let Some(plugin) = self.plugins.get(&command.tokens[0].to_lowercase()) { + + // The first token contains the name of the plugin + let name = command.tokens.remove(0); + + // Clone for the move - the server uses an Arc internally + let server = server.clone(); + let plugin = Arc::clone(plugin); + spawn(move || { + if let Err(e) = lock_plugin!(plugin).command(&server, command) { + error!("Error in {} command - {}", name, e); + }; + }); + + Ok(()) + + } else { + let help = format!("\"{} {}\" is not a command, \ + try \"{0} help\" instead.", + server.current_nickname(), + command.tokens[0]); + + server.send_notice(&command.source, &help) + } + } + + fn send_help_message(&self, server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> { + server.send_notice(&command.source, "Help has not been added yet.") + } +} + +impl fmt::Display for ThreadedPlugins { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let plugin_names = self.plugins + .iter() + .map(|(_, p)| lock_plugin!(p).name().to_string()) + .collect::<Vec<String>>(); + write!(f, "{}", plugin_names.join(", ")) + } +} |
