summaryrefslogtreecommitdiffstats
path: root/src/plugin.rs
blob: 63d3530eca02bdb1842e01fed64a0c6ab6fcb8b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Definitions required for every `Plugin`
use std::fmt;

use irc::client::prelude::*;
use irc::error::Error as IrcError;

/// `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 {
    /// This should return true if the `Plugin` wants to do work on the message.
    fn is_allowed(&self, server: &IrcServer, message: &Message) -> bool;
    /// Handles messages which are not commands but still necessary.
    fn execute(&self, server: &IrcServer, message: &Message) -> Result<(), IrcError>;
    /// Handles any command directed at this plugin.
    fn command(&self, server: &IrcServer, command: PluginCommand) -> Result<(), IrcError>;
    /// Should work like command but return a String instead of sending messages to IRC.
    fn evaluate(&self, server: &IrcServer, command: PluginCommand) -> Result<String, String>;
}

/// `PluginName` is required by `Plugin`.  
///
/// 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: Send + Sync + fmt::Debug {
    /// 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` if it is a `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();
                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
        }
    }
}