diff --git a/Cargo.toml b/Cargo.toml index 0a83f82..d24f1fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "irc" -version = "0.5.0" +version = "0.5.1" description = "A simple, thread-safe IRC client library." authors = ["Aaron Weiss "] license = "Unlicense" diff --git a/src/data/config.rs b/src/data/config.rs index ba304df..9377c97 100644 --- a/src/data/config.rs +++ b/src/data/config.rs @@ -13,6 +13,8 @@ pub struct Config { pub owners: Option>, /// The bot's nickname. pub nickname: Option, + /// Alternative nicknames for the bots, if the default is taken. + pub alt_nicks: Option>, /// The bot's username. pub username: Option, /// The bot's real name. @@ -61,11 +63,20 @@ impl Config { } /// Gets the nickname specified in the configuration. + /// This will panic if not specified. #[experimental] pub fn nickname(&self) -> &str { self.nickname.as_ref().map(|s| s[]).unwrap() } + /// Gets the alternate nicknames specified in the configuration. + /// This defaults to an empty vector when not specified. + #[experimental] + pub fn get_alternate_nicknames(&self) -> Vec<&str> { + self.alt_nicks.as_ref().map(|v| v.iter().map(|s| s[]).collect()).unwrap_or(vec![]) + } + + /// Gets the username specified in the configuration. /// This defaults to the user's nickname when not specified. #[experimental] @@ -146,6 +157,7 @@ mod test { let cfg = Config { owners: Some(vec![format!("test")]), nickname: Some(format!("test")), + alt_nicks: None, username: Some(format!("test")), realname: Some(format!("test")), password: Some(String::new()), @@ -164,6 +176,7 @@ mod test { let cfg = Config { owners: Some(vec![format!("test")]), nickname: Some(format!("test")), + alt_nicks: None, username: Some(format!("test")), realname: Some(format!("test")), password: Some(String::new()), diff --git a/src/server/mod.rs b/src/server/mod.rs index 2ce606b..ee47323 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,10 +2,10 @@ #![experimental] use std::collections::HashMap; use std::io::{BufferedReader, BufferedWriter, IoResult}; -use std::sync::Mutex; +use std::sync::{Mutex, RWLock}; use conn::{Connection, NetStream}; use data::{Command, Config, Message, Response, User}; -use data::Command::{JOIN, PONG}; +use data::Command::{JOIN, NICK, PONG}; use data::kinds::{IrcReader, IrcWriter}; pub mod utils; @@ -32,6 +32,8 @@ pub struct IrcServer { config: Config, /// A thread-safe map of channels to the list of users in them. chanlists: Mutex>>, + /// A thread-safe index to track the current alternative nickname being used. + alt_nick_index: RWLock, } /// An IrcServer over a buffered NetStream. @@ -61,7 +63,8 @@ impl IrcServer, BufferedWriter> { } else { Connection::connect(config.server(), config.port()) }); - Ok(IrcServer { config: config, conn: conn, chanlists: Mutex::new(HashMap::new()) }) + Ok(IrcServer { config: config, conn: conn, chanlists: Mutex::new(HashMap::new()), + alt_nick_index: RWLock::new(0u) }) } /// Creates a new IRC server connection from the specified configuration with the specified @@ -74,7 +77,8 @@ impl IrcServer, BufferedWriter> { } else { Connection::connect_with_timeout(config.server(), config.port(), timeout_ms) }); - Ok(IrcServer { config: config, conn: conn, chanlists: Mutex::new(HashMap::new()) }) + Ok(IrcServer { config: config, conn: conn, chanlists: Mutex::new(HashMap::new()), + alt_nick_index: RWLock::new(0u) }) } } @@ -107,7 +111,8 @@ impl IrcServer { /// Creates an IRC server from the specified configuration, and any arbitrary Connection. #[experimental] pub fn from_connection(config: Config, conn: Connection) -> IrcServer { - IrcServer { conn: conn, config: config, chanlists: Mutex::new(HashMap::new()) } + IrcServer { conn: conn, config: config, chanlists: Mutex::new(HashMap::new()), + alt_nick_index: RWLock::new(0u) } } /// Gets a reference to the IRC server's connection. @@ -136,6 +141,16 @@ impl IrcServer { for chan in self.config.channels().into_iter() { self.send(JOIN(chan[], None)).unwrap(); } + } else if resp == Response::ERR_NICKNAMEINUSE || + resp == Response::ERR_ERRONEOUSNICKNAME { + let alt_nicks = self.config.get_alternate_nicknames(); + let mut index = self.alt_nick_index.write(); + if *index.deref() >= alt_nicks.len() { + panic!("All specified nicknames were in use.") + } else { + self.send(NICK(alt_nicks[*index.deref()])).unwrap(); + *index.deref_mut() += 1; + } } return } @@ -225,6 +240,7 @@ mod test { Config { owners: Some(vec![format!("test")]), nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), server: Some(format!("irc.test.net")), channels: Some(vec![format!("#test"), format!("#test2")]), .. Default::default() @@ -263,14 +279,38 @@ mod test { "PONG :irc.test.net\r\nJOIN #test\r\nJOIN #test2\r\n"); } + #[test] + fn nickname_in_use() { + let value = ":irc.pdgn.co 433 * test :Nickname is already in use."; + let server = IrcServer::from_connection(test_config(), Connection::new( + MemReader::new(value.as_bytes().to_vec()), MemWriter::new() + )); + for message in server.iter() { + println!("{}", message); + } + assert_eq!(get_server_value(server)[], "NICK :test2\r\n"); + } + + #[test] + #[should_fail] + fn ran_out_of_nicknames() { + let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ + :irc.pdgn.co 433 * test2 :Nickname is already in use.\r\n"; + let server = IrcServer::from_connection(test_config(), Connection::new( + MemReader::new(value.as_bytes().to_vec()), MemWriter::new() + )); + for message in server.iter() { + println!("{}", message); + } + } + #[test] fn send() { let server = IrcServer::from_connection(test_config(), Connection::new( NullReader, MemWriter::new() )); assert!(server.send(PRIVMSG("#test", "Hi there!")).is_ok()); - assert_eq!(get_server_value(server)[], - "PRIVMSG #test :Hi there!\r\n"); + assert_eq!(get_server_value(server)[], "PRIVMSG #test :Hi there!\r\n"); } #[test]