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
use super::types::Prefix;
use log::*;

/// A simple IRC message
///
/// Twitch messages will be part of the Unknown variant.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Message {
    /// Ping command. The client should respond to this with a `PONG :${token}\r\n` message        
    Ping {
        /// The token sent with the ping, expected to receive back on a `PONG`
        token: String,
    },

    /// Acknowledgement (or not) on a CAPS request
    // TODO https://ircv3.net/specs/core/capability-negotiation.html#the-cap-command
    // THIS: https://ircv3.net/specs/core/capability-negotiation.html#the-cap-nak-subcommand
    Cap {
        /// Whether it was acknowledged
        acknowledge: bool,
        /// Which CAP was enabled
        cap: String,
    },

    /// Happens when you've connected to the server. Corresponds to the `001` IRC message
    Connected {
        /// The name the server assigned you
        name: String,
    },

    /// Happens after the server sent you the MOTD. Corresponds to the `376` IRC message
    Ready {
        /// The name the server assigned you
        name: String,
    },

    /// Unknown message.
    Unknown {
        /// Optional prefix. The sender of the message
        prefix: Option<Prefix>,
        /// Any parsed tags
        tags: crate::Tags,
        /// the `COMMAND` portion of the IRC message
        head: String,
        /// The argument list that follows the commands
        args: Vec<String>,
        /// Any trailing data (generally after the ':')
        tail: Option<String>,
    },
}

impl Message {
    /// Parses an irc message
    pub fn parse(input: &str) -> Option<Self> {
        let input = input.trim(); // sanity check
        if input.is_empty() {
            return None;
        }

        trace!("parsing: {}", input);
        let (tags, input) = if input.starts_with('@') {
            let pos = input.find(' ')?;
            (crate::Tags::parse(&input[..pos]), &input[pos + 1..])
        } else {
            (crate::Tags::default(), input)
        };

        // :prefix command
        let (prefix, input) = if input.starts_with(':') {
            (Prefix::parse(&input), &input[input.find(' ')? + 1..])
        } else {
            (None, input)
        };

        let mut parts = Parts::new(input);
        let ty = match parts.head {
            "PING" => Message::Ping {
                token: parts.data()?,
            },
            "CAP" => Message::Cap {
                acknowledge: parts
                    .args
                    .first()
                    .map(|d| *d == "ACK")
                    .unwrap_or_else(|| false),
                cap: parts
                    .tail
                    .map(str::to_string)
                    .expect("tail to exist on cap message"),
            },
            "001" => Message::Connected {
                name: parts.next()?,
            },
            "376" => Message::Ready {
                name: parts.next()?,
            },
            head => Message::Unknown {
                prefix,
                tags,
                head: head.to_string(),
                // reverse it because parts reverses it to act like a stack
                args: parts.args.into_iter().rev().map(str::to_string).collect(),
                tail: parts.tail.map(str::to_string),
            },
        };
        Some(ty)
    }
}

#[derive(Debug)]
struct Parts<'a> {
    head: &'a str,
    args: Vec<&'a str>,
    tail: Option<&'a str>,
}

impl<'a> Parts<'a> {
    fn new(input: &'a str) -> Self {
        let mut iter = input.split_terminator(" :");
        let index = input.find(" :");
        let (mut iter, tail) = (
            iter.next()
                .map(|s| s.split_terminator(' '))
                .expect("iter to exist on parts"),
            index.map(|i| &input[i + 2..]).filter(|s| !s.is_empty()),
        );
        let head = iter.next().expect("head to exist on parts");
        let args = iter.rev().collect();
        Self { head, args, tail }
    }

    fn next(&mut self) -> Option<String> {
        self.args.pop().map(str::to_string)
    }

    fn data(&self) -> Option<String> {
        self.tail.map(str::to_string)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn parse_empty_data() {
        assert_eq!(Message::parse(""), None);
        assert_eq!(Message::parse("            "), None);
    }
}