From edc770e22e54e1c9f6cbfc0d9595dd4e6d7ba68a Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Wed, 1 Jul 2015 18:44:09 -0400 Subject: [PATCH] Implemented IRCv3 metadata (without metadata-notify). --- src/client/data/caps.rs | 4 +++ src/client/data/command.rs | 70 +++++++++++++++++++++++++++++++++++++ src/client/data/response.rs | 22 ++++++++++-- 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/client/data/caps.rs b/src/client/data/caps.rs index 0a27255..e4ab055 100644 --- a/src/client/data/caps.rs +++ b/src/client/data/caps.rs @@ -28,6 +28,8 @@ pub enum Capability { ServerTime, /// [userhost-in-names](http://ircv3.net/specs/extensions/userhost-in-names-3.2.html) UserhostInNames, + /// [metadata](http://ircv3.net/specs/core/metadata-3.2.html) + Metadata, /// Custom IRCv3 capability extensions Custom(&'static str), } @@ -55,6 +57,7 @@ impl AsRef for Capability { Capability::InviteNotify => "invite-notify", Capability::ServerTime => "server-time", Capability::UserhostInNames => "userhost-in-names", + Capability::Metadata => "metadata", Capability::Custom(s) => s, } } @@ -78,6 +81,7 @@ mod test { assert_eq!(InviteNotify.as_ref(), "invite-notify"); assert_eq!(ServerTime.as_ref(), "server-time"); assert_eq!(UserhostInNames.as_ref(), "userhost-in-names"); + assert_eq!(Metadata.as_ref(), "metadata"); assert_eq!(Custom("example").as_ref(), "example"); } } diff --git a/src/client/data/command.rs b/src/client/data/command.rs index 2c719c7..c50ea85 100644 --- a/src/client/data/command.rs +++ b/src/client/data/command.rs @@ -157,6 +157,8 @@ pub enum Command { // JOIN(String, Option, Option), // IRCv3.2 extensions + /// METADATA target COMMAND [params] :[param] + METADATA(String, MetadataSubCommand, Option>, Option), /// MONITOR command [nicklist] MONITOR(String, Option), /// CHGHOST user host @@ -333,6 +335,11 @@ impl Into for Command { Command::ACCOUNT(a) => Message::from_owned(None, string("ACCOUNT"), Some(vec![a]), None), + Command::METADATA(t, c, None, p) => + Message::from_owned(None, string("METADATA"), Some(vec![t, c.string()]), p), + Command::METADATA(t, c, Some(a), p) => + Message::from_owned(None, string("METADATA"), + Some(vec![t, c.string()].into_iter().chain(a).collect()), p), Command::MONITOR(c, Some(t)) => Message::from_owned(None, string("MONITOR"), Some(vec![c, t]), None), Command::MONITOR(c, None) => @@ -1085,6 +1092,24 @@ impl<'a> From<&'a Message> for Result { return Err(invalid_input()) } } + } else if let "METADATA" = &m.command[..] { + if m.args.len() == 2 { + match m.suffix { + Some(_) => return Err(invalid_input()), + None => match m.args[1].parse() { + Ok(c) => Command::METADATA(m.args[0].clone(), c, None, None), + Err(_) => return Err(invalid_input()), + }, + } + } else if m.args.len() > 2 { + match m.args[1].parse() { + Ok(c) => Command::METADATA(m.args[0].clone(), c, Some(m.args[1..].to_owned()), + m.suffix.clone()), + Err(_) => return Err(invalid_input()), + } + } else { + return Err(invalid_input()) + } } else if let "MONITOR" = &m.command[..] { if m.args.len() == 1 { Command::MONITOR(m.args[0].clone(), m.suffix.clone()) @@ -1176,6 +1201,51 @@ impl FromStr for CapSubCommand { } } +/// A list of all the subcommands for the +/// [metadata extension](http://ircv3.net/specs/core/metadata-3.2.html). +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MetadataSubCommand { + /// Looks up the value for some keys. + GET, + /// Lists all of the metadata keys and values. + LIST, + /// Sets the value for some key. + SET, + /// Removes all metadata. + CLEAR, +} + +impl MetadataSubCommand { + /// Gets the string that corresponds to this subcommand. + pub fn to_str(&self) -> &str { + match self { + &MetadataSubCommand::GET => "GET", + &MetadataSubCommand::LIST => "LIST", + &MetadataSubCommand::SET => "SET", + &MetadataSubCommand::CLEAR => "CLEAR", + } + } + + // This makes some earlier lines shorter. + fn string(&self) -> String { + self.to_str().to_owned() + } +} + +impl FromStr for MetadataSubCommand { + type Err = &'static str; + fn from_str(s: &str) -> StdResult { + match s { + "GET" => Ok(MetadataSubCommand::GET), + "LIST" => Ok(MetadataSubCommand::LIST), + "SET" => Ok(MetadataSubCommand::SET), + "CLEAR" => Ok(MetadataSubCommand::CLEAR), + _ => Err("Failed to parse METADATA subcommand."), + } + } +} + + /// Produces an invalid_input IoError. fn invalid_input() -> Error { Error::new(ErrorKind::InvalidInput, "Failed to parse malformed message as command.") diff --git a/src/client/data/response.rs b/src/client/data/response.rs index f0567e6..3d48fee 100644 --- a/src/client/data/response.rs +++ b/src/client/data/response.rs @@ -189,7 +189,12 @@ pub enum Response { RPL_MONLIST = 732, /// 733 :End of MONITOR list RPL_ENDOFMONLIST = 733, - + /// 760 : + RPL_WHOISKEYVALUE = 760, + /// 761 :[] + RPL_KEYVALUE = 761, + /// 762 :end of metadata + RPL_METADATAEND = 762, // Error replies /// 401 :No such nick/channel @@ -300,6 +305,19 @@ pub enum Response { ERR_USERSDONTMATCH = 502, /// 734 :Monitor list is full. ERR_MONLISTFULL = 734, + /// 764 :metadata limit reached + ERR_METADATALIMIT = 764, + /// 765 :invalid metadata target + ERR_TARGETINVALID = 765, + /// 766 :no matching key + ERR_NOMATCHINGKEY = 766, + /// 767 :invalid metadata key + ERR_KEYINVALID = 767, + /// 768 :key not set + ERR_KEYNOTSET = 768, + /// 769 :permission denied + ERR_KEYNOPERMISSION = 779, + } impl Response { @@ -329,7 +347,7 @@ impl FromStr for Response { (rc > 420 && rc < 425) || (rc > 430 && rc < 434) || rc == 436 || rc == 437 || (rc > 440 && rc < 447) || rc == 451 || (rc > 460 && rc < 468) || (rc > 470 && rc < 479) || (rc > 480 && rc < 486) || rc == 491 || rc == 501 || - rc == 502 || (rc > 729 && rc < 735) { + rc == 502 || (rc > 729 && rc < 735) || (rc > 759 && rc < 770 && rc != 763) { Ok(unsafe { transmute(rc) }) } else { Err("Failed to parse due to unknown response code.")