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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
use std::str::FromStr;
use serialize::json::Json;
use super::error::{ParseEventTypeError, InvalidJsonError};
use std::ops::Index;
use std::ascii::AsciiExt;

/// The events that could possibly be received from the DaZeus server.
///
/// You can use the variants of this enum to start listening for an event of that type.
/// Every event that you receive will also contain its type.
#[derive(Debug, Clone, PartialEq)]
pub enum EventType {
    /// A CTCP ACTION event (IRC users will know this as `/me`).
    Action,
    /// A CTCP ACTION sent by the bot from another plugin.
    ActionMe,
    /// A command received by DaZeus.
    ///
    /// Typically a command can be given to the DaZeus server by using a PRIVMSG where
    /// the message is either prefixed by a highlight character or by the name of the bot (in
    /// typical IRC highlight style, eg: `DaZeus: do something`).
    ///
    /// The first word after the highlight is used as the command name. For example when the IRC
    /// user sends a PRIVMSG `DaZeus: start server`, then a `Command("start".to_string())`
    /// variant is sent to the plugin (as long as the plugin has subscribed to such events).
    Command(String),
    /// Signalling that the bot has connected to a new network.
    Connect,
    /// A CTCP event was sent.
    Ctcp,
    /// A CTCP event sent by the bot (from another plugin).
    CtcpMe,
    /// A CTCP_REP event was sent.
    CtcpReply,
    /// Signalling that the bot has disconnected from a network.
    Disconnect,
    /// An invite was sent to the bot.
    Invite,
    /// A JOIN event: an IRC user joined a channel (this may be the bot itself, or another user).
    Join,
    /// A KICK event: an IRC user was kicked from a channel (either the bot itself, or another user).
    Kick,
    /// A MODE event: a mode was changed.
    Mode,
    /// A list of users from some channel (will be sent by the IRC server on request).
    Names,
    /// An event for when the nickname of the bot was changed.
    Nick,
    /// A NOTICE event was sent.
    Notice,
    /// A NUMERIC event was sent (typically contains things such as error messages from the server).
    Numeric,
    /// A PART event: an IRC user left a channel (this may be the bot itself, or another user).
    Part,
    /// An event indicating the response for a ping.
    Pong,
    /// A typical IRC message.
    ///
    /// This is either a user sending a direct message to the bot (indicated by the channel being
    /// equal to the name of the bot), or a message in a channel that was joined by the bot.
    PrivMsg,
    /// A message send by the bot itself via another plugin.
    PrivMsgMe,
    /// A QUIT event: an IRC user disconnects from an IRC server.
    Quit,
    /// A TOPIC event: received when joining a channel or when the topic of a channel is changed.
    Topic,
    /// Unknown event types.
    Unknown,
    /// A WHOIS event: when requested, this is the response to some WHOIS request.
    Whois,
}

impl ToString for EventType {
    fn to_string(&self) -> String {
        match *self {
            EventType::Command(ref s) => format!("COMMAND_{}", s),
            EventType::Action => "ACTION".to_string(),
            EventType::ActionMe => "ACTION_ME".to_string(),
            EventType::Connect => "CONNECT".to_string(),
            EventType::Ctcp => "CTCP".to_string(),
            EventType::CtcpMe => "CTCP_ME".to_string(),
            EventType::CtcpReply => "CTCP_REP".to_string(),
            EventType::Disconnect => "DISCONNECT".to_string(),
            EventType::Invite => "INVITE".to_string(),
            EventType::Join => "JOIN".to_string(),
            EventType::Kick => "KICK".to_string(),
            EventType::Mode => "MODE".to_string(),
            EventType::Names => "NAMES".to_string(),
            EventType::Nick => "NICK".to_string(),
            EventType::Notice => "NOTICE".to_string(),
            EventType::Numeric => "NUMERIC".to_string(),
            EventType::Part => "PART".to_string(),
            EventType::Pong => "PONG".to_string(),
            EventType::PrivMsg => "PRIVMSG".to_string(),
            EventType::PrivMsgMe => "PRIVMSG_ME".to_string(),
            EventType::Quit => "QUIT".to_string(),
            EventType::Topic => "TOPIC".to_string(),
            EventType::Unknown => "UNKNOWN".to_string(),
            EventType::Whois => "WHOIS".to_string(),
        }
    }
}

impl FromStr for EventType {
    type Err = ParseEventTypeError;

    fn from_str(s: &str) -> Result<Self, ParseEventTypeError> {
        match &s.to_ascii_uppercase()[..] {
            "ACTION" => Ok(EventType::Action),
            "ACTION_ME" => Ok(EventType::ActionMe),
            "CONNECT" => Ok(EventType::Connect),
            "CTCP" => Ok(EventType::Ctcp),
            "CTCP_ME" => Ok(EventType::CtcpMe),
            "CTCP_REP" => Ok(EventType::CtcpReply),
            "DISCONNECT" => Ok(EventType::Disconnect),
            "INVITE" => Ok(EventType::Invite),
            "JOIN" => Ok(EventType::Join),
            "KICK" => Ok(EventType::Kick),
            "MODE" => Ok(EventType::Mode),
            "NAMES" => Ok(EventType::Names),
            "NICK" => Ok(EventType::Nick),
            "NOTICE" => Ok(EventType::Notice),
            "NUMERIC" => Ok(EventType::Numeric),
            "PART" => Ok(EventType::Part),
            "PONG" => Ok(EventType::Pong),
            "PRIVMSG" => Ok(EventType::PrivMsg),
            "PRIVMSG_ME" => Ok(EventType::PrivMsgMe),
            "QUIT" => Ok(EventType::Quit),
            "TOPIC" => Ok(EventType::Topic),
            "UNKNOWN" => Ok(EventType::Unknown),
            "WHOIS" => Ok(EventType::Whois),
            other if other.len() > 8 => {
                match &other[..7] {
                    "COMMAND" => Ok(EventType::Command(other[8..].to_string())),
                    _ => Err(ParseEventTypeError::new())
                }
            },
            _ => Err(ParseEventTypeError::new())
        }
    }
}

/// An event received from the DaZeus server.
///
/// You can retrieve the parameters from the event using one of three different methods:
///
/// 1. Using the params field directly.
/// 2. Using the `param()` method with an index which will return a string slice.
/// 3. Using indexing on the event struct itself, i.e. `event[0]` to receive the first parameter.
///
/// The prefered method is the last one.
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
    /// The type of event that was received.
    pub event: EventType,
    /// The parameters attached to the event.
    pub params: Vec<String>,
}

/// Returns whether or not the given Json data could be a valid event object.
pub fn is_event_json(data: &Json) -> bool {
    data.is_object() && data.as_object().unwrap().contains_key("event")
}

impl Event {
    /// Create a new event based on the basic properties of an event.
    ///
    /// Allows creation of events for testing purposes. Also used internally for constructing
    /// events based on parsed Json objects.
    ///
    /// # Example
    /// ```
    /// Event::new(EventType::PrivMsg, vec!(
    ///    "network".to_string(),
    ///    "sender".to_string(),
    ///    "receiver".to_string(),
    ///    "message".to_string()
    /// ))
    /// ```
    pub fn new(event: EventType, params: Vec<String>) -> Event {
        Event { event: event, params: params }
    }

    /// Create a new event based on a Json data object.
    ///
    /// Typically this method will be called by the bindings itself to create an event instance
    /// from some received json blob from the core.
    pub fn from_json(data: &Json) -> Result<Event, InvalidJsonError> {
        if data.is_object() {
            let obj = data.as_object().unwrap();
            if obj.contains_key("event") && obj.contains_key("params") {
                let evt = obj.get("event").unwrap();
                let params = obj.get("params").unwrap();
                if evt.is_string() && params.is_array() {
                    Event::create_event(&evt.as_string().unwrap(), &params.as_array().unwrap())
                } else {
                    Err(InvalidJsonError::new(""))
                }
            } else {
                Err(InvalidJsonError::new(""))
            }
        } else {
            Err(InvalidJsonError::new(""))
        }
    }

    /// Create a new event based on the properties extracted from the Json.
    fn create_event(evt: &str, params: &Vec<Json>) -> Result<Event, InvalidJsonError> {
        if evt == "COMMAND" {
            if params.len() >= 4 && params[3].is_string() {
                let cmd = params[3].as_string().unwrap().to_string();
                Ok(Event::new(EventType::Command(cmd), Event::param_strs(params)))
            } else {
                Err(InvalidJsonError::new(""))
            }
        } else {
            match EventType::from_str(evt) {
                Ok(evt) => Ok(Event::new(evt, Event::param_strs(params))),
                Err(_) => Err(InvalidJsonError::new(""))
            }
        }
    }

    /// Extract string parameters from an array of `Json::String` objects.
    fn param_strs(params: &Vec<Json>) -> Vec<String> {
        let mut strs = Vec::new();
        for param in params {
            if param.is_string() {
                strs.push(param.as_string().unwrap().to_string());
            }
        }
        strs
    }

    /// Retrieve a parameter from the list of parameters contained in the event.
    pub fn param<'a>(&'a self, idx: usize) -> &'a str {
        &self.params[idx][..]
    }

    /// Retrieve the number of parameters for the event.
    pub fn len(&self) -> usize {
        self.params.len()
    }
}

impl<'b> Index<usize> for Event {
    type Output = str;

    fn index<'a>(&'a self, index: usize) -> &'a str {
        self.param(index)
    }
}