diff options
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 210 |
1 files changed, 85 insertions, 125 deletions
@@ -4,42 +4,40 @@ //! Frippy is an IRC bot that runs plugins on each message //! received. //! -//! # Example +//! ## Example //! ```no_run //! extern crate frippy; //! //! frippy::run(); //! ``` +//! +//! # Logging +//! Frippy uses the [log](https://docs.rs/log) crate so you can log events +//! which might be of interest. #[macro_use] -extern crate lazy_static; +extern crate log; #[macro_use] extern crate plugin_derive; extern crate irc; -extern crate regex; +extern crate tokio_core; +extern crate futures; +extern crate glob; mod plugin; mod plugins; -use std::thread::spawn; -use std::sync::{Arc, Mutex}; -use regex::Regex; +use std::sync::Arc; + use irc::client::prelude::*; -use irc::proto::Command::PRIVMSG; use irc::error::Error as IrcError; -use plugin::*; +use tokio_core::reactor::Core; +use futures::future; +use glob::glob; -// 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(), - } - } -} +use plugin::*; /// Runs the bot /// @@ -47,136 +45,98 @@ macro_rules! lock_plugin { /// /// This blocks the current thread while the bot is running pub fn run() { - let server = IrcServer::new("config.toml").unwrap(); - server.identify().unwrap(); - // 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| p.lock().unwrap().name().to_lowercase()) - .collect(); - - // The main loop over received messages - server - .for_each_incoming(|message| { - 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); - - // 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).unwrap(); - - } else if "help" == &c.tokens[0].to_lowercase() { - send_help_message(&server, c).unwrap(); - - } 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]); - - server.send_notice(&c.source, &help).unwrap(); + // Load all toml files in the configs directory + let mut configs = Vec::new(); + for toml in glob("configs/*.toml").unwrap() { + match toml { + Ok(path) => { + info!("Loading {}", path.to_str().unwrap()); + match Config::load(path) { + Ok(v) => configs.push(v), + Err(e) => error!("Incorrect config file {}", e), } } + Err(e) => error!("Failed to read path {}", e), + } + } - for plugin in 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(); + // Without configs the bot would just idle + if configs.is_empty() { + error!("No config file found"); + return; + } - // Execute the plugin in another thread - spawn(move || { lock_plugin!(plugin).execute(&server, &message).unwrap(); }); + // The list of plugins in use + let mut plugins = ThreadedPlugins::new(); + plugins.add(plugins::Help::new()); + plugins.add(plugins::Emoji::new()); + plugins.add(plugins::Currency::new()); + info!("Plugins loaded: {}", plugins); + + // Create an event loop to run the connections on. + let mut reactor = Core::new().unwrap(); + + // Open a connection and add work for each config + for config in configs { + let server = + match IrcServer::new_future(reactor.handle(), &config).and_then(|f| reactor.run(f)) { + Ok(v) => v, + Err(e) => { + error!("Failed to connect: {}", e); + return; } + }; - // Check if the command is for this plugin - // Clone it for the move - if let Some(mut c) = command.clone() { + info!("Connected to server"); - // Skip empty commands - if c.tokens.is_empty() { continue; } + match server.identify() { + Ok(_) => info!("Identified"), + Err(e) => error!("Failed to identify: {}", e), + }; - if lock_plugin!(plugin).name().to_lowercase() == c.tokens[0].to_lowercase() { + // TODO Verify if we actually need to clone plugins twice + let plugins = plugins.clone(); - // The first token contains the name of the plugin - c.tokens.remove(0); + let task = server + .stream() + .for_each(move |message| process_msg(&server, plugins.clone(), message)) + .map_err(|e| error!("Failed to process message: {}", e)); - // Clone the server for the move - it uses an Arc internally - let server = server.clone(); - spawn(move || { lock_plugin!(plugin).command(&server, c).unwrap(); }); - } - } - } - }) - .unwrap(); -} + reactor.handle().spawn(task); + } -fn send_help_message(server: &IrcServer, command: &PluginCommand) -> Result<(), IrcError> { - server.send_notice(&command.source, "Help has not been added yet.") + // Run the main loop forever + reactor.run(future::empty::<(), ()>()).unwrap(); } -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(' ') - .filter(|&x| !x.is_empty()) - .map(ToOwned::to_owned) - .collect(); - - // Check if the message contained notthing but spaces - if tokens.is_empty() { - return None; - } +fn process_msg(server: &IrcServer, + mut plugins: ThreadedPlugins, + message: Message) + -> Result<(), IrcError> { - // Only compile the regex once - // We assume that only ':' and ',' are used as suffixes on IRC - lazy_static! { - static ref RE: Regex = Regex::new("^[:,]*?$").unwrap(); + if let Command::JOIN(ref channel, _, _) = message.command { + if message.source_nickname().unwrap() == server.current_nickname() { + info!("Joined {}", channel); } + } - if tokens[0].to_lowercase().starts_with(nick) { - - // Remove the bot's name from the first token - tokens[0].drain(..nick.len()); - - // If the regex does not match the message is not directed at the bot - if !RE.is_match(&tokens[0]) { - return None; - } + // Check for possible command and save the result for later + let command = PluginCommand::from(&server.current_nickname().to_lowercase(), &message); - // The first token contained the name of the bot - tokens.remove(0); + let message = Arc::new(message); + plugins.execute_plugins(server, message); - Some(PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), - tokens: tokens, - }) - } else { - None + // 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); } - } else { - None } + + Ok(()) } + #[cfg(test)] mod tests {} |
