diff options
| author | Jokler <jokler.contact@gmail.com> | 2018-03-12 16:02:51 +0100 |
|---|---|---|
| committer | Jokler <jokler.contact@gmail.com> | 2018-03-12 16:02:51 +0100 |
| commit | 909cabe9280722e43c5fb283f768051bb85e1890 (patch) | |
| tree | 506ac34b7e22cdb95568cef9e649ee64cb3b0fdb /src/plugin.rs | |
| parent | 15e855ddecfdac31ddda26b12fcfd1a142a0ec21 (diff) | |
| parent | 8e40e919aca8b8592be43e2c5bbcc0717bf14a6b (diff) | |
| download | frippy-909cabe9280722e43c5fb283f768051bb85e1890.tar.gz frippy-909cabe9280722e43c5fb283f768051bb85e1890.zip | |
Merge branch 'dev'
Diffstat (limited to 'src/plugin.rs')
| -rw-r--r-- | src/plugin.rs | 167 |
1 files changed, 55 insertions, 112 deletions
diff --git a/src/plugin.rs b/src/plugin.rs index d1f849a..bc428d5 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,49 +1,85 @@ +//! Definitions required for every `Plugin` use std::fmt; -use std::collections::HashMap; -use std::thread::spawn; -use std::sync::Arc; use irc::client::prelude::*; -use irc::error::Error as IrcError; +use error::FrippyError; + +/// Describes if a [`Plugin`](trait.Plugin.html) is done working on a +/// [`Message`](../../irc/proto/message/struct.Message.html) or if another thread is required. +#[derive(Debug)] +pub enum ExecutionStatus { + /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this + /// [`Message`](../../irc/proto/message/struct.Message.html). + Done, + /// An error occured during the execution. + Err(FrippyError), + /// The execution needs to be done by [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded). + RequiresThread, +} +/// `Plugin` has to be implemented for any struct that should be usable +/// as a `Plugin` in frippy. pub trait Plugin: PluginName + Send + Sync + fmt::Debug { - fn is_allowed(&self, server: &IrcServer, message: &Message) -> bool; - fn execute(&self, server: &IrcServer, message: &Message) -> Result<(), IrcError>; - fn command(&self, server: &IrcServer, command: PluginCommand) -> Result<(), IrcError>; + /// Handles messages which are not commands or returns + /// [`RequiresThread`](enum.ExecutionStatus.html#variant.RequiresThread) + /// if [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded) should be used instead. + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus; + /// Handles messages which are not commands in a new thread. + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError>; + /// Handles any command directed at this plugin. + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError>; + /// Similar to [`command()`](trait.Plugin.html#tymethod.command) but return a String instead of + /// sending messages directly to IRC. + fn evaluate(&self, client: &IrcClient, command: PluginCommand) -> Result<String, String>; } -pub trait PluginName: Send + Sync + fmt::Debug { +/// `PluginName` is required by [`Plugin`](trait.Plugin.html). +/// +/// To implement it simply add `#[derive(PluginName)]` +/// above the definition of the struct. +/// +/// # Examples +/// ```ignore +/// #[macro_use] extern crate frippy_derive; +/// +/// #[derive(PluginName)] +/// struct Foo; +/// ``` +pub trait PluginName { + /// Returns the name of the `Plugin`. fn name(&self) -> &str; } +/// Represents a command sent by a user to the bot. #[derive(Clone, Debug)] pub struct PluginCommand { + /// The sender of the command. pub source: String, + /// If the command was sent to a channel, this will be that channel + /// otherwise it is the same as `source`. pub target: String, + /// The remaining part of the message that has not been processed yet - split by spaces. pub tokens: Vec<String>, } impl PluginCommand { + /// Creates a `PluginCommand` from [`Message`](../../irc/proto/message/struct.Message.html) + /// if it contains a [`PRIVMSG`](../../irc/proto/command/enum.Command.html#variant.PRIVMSG) + /// that starts with the provided `nick`. 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(); + tokens[0] = tokens[0].chars().filter(|&c| !":,".contains(c)).collect(); if !tokens[0].is_empty() { return None; } @@ -52,10 +88,10 @@ impl PluginCommand { tokens.remove(0); Some(PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), - tokens: tokens, - }) + source: message.source_nickname().unwrap().to_string(), + target: message.response_target().unwrap().to_string(), + tokens: tokens, + }) } else { None } @@ -64,96 +100,3 @@ impl PluginCommand { } } } - -#[derive(Clone, Debug)] -pub struct ThreadedPlugins { - plugins: HashMap<String, Arc<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(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 plugin.is_allowed(server, &message) { - - debug!("Executing {} with {}", - name, - message.to_string().replace("\r\n", "")); - - // 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) = 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); - } - - // 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); - - debug!("Sending command \"{:?}\" to {}", command, name); - - // 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) = 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) - } - } -} - -impl fmt::Display for ThreadedPlugins { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let plugin_names = self.plugins - .iter() - .map(|(_, p)| p.name().to_string()) - .collect::<Vec<String>>(); - write!(f, "{}", plugin_names.join(", ")) - } -} |
