diff --git a/src/client/data/config.rs b/src/client/data/config.rs index 2e5531b..d1d14f1 100644 --- a/src/client/data/config.rs +++ b/src/client/data/config.rs @@ -44,6 +44,12 @@ pub struct Config { pub ping_time: Option, /// The amount of time in seconds for a client to reconnect due to no ping response. pub ping_timeout: Option, + /// Whether the client should use NickServ GHOST to reclaim its primary nickname if it is in use. + /// This has no effect if `nick_password` is not set. + pub should_ghost: Option, + /// The command(s) that should be sent to NickServ to recover a nickname. The nickname and password will be appended in that order after the command. + /// E.g. `["RECOVER", "RELEASE"]` means `RECOVER nick pass` and `RELEASE nick pass` will be sent in that order. + pub ghost_sequence: Option>, /// A map of additional options to be stored in config. pub options: Option>, } @@ -168,6 +174,18 @@ impl Config { self.ping_timeout.as_ref().map(|t| *t).unwrap_or(10) } + /// Gets whether or not to use NickServ GHOST + /// This defaults to false when not specified. + pub fn should_ghost(&self) -> bool { + self.should_ghost.as_ref().map(|u| *u).unwrap_or(false) + } + + /// Gets the NickServ command sequence to recover a nickname. + /// This defaults to `["GHOST"]` when not specified. + pub fn ghost_sequence(&self) -> Vec<&str> { + self.ghost_sequence.as_ref().map(|v| v.iter().map(|s| &s[..]).collect()).unwrap_or(vec!["GHOST"]) + } + /// Looks up the specified string in the options map. /// This uses indexing, and thus panics when the string is not present. /// This will also panic if used and there are no options. @@ -202,6 +220,8 @@ mod test { user_info: None, ping_time: None, ping_timeout: None, + should_ghost: None, + ghost_sequence: None, options: Some(HashMap::new()), }; assert_eq!(Config::load(Path::new("client_config.json")).unwrap(), cfg); @@ -226,6 +246,8 @@ mod test { user_info: None, ping_time: None, ping_timeout: None, + should_ghost: None, + ghost_sequence: None, options: Some(HashMap::new()), }; assert_eq!(Config::load("client_config.json").unwrap(), cfg); diff --git a/src/client/server/mod.rs b/src/client/server/mod.rs index f02de4c..b80b3cd 100644 --- a/src/client/server/mod.rs +++ b/src/client/server/mod.rs @@ -342,6 +342,16 @@ impl IrcServer where Connection: Reconnect Command::Response(Response::RPL_ENDOFMOTD, _, _) | Command::Response(Response::ERR_NOMOTD, _, _) => { if self.config().nick_password() != "" { + let mut index = self.state.alt_nick_index.write().unwrap(); + if self.config().should_ghost() && *index != 0 { + for seq in self.config().ghost_sequence().iter() { + try!(self.send(NICKSERV( + format!("{} {} {}", seq, self.config().nickname(), self.config().nick_password()) + ))); + } + *index = 0; + try!(self.send(NICK(self.config().nickname().to_owned()))) + } try!(self.send(NICKSERV( format!("IDENTIFY {}", self.config().nick_password()) ))) @@ -358,7 +368,7 @@ impl IrcServer where Connection: Reconnect let alt_nicks = self.config().alternate_nicknames(); let mut index = self.state.alt_nick_index.write().unwrap(); if *index >= alt_nicks.len() { - panic!("All specified nicknames were in use.") + panic!("All specified nicknames were in use or disallowed.") } else { try!(self.send(NICK(alt_nicks[*index].to_owned()))); *index += 1; @@ -548,6 +558,49 @@ mod test { "NICKSERV IDENTIFY password\r\nJOIN #test\r\nJOIN #test2\r\n"); } + #[test] + fn handle_end_motd_with_ghost() { + let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ + :irc.test.net 376 test2 :End of /MOTD command.\r\n"; + let server = IrcServer::from_connection(Config { + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + should_ghost: Some(true), + .. Default::default() + }, Connection::new( + Cursor::new(value.as_bytes().to_vec()), Vec::new() + )); + for message in server.iter() { + println!("{:?}", message); + } + assert_eq!(&get_server_value(server)[..], + "NICK :test2\r\nNICKSERV GHOST test password\r\nNICK :test\r\nNICKSERV IDENTIFY password\r\nJOIN #test\r\nJOIN #test2\r\n"); + } + + #[test] + fn handle_end_motd_with_ghost_seq() { + let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ + :irc.test.net 376 test2 :End of /MOTD command.\r\n"; + let server = IrcServer::from_connection(Config { + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + should_ghost: Some(true), + ghost_sequence: Some(vec![format!("RECOVER"), format!("RELEASE")]), + .. Default::default() + }, Connection::new( + Cursor::new(value.as_bytes().to_vec()), Vec::new() + )); + for message in server.iter() { + println!("{:?}", message); + } + assert_eq!(&get_server_value(server)[..], + "NICK :test2\r\nNICKSERV RECOVER test password\r\nNICKSERV RELEASE test password\r\nNICK :test\r\nNICKSERV IDENTIFY password\r\nJOIN #test\r\nJOIN #test2\r\n"); + } + #[test] fn handle_end_motd_with_umodes() { let value = ":irc.test.net 376 test :End of /MOTD command.\r\n";