Add NickServ GHOST functionality

This commit is contained in:
angelsl 2016-02-09 18:24:52 +08:00
parent 2cf0b26dca
commit 5f20e715fd
2 changed files with 76 additions and 1 deletions

View file

@ -44,6 +44,12 @@ pub struct Config {
pub ping_time: Option<u32>,
/// The amount of time in seconds for a client to reconnect due to no ping response.
pub ping_timeout: Option<u32>,
/// 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<bool>,
/// 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<Vec<String>>,
/// A map of additional options to be stored in config.
pub options: Option<HashMap<String, String>>,
}
@ -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);

View file

@ -342,6 +342,16 @@ impl<T: IrcRead, U: IrcWrite> IrcServer<T, U> where Connection<T, U>: 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<T: IrcRead, U: IrcWrite> IrcServer<T, U> where Connection<T, U>: 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";