2017-12-13 20:38:04 +01:00
|
|
|
//! A module providing a data structure for messages to and from IRC servers.
|
2014-12-23 18:53:30 +01:00
|
|
|
use std::borrow::ToOwned;
|
2018-09-17 13:52:22 +02:00
|
|
|
use std::fmt::{Display, Formatter, Result as FmtResult, Write};
|
2014-11-19 01:00:18 +01:00
|
|
|
use std::str::FromStr;
|
2017-06-22 20:14:49 +02:00
|
|
|
|
2019-10-16 03:12:29 +02:00
|
|
|
use crate::chan::ChannelExt;
|
|
|
|
use crate::command::Command;
|
|
|
|
use crate::error;
|
|
|
|
use crate::error::{MessageParseError, ProtocolError};
|
|
|
|
use crate::prefix::Prefix;
|
2018-09-18 07:03:22 +02:00
|
|
|
|
2017-12-25 03:23:19 +01:00
|
|
|
/// A data structure representing an IRC message according to the protocol specification. It
|
|
|
|
/// consists of a collection of IRCv3 tags, a prefix (describing the source of the message), and
|
|
|
|
/// the protocol command. If the command is unknown, it is treated as a special raw command that
|
|
|
|
/// consists of a collection of arguments and the special suffix argument. Otherwise, the command
|
2024-03-19 00:21:31 +01:00
|
|
|
/// is parsed into a more useful form as described in [`Command`].
|
2015-02-03 19:11:33 +01:00
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
2014-11-02 23:25:45 +01:00
|
|
|
pub struct Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
/// Message tags as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
2017-12-25 03:23:19 +01:00
|
|
|
/// These tags are used to add extended information to the given message, and are commonly used
|
|
|
|
/// in IRCv3 extensions to the IRC protocol.
|
2015-06-04 18:35:43 +02:00
|
|
|
pub tags: Option<Vec<Tag>>,
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
2018-09-17 13:52:22 +02:00
|
|
|
pub prefix: Option<Prefix>,
|
2017-12-25 03:23:19 +01:00
|
|
|
/// The IRC command, parsed according to the known specifications. The command itself and its
|
|
|
|
/// arguments (including the special suffix argument) are captured in this component.
|
2016-01-30 10:56:32 +01:00
|
|
|
pub command: Command,
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Message {
|
2017-12-13 20:38:04 +01:00
|
|
|
/// Creates a new message from the given components.
|
2017-12-25 03:23:19 +01:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```
|
2018-03-10 15:59:29 +01:00
|
|
|
/// # extern crate irc_proto;
|
|
|
|
/// # use irc_proto::Message;
|
2017-12-25 03:23:19 +01:00
|
|
|
/// # fn main() {
|
|
|
|
/// let message = Message::new(
|
2020-01-31 01:12:44 +01:00
|
|
|
/// Some("nickname!username@hostname"), "JOIN", vec!["#channel"]
|
2017-12-25 03:23:19 +01:00
|
|
|
/// ).unwrap();
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2017-06-19 19:59:26 +02:00
|
|
|
pub fn new(
|
|
|
|
prefix: Option<&str>,
|
|
|
|
command: &str,
|
|
|
|
args: Vec<&str>,
|
2018-01-28 00:52:11 +01:00
|
|
|
) -> Result<Message, MessageParseError> {
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::with_tags(None, prefix, command, args)
|
2015-06-04 18:35:43 +02:00
|
|
|
}
|
|
|
|
|
2017-12-25 03:23:19 +01:00
|
|
|
/// Creates a new IRCv3.2 message from the given components, including message tags. These tags
|
|
|
|
/// are used to add extended information to the given message, and are commonly used in IRCv3
|
|
|
|
/// extensions to the IRC protocol.
|
2017-06-19 19:59:26 +02:00
|
|
|
pub fn with_tags(
|
|
|
|
tags: Option<Vec<Tag>>,
|
|
|
|
prefix: Option<&str>,
|
|
|
|
command: &str,
|
|
|
|
args: Vec<&str>,
|
2018-01-28 00:52:11 +01:00
|
|
|
) -> Result<Message, error::MessageParseError> {
|
2016-01-30 10:56:32 +01:00
|
|
|
Ok(Message {
|
2021-11-14 19:22:29 +01:00
|
|
|
tags,
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: prefix.map(|p| p.into()),
|
2020-01-31 01:12:44 +01:00
|
|
|
command: Command::new(command, args)?,
|
2016-01-30 10:56:32 +01:00
|
|
|
})
|
2015-04-26 05:43:16 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 18:03:57 +02:00
|
|
|
/// Gets the nickname of the message source, if it exists.
|
2017-12-25 03:23:19 +01:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```
|
2018-03-10 15:59:29 +01:00
|
|
|
/// # extern crate irc_proto;
|
|
|
|
/// # use irc_proto::Message;
|
2017-12-25 03:23:19 +01:00
|
|
|
/// # fn main() {
|
|
|
|
/// let message = Message::new(
|
2020-01-31 01:12:44 +01:00
|
|
|
/// Some("nickname!username@hostname"), "JOIN", vec!["#channel"]
|
2017-12-25 03:23:19 +01:00
|
|
|
/// ).unwrap();
|
|
|
|
/// assert_eq!(message.source_nickname(), Some("nickname"));
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2016-01-30 17:59:57 +01:00
|
|
|
pub fn source_nickname(&self) -> Option<&str> {
|
2016-02-07 09:01:05 +01:00
|
|
|
// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
|
|
|
|
// <servername> ::= <host>
|
2018-09-17 13:52:22 +02:00
|
|
|
self.prefix.as_ref().and_then(|p| match p {
|
|
|
|
Prefix::Nickname(name, _, _) => Some(&name[..]),
|
2019-08-27 15:05:51 +02:00
|
|
|
_ => None,
|
2017-06-19 19:59:26 +02:00
|
|
|
})
|
2015-01-02 21:20:26 +01:00
|
|
|
}
|
|
|
|
|
2017-06-25 10:47:45 +02:00
|
|
|
/// Gets the likely intended place to respond to this message.
|
|
|
|
/// If the type of the message is a `PRIVMSG` or `NOTICE` and the message is sent to a channel,
|
|
|
|
/// the result will be that channel. In all other cases, this will call `source_nickname`.
|
2017-12-25 03:23:19 +01:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```
|
2018-03-10 15:59:29 +01:00
|
|
|
/// # extern crate irc_proto;
|
|
|
|
/// # use irc_proto::Message;
|
2017-12-25 03:23:19 +01:00
|
|
|
/// # fn main() {
|
|
|
|
/// let msg1 = Message::new(
|
2020-01-31 01:12:44 +01:00
|
|
|
/// Some("ada"), "PRIVMSG", vec!["#channel", "Hi, everyone!"]
|
2017-12-25 03:23:19 +01:00
|
|
|
/// ).unwrap();
|
|
|
|
/// assert_eq!(msg1.response_target(), Some("#channel"));
|
|
|
|
/// let msg2 = Message::new(
|
2020-01-31 01:12:44 +01:00
|
|
|
/// Some("ada"), "PRIVMSG", vec!["betsy", "betsy: hi"]
|
2017-12-25 03:23:19 +01:00
|
|
|
/// ).unwrap();
|
|
|
|
/// assert_eq!(msg2.response_target(), Some("ada"));
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2017-06-25 10:47:45 +02:00
|
|
|
pub fn response_target(&self) -> Option<&str> {
|
|
|
|
match self.command {
|
|
|
|
Command::PRIVMSG(ref target, _) if target.is_channel_name() => Some(target),
|
|
|
|
Command::NOTICE(ref target, _) if target.is_channel_name() => Some(target),
|
2019-08-27 15:05:51 +02:00
|
|
|
_ => self.source_nickname(),
|
2017-06-25 10:47:45 +02:00
|
|
|
}
|
|
|
|
}
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
2014-11-03 00:08:56 +01:00
|
|
|
|
2016-01-30 10:56:32 +01:00
|
|
|
impl From<Command> for Message {
|
|
|
|
fn from(cmd: Command) -> Message {
|
2017-06-19 19:59:26 +02:00
|
|
|
Message {
|
|
|
|
tags: None,
|
|
|
|
prefix: None,
|
|
|
|
command: cmd,
|
|
|
|
}
|
2016-01-30 10:56:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-03 00:08:56 +01:00
|
|
|
impl FromStr for Message {
|
2018-03-10 15:46:49 +01:00
|
|
|
type Err = ProtocolError;
|
2017-06-20 20:54:06 +02:00
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Message, Self::Err> {
|
2017-06-19 19:59:26 +02:00
|
|
|
if s.is_empty() {
|
2018-03-10 15:46:49 +01:00
|
|
|
return Err(ProtocolError::InvalidMessage {
|
2018-01-28 00:52:11 +01:00
|
|
|
string: s.to_owned(),
|
|
|
|
cause: MessageParseError::EmptyMessage,
|
2019-08-27 15:05:51 +02:00
|
|
|
});
|
2017-06-19 19:59:26 +02:00
|
|
|
}
|
2018-01-28 00:52:11 +01:00
|
|
|
|
|
|
|
let mut state = s;
|
|
|
|
|
2016-03-18 02:35:35 +01:00
|
|
|
let tags = if state.starts_with('@') {
|
2015-06-04 18:35:43 +02:00
|
|
|
let tags = state.find(' ').map(|i| &state[1..i]);
|
2017-06-19 19:59:26 +02:00
|
|
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
|
|
|
tags.map(|ts| {
|
|
|
|
ts.split(';')
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
.map(|s: &str| {
|
|
|
|
let mut iter = s.splitn(2, '=');
|
|
|
|
let (fst, snd) = (iter.next(), iter.next());
|
2020-01-30 22:39:47 +01:00
|
|
|
let snd = snd.map(unescape_tag_value);
|
2019-12-28 16:37:47 +01:00
|
|
|
Tag(fst.unwrap_or("").to_owned(), snd)
|
2017-06-19 19:59:26 +02:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
2015-06-04 18:35:43 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2016-03-18 02:35:35 +01:00
|
|
|
let prefix = if state.starts_with(':') {
|
2015-01-09 23:38:46 +01:00
|
|
|
let prefix = state.find(' ').map(|i| &state[1..i]);
|
2017-06-19 19:59:26 +02:00
|
|
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
2014-11-03 00:08:56 +01:00
|
|
|
prefix
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2017-12-30 18:21:20 +01:00
|
|
|
let line_ending_len = if state.ends_with("\r\n") {
|
|
|
|
"\r\n"
|
2018-01-02 04:25:25 +01:00
|
|
|
} else if state.ends_with('\r') {
|
2017-12-30 18:21:20 +01:00
|
|
|
"\r"
|
2018-01-02 04:25:25 +01:00
|
|
|
} else if state.ends_with('\n') {
|
2017-12-30 18:21:20 +01:00
|
|
|
"\n"
|
|
|
|
} else {
|
|
|
|
""
|
2019-08-27 15:05:51 +02:00
|
|
|
}
|
|
|
|
.len();
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2015-03-10 03:06:43 +01:00
|
|
|
let suffix = if state.contains(" :") {
|
2019-08-27 15:05:51 +02:00
|
|
|
let suffix = state
|
|
|
|
.find(" :")
|
|
|
|
.map(|i| &state[i + 2..state.len() - line_ending_len]);
|
2017-06-19 19:59:26 +02:00
|
|
|
state = state.find(" :").map_or("", |i| &state[..i + 1]);
|
2014-11-03 00:08:56 +01:00
|
|
|
suffix
|
|
|
|
} else {
|
2017-12-30 18:21:20 +01:00
|
|
|
state = &state[..state.len() - line_ending_len];
|
2014-11-03 00:08:56 +01:00
|
|
|
None
|
|
|
|
};
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2015-01-09 23:38:46 +01:00
|
|
|
let command = match state.find(' ').map(|i| &state[..i]) {
|
2014-11-03 08:02:29 +01:00
|
|
|
Some(cmd) => {
|
2017-06-19 19:59:26 +02:00
|
|
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
2014-11-03 08:02:29 +01:00
|
|
|
cmd
|
|
|
|
}
|
2018-01-06 19:31:08 +01:00
|
|
|
// If there's no arguments but the "command" starts with colon, it's not a command.
|
2019-08-27 15:05:51 +02:00
|
|
|
None if state.starts_with(':') => {
|
|
|
|
return Err(ProtocolError::InvalidMessage {
|
|
|
|
string: s.to_owned(),
|
|
|
|
cause: MessageParseError::InvalidCommand,
|
|
|
|
})
|
|
|
|
}
|
2018-01-06 19:31:08 +01:00
|
|
|
// If there's no arguments following the command, the rest of the state is the command.
|
|
|
|
None => {
|
|
|
|
let cmd = state;
|
|
|
|
state = "";
|
|
|
|
cmd
|
2019-08-27 15:05:51 +02:00
|
|
|
}
|
2014-11-03 00:08:56 +01:00
|
|
|
};
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2020-01-31 01:12:44 +01:00
|
|
|
let mut args: Vec<_> = state.splitn(14, ' ').filter(|s| !s.is_empty()).collect();
|
|
|
|
if let Some(suffix) = suffix {
|
|
|
|
args.push(suffix);
|
|
|
|
}
|
2018-01-28 00:52:11 +01:00
|
|
|
|
2020-03-07 06:17:35 +01:00
|
|
|
Message::with_tags(tags, prefix, command, args).map_err(|e| ProtocolError::InvalidMessage {
|
|
|
|
string: s.to_owned(),
|
|
|
|
cause: e,
|
2018-01-28 00:52:11 +01:00
|
|
|
})
|
2014-11-03 00:08:56 +01:00
|
|
|
}
|
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
|
2015-04-26 06:01:33 +02:00
|
|
|
impl<'a> From<&'a str> for Message {
|
|
|
|
fn from(s: &'a str) -> Message {
|
|
|
|
s.parse().unwrap()
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-18 02:39:58 +01:00
|
|
|
impl Display for Message {
|
2021-11-14 20:12:04 +01:00
|
|
|
/// Converts a Message into a String according to the IRC protocol.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```
|
|
|
|
/// # extern crate irc_proto;
|
|
|
|
/// # use irc_proto::Message;
|
|
|
|
/// # fn main() {
|
|
|
|
/// let msg = Message::new(
|
|
|
|
/// Some("ada"), "PRIVMSG", vec!["#channel", "Hi, everyone!"]
|
|
|
|
/// ).unwrap();
|
|
|
|
/// assert_eq!(msg.to_string(), ":ada PRIVMSG #channel :Hi, everyone!\r\n");
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2016-03-18 02:39:58 +01:00
|
|
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
2021-11-14 20:12:04 +01:00
|
|
|
if let Some(ref tags) = self.tags {
|
|
|
|
f.write_char('@')?;
|
|
|
|
for (i, tag) in tags.iter().enumerate() {
|
|
|
|
if i > 0 {
|
|
|
|
f.write_char(';')?;
|
|
|
|
}
|
|
|
|
f.write_str(&tag.0)?;
|
|
|
|
if let Some(ref value) = tag.1 {
|
|
|
|
f.write_char('=')?;
|
|
|
|
escape_tag_value(f, value)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.write_char(' ')?;
|
|
|
|
}
|
|
|
|
if let Some(ref prefix) = self.prefix {
|
|
|
|
write!(f, ":{} ", prefix)?
|
|
|
|
}
|
|
|
|
write!(f, "{}\r\n", String::from(&self.command))
|
2016-03-18 02:39:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-04 18:35:43 +02:00
|
|
|
/// A message tag as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
2017-12-25 03:23:19 +01:00
|
|
|
/// It consists of a tag key, and an optional value for the tag. Each message can contain a number
|
|
|
|
/// of tags (in the string format, they are separated by semicolons). Tags are used to add extended
|
|
|
|
/// information to a message under IRCv3.
|
2015-06-04 18:35:43 +02:00
|
|
|
#[derive(Clone, PartialEq, Debug)]
|
2016-06-19 06:37:58 +02:00
|
|
|
pub struct Tag(pub String, pub Option<String>);
|
2015-06-04 18:35:43 +02:00
|
|
|
|
2021-11-14 20:12:04 +01:00
|
|
|
fn escape_tag_value(f: &mut dyn Write, value: &str) -> FmtResult {
|
2020-01-30 22:39:47 +01:00
|
|
|
for c in value.chars() {
|
|
|
|
match c {
|
2021-11-14 20:12:04 +01:00
|
|
|
';' => f.write_str("\\:")?,
|
|
|
|
' ' => f.write_str("\\s")?,
|
|
|
|
'\\' => f.write_str("\\\\")?,
|
|
|
|
'\r' => f.write_str("\\r")?,
|
|
|
|
'\n' => f.write_str("\\n")?,
|
|
|
|
c => f.write_char(c)?,
|
2020-01-30 22:39:47 +01:00
|
|
|
}
|
|
|
|
}
|
2021-11-14 20:12:04 +01:00
|
|
|
Ok(())
|
2020-01-30 22:39:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn unescape_tag_value(value: &str) -> String {
|
|
|
|
let mut unescaped = String::with_capacity(value.len());
|
|
|
|
let mut iter = value.chars();
|
|
|
|
while let Some(c) = iter.next() {
|
2021-11-14 20:20:49 +01:00
|
|
|
let r = if c == '\\' {
|
2020-01-30 22:39:47 +01:00
|
|
|
match iter.next() {
|
2021-11-14 20:20:49 +01:00
|
|
|
Some(':') => ';',
|
|
|
|
Some('s') => ' ',
|
|
|
|
Some('\\') => '\\',
|
|
|
|
Some('r') => '\r',
|
|
|
|
Some('n') => '\n',
|
|
|
|
Some(c) => c,
|
2020-01-30 22:39:47 +01:00
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
} else {
|
2021-11-14 20:20:49 +01:00
|
|
|
c
|
|
|
|
};
|
|
|
|
unescaped.push(r);
|
2020-01-30 22:39:47 +01:00
|
|
|
}
|
|
|
|
unescaped
|
|
|
|
}
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2015-06-04 18:35:43 +02:00
|
|
|
use super::{Message, Tag};
|
2019-10-16 03:12:29 +02:00
|
|
|
use crate::command::Command::{Raw, PRIVMSG, QUIT};
|
2014-11-05 07:45:17 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(None, "PRIVMSG", vec!["test", "Testing!"]).unwrap(),
|
2017-06-19 19:59:26 +02:00
|
|
|
message
|
|
|
|
)
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
|
|
|
|
2015-01-02 21:20:26 +01:00
|
|
|
#[test]
|
2016-01-30 17:59:57 +01:00
|
|
|
fn source_nickname() {
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(None, "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
None
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("irc.test.net"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
None
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test!test@test"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test@test"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test!test@irc.test.com"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test!test@127.0.0.1"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test@test.com"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2016-02-07 09:00:35 +01:00
|
|
|
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2020-01-31 01:12:44 +01:00
|
|
|
Message::new(Some("test"), "PING", vec!["data"])
|
2017-06-19 19:59:26 +02:00
|
|
|
.unwrap()
|
|
|
|
.source_nickname(),
|
|
|
|
Some("test")
|
|
|
|
);
|
2015-01-02 21:20:26 +01:00
|
|
|
}
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[test]
|
2016-03-18 02:35:35 +01:00
|
|
|
fn to_string() {
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2020-01-31 01:12:44 +01:00
|
|
|
assert_eq!(&message.to_string()[..], "PRIVMSG test Testing!\r\n");
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: Some("test!test@test".into()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
|
|
|
&message.to_string()[..],
|
|
|
|
":test!test@test PRIVMSG test :Still testing!\r\n"
|
|
|
|
);
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn from_string() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2014-11-05 07:45:17 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2017-06-21 23:15:30 +02:00
|
|
|
assert_eq!(
|
|
|
|
"PRIVMSG test :Testing!\r\n".parse::<Message>().unwrap(),
|
|
|
|
message
|
|
|
|
);
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: Some("test!test@test".into()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2014-11-05 07:45:17 +01:00
|
|
|
};
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
2017-06-21 23:15:30 +02:00
|
|
|
":test!test@test PRIVMSG test :Still testing!\r\n"
|
|
|
|
.parse::<Message>()
|
|
|
|
.unwrap(),
|
2017-06-20 20:54:06 +02:00
|
|
|
message
|
2017-06-19 19:59:26 +02:00
|
|
|
);
|
2015-06-04 18:35:43 +02:00
|
|
|
let message = Message {
|
2017-06-19 19:59:26 +02:00
|
|
|
tags: Some(vec![
|
|
|
|
Tag(format!("aaa"), Some(format!("bbb"))),
|
|
|
|
Tag(format!("ccc"), None),
|
|
|
|
Tag(format!("example.com/ddd"), Some(format!("eee"))),
|
|
|
|
]),
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: Some("test!test@test".into()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing with tags!")),
|
2015-06-04 18:35:43 +02:00
|
|
|
};
|
2017-06-19 19:59:26 +02:00
|
|
|
assert_eq!(
|
|
|
|
"@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \
|
2019-08-27 15:05:51 +02:00
|
|
|
tags!\r\n"
|
2017-06-21 23:15:30 +02:00
|
|
|
.parse::<Message>()
|
|
|
|
.unwrap(),
|
2017-06-20 20:54:06 +02:00
|
|
|
message
|
2017-06-19 19:59:26 +02:00
|
|
|
)
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
2015-01-01 02:05:10 +01:00
|
|
|
|
2017-12-30 18:21:20 +01:00
|
|
|
#[test]
|
|
|
|
fn from_string_atypical_endings() {
|
|
|
|
let message = Message {
|
|
|
|
tags: None,
|
|
|
|
prefix: None,
|
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
"PRIVMSG test :Testing!\r".parse::<Message>().unwrap(),
|
|
|
|
message
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"PRIVMSG test :Testing!\n".parse::<Message>().unwrap(),
|
|
|
|
message
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
"PRIVMSG test :Testing!".parse::<Message>().unwrap(),
|
|
|
|
message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-12-13 20:32:04 +01:00
|
|
|
#[test]
|
|
|
|
fn from_and_to_string() {
|
2019-08-27 15:05:51 +02:00
|
|
|
let message =
|
|
|
|
"@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \
|
|
|
|
tags!\r\n";
|
2017-12-13 20:32:04 +01:00
|
|
|
assert_eq!(message.parse::<Message>().unwrap().to_string(), message);
|
|
|
|
}
|
|
|
|
|
2015-01-01 02:05:10 +01:00
|
|
|
#[test]
|
|
|
|
fn to_message() {
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2015-01-01 02:05:10 +01:00
|
|
|
prefix: None,
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Testing!")),
|
2015-01-01 02:05:10 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = "PRIVMSG test :Testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-01-01 02:05:10 +01:00
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: Some("test!test@test".into()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
2015-01-01 02:05:10 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
|
|
|
|
2015-03-10 03:06:43 +01:00
|
|
|
#[test]
|
|
|
|
fn to_message_with_colon_in_arg() {
|
|
|
|
// Apparently, UnrealIRCd (and perhaps some others) send some messages that include
|
|
|
|
// colons within individual parameters. So, let's make sure it parses correctly.
|
|
|
|
let message = Message {
|
2015-06-04 18:35:43 +02:00
|
|
|
tags: None,
|
2018-09-17 13:52:22 +02:00
|
|
|
prefix: Some("test!test@test".into()),
|
2016-01-30 10:56:32 +01:00
|
|
|
command: Raw(
|
2017-06-19 19:59:26 +02:00
|
|
|
format!("COMMAND"),
|
2020-01-31 01:12:44 +01:00
|
|
|
vec![format!("ARG:test"), format!("Testing!")],
|
2016-01-30 10:56:32 +01:00
|
|
|
),
|
2015-03-10 03:06:43 +01:00
|
|
|
};
|
2015-04-26 06:11:51 +02:00
|
|
|
let msg: Message = ":test!test@test COMMAND ARG:test :Testing!\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
2015-03-10 03:06:43 +01:00
|
|
|
}
|
|
|
|
|
2018-01-06 19:31:08 +01:00
|
|
|
#[test]
|
|
|
|
fn to_message_no_prefix_no_args() {
|
|
|
|
let message = Message {
|
|
|
|
tags: None,
|
|
|
|
prefix: None,
|
|
|
|
command: QUIT(None),
|
|
|
|
};
|
|
|
|
let msg: Message = "QUIT\r\n".into();
|
|
|
|
assert_eq!(msg, message);
|
|
|
|
}
|
|
|
|
|
2015-01-01 02:05:10 +01:00
|
|
|
#[test]
|
2015-03-22 04:08:41 +01:00
|
|
|
#[should_panic]
|
2015-01-01 02:05:10 +01:00
|
|
|
fn to_message_invalid_format() {
|
2015-04-26 06:11:51 +02:00
|
|
|
let _: Message = ":invalid :message".into();
|
2015-01-01 02:05:10 +01:00
|
|
|
}
|
2019-12-28 16:37:47 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_message_tags_escapes() {
|
|
|
|
let msg = "@tag=\\:\\s\\\\\\r\\n\\a\\ :test PRIVMSG #test :test\r\n"
|
|
|
|
.parse::<Message>()
|
|
|
|
.unwrap();
|
|
|
|
let message = Message {
|
|
|
|
tags: Some(vec![Tag("tag".to_string(), Some("; \\\r\na".to_string()))]),
|
|
|
|
prefix: Some("test".into()),
|
|
|
|
command: PRIVMSG("#test".to_string(), "test".to_string()),
|
|
|
|
};
|
|
|
|
assert_eq!(msg, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_string_tags_escapes() {
|
|
|
|
let msg = Message {
|
|
|
|
tags: Some(vec![Tag("tag".to_string(), Some("; \\\r\na".to_string()))]),
|
|
|
|
prefix: Some("test".into()),
|
|
|
|
command: PRIVMSG("#test".to_string(), "test".to_string()),
|
|
|
|
}
|
|
|
|
.to_string();
|
2020-01-31 23:11:28 +01:00
|
|
|
let message = "@tag=\\:\\s\\\\\\r\\na :test PRIVMSG #test test\r\n";
|
2019-12-28 16:37:47 +01:00
|
|
|
assert_eq!(msg, message);
|
|
|
|
}
|
2021-05-04 19:35:36 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_message_with_colon_in_suffix() {
|
2023-04-12 16:18:34 +02:00
|
|
|
let msg = "PRIVMSG #test ::test".parse::<Message>().unwrap();
|
2021-05-04 19:35:36 +02:00
|
|
|
let message = Message {
|
|
|
|
tags: None,
|
|
|
|
prefix: None,
|
2023-04-12 16:18:34 +02:00
|
|
|
command: PRIVMSG("#test".to_string(), ":test".to_string()),
|
2021-05-04 19:35:36 +02:00
|
|
|
};
|
|
|
|
assert_eq!(msg, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_string_with_colon_in_suffix() {
|
|
|
|
let msg = Message {
|
|
|
|
tags: None,
|
|
|
|
prefix: None,
|
|
|
|
command: PRIVMSG("#test".to_string(), ":test".to_string()),
|
|
|
|
}
|
|
|
|
.to_string();
|
|
|
|
let message = "PRIVMSG #test ::test\r\n";
|
|
|
|
assert_eq!(msg, message);
|
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|