#![allow(non_camel_case_types)] use std::str::FromStr; use std::string::{ ToString }; use std::borrow::{ Cow, Borrow, ToOwned }; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum MsgType { /// Plain old IRC messages, as defined in [rfc2812][rfc] /// rfc: http://tools.ietf.org/html/rfc2812 Irc, /// Ctcp messages, wrapped in \u{1} Ctcp } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Message<'a> { pub prefix: Option>, pub command: Cow<'a, String>, pub content: Vec>, pub suffix: Option>, pub msg_type: MsgType } impl<'a> Message<'a> { pub fn new(prefix: Option>, command: Cow<'a, String>, content: Vec>, suffix: Option>, msg_type: MsgType) -> Message<'a> { Message { prefix: prefix, command: command, content: content, suffix: suffix, msg_type: msg_type } } } impl<'a> FromStr for Message<'a> { type Err = ::IrscError; fn from_str(i: &str) -> Result, ::IrscError> { info!("Attempting to parse message: {}", i); let len = i.len(); let mut s = i; let prefix = if len >= 1 && s.chars().next() == Some(':') { s.find(' ').map(|i| { let p = s.slice_chars(1, i).to_owned(); s = &s[i + 1..]; p }) } else { None }; let command = s.find(' ').map(|i| { let p = s.slice_chars(0, i).to_owned(); s = &s[i..]; p }).map(|c| c.parse()).map(|c| Some(Cow::Owned(c.unwrap()))); // TODO: Parse last non-suffix argument as suffix if no suffix // with colon is available. let mut content = Vec::with_capacity(15); let mut suffix = None; while s.len() > 0 { if s.chars().next() == Some(':') { suffix = Some(s[1..].to_owned()); break } s.find(' ').map(|i| { if i > 0 { content.push(Cow::Owned(s.slice_chars(0, i).to_owned())); s = &s[i..]; } }); if s.chars().next() == Some(' ') { s = &s[1..] }; } let msg_type = if suffix.as_ref() .and_then(|s| s.chars().next()) == Some('\u{1}') { MsgType::Ctcp } else { MsgType::Irc }; command.map(move |c| Ok(Message::new(prefix.map(Cow::Owned), c.unwrap(), content, // strip \{1} if CTCP message // strip \r\n for each line, relying on their existence match msg_type { MsgType::Irc => suffix.map(|s| s[0..s.len() - 2].to_string()).map(Cow::Owned), MsgType::Ctcp => suffix.map(|s| s[1..s.len() - 3].to_string()).map(Cow::Owned) }, msg_type )) ).unwrap() } } impl<'a> ToString for Message<'a> { fn to_string(&self) -> String { let mut s = String::with_capacity(512); if let Some(ref p) = self.prefix { s.push(':'); s.push_str(&p); s.push(' '); } s.push_str(&self.command); s.push(' '); for part in self.content.iter() { s.push_str(&part); s.push(' '); } if let Some(ref p) = self.suffix { s.push(':'); if let MsgType::Ctcp = self.msg_type { s.push('\u{1}') } s.push_str(&p); if let MsgType::Ctcp = self.msg_type { s.push('\u{1}') } } s.push_str("\r\n"); s } } /* pub trait Command { fn name(&self) -> String; fn to_message(&self) -> Message; fn from_message(msg: &Message) -> Option } macro_rules! command ( ($name: ident { $($field: ident: $t: ty),* } to $to_msg: expr; from $from_msg: expr;) => ( pub struct $name { $(pub $field: $t),* } impl Command for $name { fn name(&self) -> String { stringify!($name).to_owned() } fn to_message(&self) -> Message { ($to_msg)(self) } fn from_message(msg: &Message) -> Option<$name> { ($from_msg)(msg) } } ) );*/ /* command!(Pass { password: String } to |&:s: &Pass| Message::new(None, "PASS".to_owned(), Vec::new(), Some(s.password.clone())); from |&:msg: &Message| msg.clone().suffix.map(|s| Pass { password: s });); command!(Ping { server1: Option, server2: Option } to |&:s :&Ping| { let mut v = Vec::new(); if let Some(ref s) = s.server1 { v.push(s.clone()) } if let Some(ref s) = s.server2 { v.push(s.clone()) } Message::new(None, "PING".to_owned(), v, None) }; from |&:msg: &Message| { let mut c = msg.content.clone(); Some(Ping { server2: c.pop(), server1: c.pop() }) }; ); command!(Pong { server1: Option, server2: Option } to |&:s: &Pong| { let mut v = Vec::new(); if let Some(ref s) = s.server1 { v.push(s.clone()) } if let Some(ref s) = s.server2 { v.push(s.clone()) } Message::new(None, "PONG".to_owned(), v, None) }; from |&:msg: &Message| { let mut c = msg.content.clone(); Some(Pong { server2: c.pop(), server1: c.pop() }) }; ); command!(PrivMsg { from: Option, to: String, content: String } to |&:s: &PrivMsg| { Message::new(s.from.clone(), "PRIVMSG".to_owned(), vec![s.to.clone()], Some(s.content.clone())) }; from |&:msg: &Message| { msg.content.clone().pop().map( |c| PrivMsg { from: msg.prefix.clone(), to: c, content: msg.suffix.clone().unwrap_or(String::new()) }) }; ); */ #[derive(Clone, PartialEq, Eq, Hash)] pub enum Mode { Away, Invisible, Wallops, Restricted, Operator, LocalOperator, ServerNotices, Custom(String) } #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum SetMode { Plus, Minus } #[derive(Clone, PartialEq, Eq, Hash)] pub enum Command<'a> { Pass { password: Cow<'a, String> }, Nick { nickname: Cow<'a, String> }, User { user: Cow<'a, String>, mode: Cow<'a, String>, unused: Cow<'a, String>, realname: Cow<'a, String> }, Oper { name: Cow<'a, String>, password: Cow<'a, String> }, Mode { nickname: Cow<'a, String>, mode: &'a [(SetMode, Mode)] }, Service { nickname: Cow<'a, String>, reserved: Cow<'a, String>, distribution: Cow<'a, String>, service_type: Cow<'a, String>, reserved2: Cow<'a, String>, info: Cow<'a, String> }, Quit { message: Option> }, SQuit { server: Cow<'a, String>, comment: Cow<'a, String> }, Join { channels: &'a [(Cow<'a, String>, Option>)] }, Join_leave_all, Part { channels: &'a [Cow<'a, String>], message: Cow<'a, String> }, /* Topic = 1011, Names = 1012, List = 1013, Invite = 1014, Kick = 1015, */ PrivMsg { from: Option>, to: Cow<'a, String>, content: Cow<'a, String> }, Notice { to: Cow<'a, String>, content: Cow<'a, String> }, /* Motd = 1018, LUsers = 1019, Version = 1020, Stats = 1021, Links = 1022, Time = 1023, Connect = 1024, Trace = 1025, Admin = 1026, Info = 1027, ServList = 1028, SQuery = 1029, Who = 1030, WhoIs = 1031, WhoWas = 1032, Kill = 1033, */ Ping { server1: Option>, server2: Option> }, Pong { server1: Option>, server2: Option> }, /* Error = 1036, Away = 1037, Rehash = 1038, Die = 1039, Restart = 1040, Summon = 1041, Users = 1042, Wallops = 1043, Userhost = 1044, Ison = 1045,*/ Ctcp { command: Cow<'a, String> }, /* /// "Welcome to the Internet Relay Network !@" RPL_WELCOME = 001, /// "Your host is , running version " RPL_YOURHOST = 002, /// "This server was created " RPL_CREATED = 003, /// " " RPL_MYINFO = 004, /// "Try server , port " /// Sent by the server to a user to suggest an alternative /// server. This is often used when the connection is /// refused because the server is already full. RPL_BOUNCE = 005, /// ":*1 *( " " )" /// - Reply format used by USERHOST to list replies to /// the query list. The reply string is composed as /// follows: /// /// reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname /// /// The '*' indicates whether the client has registered /// as an Operator. The '-' or '+' characters represent /// whether the client has set an AWAY message or not /// respectively. RPL_USERHOST = 302, /// ":*1 *( " " )" /// - Reply format used by ISON to list replies to the query list. RPL_ISON = 303, /// " :" RPL_AWAY = 301, /// ":You are no longer marked as being away" RPL_UNAWAY = 305, /// ":You have been marked as being away" /// - These replies are used with the AWAY command (if /// allowed). RPL_AWAY is sent to any client sending a /// PRIVMSG to a client which is away. RPL_AWAY is only /// sent by the server to which the client is connected. /// Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the /// client removes and sets an AWAY message./ RPL_NOWAWAY = 306, /// " * :" RPL_WHOISUSER = 311, /// " :" RPL_WHOISSERVER = 312, /// " :is an IRC operator" RPL_WHOISOPERATOR = 313, /// " :seconds idle" RPL_WHOISIDLE = 317, /// " :End of WHOIS list" RPL_ENDOFWHOIS = 318, /// " :*( ( "@" / "+" ) " " )" /// - Replies 311 - 313, 317 - 319 are all replies /// generated in response to a WHOIS message. Given that /// there are enough parameters present, the answering /// server MUST either formulate a reply out of the above /// numerics (if the query nick is found) or return an /// error reply. The '*' in RPL_WHOISUSER is there as /// the literal character and not as a wild card. For /// each reply set, only RPL_WHOISCHANNELS may appear /// more than once (for long lists of channel names). /// The '@' and '+' characters next to the channel name /// indicate whether a client is a channel operator or /// has been granted permission to speak on a moderated /// channel. The RPL_ENDOFWHOIS reply is used to mark /// the end of processing a WHOIS message. RPL_WHOISCHANNELS = 319, /// " * :" RPL_WHOWASUSER = 314, /// " :End of WHOWAS" /// - When replying to a WHOWAS message, a server MUST use /// the replies RPL_WHOWASUSER, RPL_WHOISSERVER or /// ERR_WASNOSUCHNICK for each nickname in the presented /// list. At the end of all reply batches, there MUST /// be RPL_ENDOFWHOWAS (even if there was only one reply /// and it was an error). RPL_ENDOFWHOWAS = 369, /// Obsolete. Not used. #[deprecated = "Obsolete. Not used."] RPL_LISTSTART = 321, /// " <# visible> :" RPL_LIST = 322, /// ":End of LIST" /// - Replies RPL_LIST, RPL_LISTEND mark the actual replies /// with data and end of the server's response to a LIST /// command. If there are no channels available to return, /// only the end reply MUST be sent. RPL_LISTEND = 323, /// " " RPL_UNIQOPIS = 325, /// " " RPL_CHANNELMODEIS = 324, /// " :No topic is set" RPL_NOTOPIC = 331, /// " :" /// - When sending a TOPIC message to determine the /// channel topic, one of two replies is sent. If /// the topic is set, RPL_TOPIC is sent back else /// RPL_NOTOPIC. RPL_TOPIC = 332, /// " " /// - Returned by the server to indicate that the /// attempted INVITE message was successful and is /// being passed onto the end client. RPL_INVITING = 341, /// " :Summoning user to IRC" /// - Returned by a server answering a SUMMON message to /// indicate that it is summoning that user. RPL_SUMMONING = 342, /// " " RPL_INVITELIST = 346, /// " :End of channel invite list" /// - When listing the 'invitations masks' for a given channel, /// a server is required to send the list back using the /// RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A /// separate RPL_INVITELIST is sent for each active mask. /// After the masks have been listed (or if none present) a /// RPL_ENDOFINVITELIST MUST be sent. RPL_ENDOFINVITELIST = 347, /// " " RPL_EXCEPTLIST = 348, /// " :End of channel exception list" /// - When listing the 'exception masks' for a given channel, /// a server is required to send the list back using the /// RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A /// separate RPL_EXCEPTLIST is sent for each active mask. /// After the masks have been listed (or if none present) /// a RPL_ENDOFEXCEPTLIST MUST be sent./ RPL_ENDOFEXCEPTLIST = 349, /// ". :" /// - Reply by the server showing its version details. /// The is the version of the software being /// used (including any patchlevel revisions) and the /// is used to indicate if the server is /// running in "debug mode". /// /// The "comments" field may contain any comments about /// the version or further version details. RPL_VERSION = 351, /// " /// ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] /// : " RPL_WHOREPLY = 352, /// " :End of WHO list" /// - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used /// to answer a WHO message. The RPL_WHOREPLY is only /// sent if there is an appropriate match to the WHO /// query. If there is a list of parameters supplied /// with a WHO message, a RPL_ENDOFWHO MUST be sent /// after processing each list item with being /// the item. RPL_ENDOFWHO = 315, /// "( "=" / "*" / "@" ) /// :[ "@" / "+" ] *( " " [ "@" / "+" ] ) /// - "@" is used for secret channels, "*" for private /// channels, and "=" for others (public channels). RPL_NAMREPLY = 353, /// " :End of NAMES list" /// - To reply to a NAMES message, a reply pair consisting /// of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the /// server back to the client. If there is no channel /// found as in the query, then only RPL_ENDOFNAMES is /// returned. The exception to this is when a NAMES /// message is sent with no parameters and all visible /// channels and contents are sent back in a series of /// RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark /// the end. RPL_ENDOFNAMES = 366, /// " : " RPL_LINKS = 364, /// " :End of LINKS list" /// - In replying to the LINKS message, a server MUST send /// replies back using the RPL_LINKS numeric and mark the /// end of the list using an RPL_ENDOFLINKS reply. RPL_ENDOFLINKS = 365, /// " " RPL_BANLIST = 367, /// " :End of channel ban list" /// - When listing the active 'bans' for a given channel, /// a server is required to send the list back using the /// RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate /// RPL_BANLIST is sent for each active banmask. After the /// banmasks have been listed (or if none present) a /// RPL_ENDOFBANLIST MUST be sent. RPL_ENDOFBANLIST = 368, /// ":" RPL_INFO = 371, /// ":End of INFO list" /// - A server responding to an INFO message is required to /// send all its 'info' in a series of RPL_INFO messages /// with a RPL_ENDOFINFO reply to indicate the end of the /// replies. RPL_ENDOFINFO = 374, /// ":- Message of the day - " RPL_MOTDSTART = 375, /// ":- " RPL_MOTD = 372, /// ":End of MOTD command" /// - When responding to the MOTD message and the MOTD file /// is found, the file is displayed line by line, with /// each line no longer than 80 characters, using /// RPL_MOTD format replies. These MUST be surrounded /// by a RPL_MOTDSTART (before the RPL_MOTDs) and an /// RPL_ENDOFMOTD (after). RPL_ENDOFMOTD = 376, /// ":You are now an IRC operator" /// - RPL_YOUREOPER is sent back to a client which has /// just successfully issued an OPER message and gained /// operator status. RPL_YOUREOPER = 381, /// " :Rehashing" /// - If the REHASH option is used and an operator sends /// a REHASH message, an RPL_REHASHING is sent back to /// the operator. RPL_REHASHING = 382, /// "You are service " /// - Sent by the server to a service upon successful /// registration. RPL_YOURESERVICE = 383, /// " :" /// - When replying to the TIME message, a server MUST send /// the reply using the RPL_TIME format above. The string /// showing the time need only contain the correct day and /// time there. There is no further requirement for the /// time string. RPL_TIME = 391, /// ":UserID Terminal Host" RPL_USERSSTART = 392, /// ": " RPL_USERS = 393, /// ":End of users" RPL_ENDOFUSERS = 394, /// ":Nobody logged in" /// - If the USERS message is handled by a server, the /// replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and /// RPL_NOUSERS are used. RPL_USERSSTART MUST be sent /// first, following by either a sequence of RPL_USERS /// or a single RPL_NOUSER. Following this is /// RPL_ENDOFUSERS. RPL_NOUSERS = 395, /// "Link /// V /// /// " RPL_TRACELINK = 200, /// "Try. " RPL_TRACECONNECTING = 201, /// "H.S. " RPL_TRACEHANDSHAKE = 202, /// "???? []" RPL_TRACEUNKNOWN = 203, /// "Oper " RPL_TRACEOPERATOR = 204, /// "User " RPL_TRACEUSER = 205, /// "Serv S C /// @ V" RPL_TRACESERVER = 206, /// "Service " RPL_TRACESERVICE = 207, /// " 0 " RPL_TRACENEWTYPE = 208, /// "Class " RPL_TRACECLASS = 209, /// Unused. #[deprecated = "Unused."] RPL_TRACERECONNECT = 210, /// "File " RPL_TRACELOG = 261, /// " :End of TRACE" /// - The RPL_TRACE* are all returned by the server in /// response to the TRACE message. How many are /// returned is dependent on the TRACE message and /// whether it was sent by an operator or not. There /// is no predefined order for which occurs first. /// Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and /// RPL_TRACEHANDSHAKE are all used for connections /// which have not been fully established and are either /// unknown, still attempting to connect or in the /// process of completing the 'server handshake'. /// RPL_TRACELINK is sent by any server which handles /// a TRACE message and has to pass it on to another /// server. The list of RPL_TRACELINKs sent in /// response to a TRACE command traversing the IRC /// network should reflect the actual connectivity of /// the servers themselves along that path. /// RPL_TRACENEWTYPE is to be used for any connection /// which does not fit in the other categories but is /// being displayed anyway. /// RPL_TRACEEND is sent to indicate the end of the list./ RPL_TRACEEND = 262, /// " /// ///