diff --git a/Cargo.toml b/Cargo.toml index c538663..83f1052 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,6 @@ name = "irc" version = "0.0.1" authors = ["Aaron Weiss "] + +[[example]] +name = "simple" diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..566a78c --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,27 @@ +#![feature(slicing_syntax)] +extern crate irc; + +use std::collections::HashMap; +use irc::Server; +use irc::bot::IrcServer; +use irc::data::{Config, Message}; + +fn main() { + let config = Config { + owners: vec!("awe".into_string()), + nickname: "pickles".into_string(), + username: "pickles".into_string(), + realname: "pickles".into_string(), + password: "".into_string(), + server: "irc.fyrechat.net".into_string(), + port: 6667, + channels: vec!("#vana".into_string()), + options: HashMap::new(), + }; + let mut server = IrcServer::new_with_config(config).unwrap(); + server.send(Message::new(None, "NICK", vec!["pickles"], None)).unwrap(); + server.send(Message::new(None, "USER", vec!["pickles", "0", "*", "pickles"], None)).unwrap(); + for message in server { + println!("RCV: {}", message); + } +} diff --git a/src/bot.rs b/src/bot.rs index 4672cb8..4e1e015 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,113 +1,52 @@ use std::cell::RefCell; use std::collections::HashMap; use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream}; -use {Bot, process}; +use {Server, process}; use conn::Connection; use data::{Config, IrcReader, IrcWriter, Message, User}; -pub struct IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader { +pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { pub conn: Connection, pub config: Config, - process: RefCell<|&IrcBot, &str, &str, &[&str]|:'a -> IoResult<()>>, chanlists: RefCell>>, } -impl<'a> IrcBot<'a, BufferedWriter, BufferedReader> { - pub fn new(process: |&IrcBot, BufferedReader>, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult, BufferedReader>> { +impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { + pub fn new() -> IoResult, BufferedReader>> { let config = try!(Config::load_utf8("config.json")); let conn = try!(Connection::connect(config.server[], config.port)); - Ok(IrcBot { + Ok(IrcServer { conn: conn, config: config, - process: RefCell::new(process), chanlists: RefCell::new(HashMap::new()), }) } - pub fn new_with_config(config: Config, process: |&IrcBot, BufferedReader>, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult, BufferedReader>> { + pub fn new_with_config(config: Config) -> IoResult, BufferedReader>> { let conn = try!(Connection::connect(config.server[], config.port)); - Ok(IrcBot { + Ok(IrcServer { conn: conn, config: config, - process: RefCell::new(process), chanlists: RefCell::new(HashMap::new()), }) } } -impl<'a, T, U> Bot for IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader { - fn send_sanick(&self, old_nick: &str, new_nick: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "SANICK", [old_nick, new_nick], false)) +impl<'a, T, U> Iterator for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { + fn next(&mut self) -> Option { + let line_res = self.conn.reader().read_line(); + if let Err(e) = line_res { println!("{}", e); return None; } + let line = line_res.unwrap(); + let processed = process(line[]); + if let Err(e) = processed { println!("{}", e); return None; } + let (source, command, args) = processed.unwrap(); + Some(Message::new(Some(source), command, args, None)) } +} - fn send_nick(&self, nick: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "NICK", [nick], true)) - } - - fn send_user(&self, username: &str, real_name: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "USER", [username, "0", "*", real_name], true)) - } - - fn send_join(&self, chan: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "JOIN", [chan], true)) - } - - fn send_samode(&self, target: &str, mode: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "SAMODE", [target, mode], false)) - } - - fn send_mode(&self, target: &str, mode: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "MODE", [target, mode], false)) - } - - fn send_oper(&self, name: &str, password: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "OPER", [name, password], false)) - } - - fn send_topic(&self, chan: &str, topic: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "TOPIC", [chan, topic], true)) - } - - fn send_invite(&self, person: &str, chan: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "INVITE", [person, chan], true)) - } - - fn send_kick(&self, chan: &str, user: &str, msg: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "KICK", [chan, user, msg], true)) - } - - fn send_kill(&self, nick: &str, msg: &str) -> IoResult<()> { - self.conn.send(Message::new(None, "KILL", [nick, msg], true)) - } - - fn send_privmsg(&self, chan: &str, msg: &str) -> IoResult<()> { - for line in msg.split_str("\r\n") { - try!(self.conn.send(Message::new(None, "PRIVMSG", [chan, line], true))); - } - Ok(()) - } - - fn identify(&self) -> IoResult<()> { - try!(self.send_nick(self.config.nickname[])); - self.send_user(self.config.username[], self.config.realname[]) - } - - fn output(&mut self) -> IoResult<()> { - let mut reader = self.conn.reader(); - for line in reader.lines() { - match line { - Ok(ln) => { - let (source, command, args) = try!(process(ln[])); - try!(self.handle_command(source, command, args[])); - println!("{}", ln) - }, - Err(e) => { - println!("{}", e); - return Err(e) - }, - } - } - Ok(()) +impl<'a, T, U> Server<'a> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { + fn send(&self, message: Message) -> IoResult<()> { + self.conn.send(message) } fn config(&self) -> &Config { @@ -119,267 +58,12 @@ impl<'a, T, U> Bot for IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader { } } -impl<'a, T, U> IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader { - pub fn from_connection(conn: Connection, process: |&IrcBot, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult> { - Ok(IrcBot { +impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { + pub fn from_connection(conn: Connection) -> IoResult> { + Ok(IrcServer { conn: conn, config: try!(Config::load_utf8("config.json")), - process: RefCell::new(process), chanlists: RefCell::new(HashMap::new()), }) } - - fn handle_command(&self, source: &str, command: &str, args: &[&str]) -> IoResult<()> { - match (command, args) { - ("PING", [msg]) => { - try!(self.conn.send(Message::new(None, "PONG", [msg], true))); - }, - ("376", _) => { // End of MOTD - for chan in self.config.channels.iter() { - try!(self.send_join(chan[])); - } - }, - ("422", _) => { // Missing MOTD - for chan in self.config.channels.iter() { - try!(self.send_join(chan[])); - } - }, - ("353", [_, _, chan, users]) => { // /NAMES - for user in users.split_str(" ") { - if !match self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) { - Some(vec) => { vec.push(User::new(user)); true }, - None => false, - } { - self.chanlists.borrow_mut().insert(chan.into_string(), vec!(User::new(user))); - } - } - }, - ("JOIN", [chan]) => { - if let Some(vec) = self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) { - if let Some(i) = source.find('!') { - vec.push(User::new(source[..i])); - } - } - }, - ("PART", [chan, _]) => { - if let Some(vec) = self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) { - if let Some(i) = source.find('!') { - if let Some(n) = vec.as_slice().position_elem(&User::new(source[..i])) { - vec.swap_remove(n); - } - } - } - }, - _ => (), - }; - (*self.process.borrow_mut().deref_mut())(self, source, command, args) - } -} - -#[cfg(test)] -mod test { - use Bot; - use super::IrcBot; - use std::io::{BufReader, MemWriter}; - use std::io::util::{NullReader, NullWriter}; - use conn::Connection; - use data::{IrcReader, User}; - - fn data(conn: Connection) -> String where U: IrcReader { - String::from_utf8(conn.writer().deref_mut().get_ref().to_vec()).unwrap() - } - - #[test] - fn from_connection() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - assert!(IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).is_ok()); - } - - #[test] - fn send_sanick() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_sanick("test", "test2").unwrap(); - assert_eq!(data(b.conn), format!("SANICK test test2\r\n")); - } - - #[test] - fn send_nick() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_nick("test").unwrap(); - assert_eq!(data(b.conn), format!("NICK :test\r\n")); - } - - #[test] - fn send_user() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_user("test", "Test").unwrap(); - assert_eq!(data(b.conn), format!("USER test 0 * :Test\r\n")); - } - - #[test] - fn send_join() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_join("#test").unwrap(); - assert_eq!(data(b.conn), format!("JOIN :#test\r\n")); - } - - #[test] - fn send_samode() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_samode("#test", "+i").unwrap(); - assert_eq!(data(b.conn), format!("SAMODE #test +i\r\n")); - } - - #[test] - fn send_mode() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_mode("#test", "+i").unwrap(); - assert_eq!(data(b.conn), format!("MODE #test +i\r\n")); - } - - #[test] - fn send_oper() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_oper("test", "test").unwrap(); - assert_eq!(data(b.conn), format!("OPER test test\r\n")); - } - - #[test] - fn send_topic() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_topic("#test", "This is a test topic.").unwrap(); - assert_eq!(data(b.conn), format!("TOPIC #test :This is a test topic.\r\n")); - } - - #[test] - fn send_invite() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_invite("test2", "#test").unwrap(); - assert_eq!(data(b.conn), format!("INVITE test2 :#test\r\n")); - } - - #[test] - fn send_kick() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_kick("#test", "test2", "Goodbye.").unwrap(); - assert_eq!(data(b.conn), format!("KICK #test test2 :Goodbye.\r\n")); - } - - #[test] - fn send_kill() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_kill("test", "Goodbye.").unwrap(); - assert_eq!(data(b.conn), format!("KILL test :Goodbye.\r\n")); - } - - #[test] - fn send_privmsg() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_privmsg("#test", "This is a test message.").unwrap(); - assert_eq!(data(b.conn), format!("PRIVMSG #test :This is a test message.\r\n")); - } - - #[test] - fn send_privmsg_multiline() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.send_privmsg("#test", "This is a test message.\r\nIt has two lines.").unwrap(); - let mut exp = format!("PRIVMSG #test :This is a test message.\r\n"); - exp.push_str("PRIVMSG #test :It has two lines.\r\n"); - assert_eq!(data(b.conn), format!("{}", exp)); - - } - - #[test] - fn identify() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.identify().unwrap(); - assert_eq!(data(b.conn), format!("NICK :test\r\nUSER test 0 * :test\r\n")); - } - - #[test] - fn ping_response() { - let r = BufReader::new(":embyr.tx.us.fyrechat.net PING :01R6\r\n".as_bytes()); - let c = Connection::new(MemWriter::new(), r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - assert_eq!(data(b.conn), format!("PONG :01R6\r\n")); - } - - #[test] - fn end_of_motd_response() { - let r = BufReader::new(":embyr.tx.us.fyrechat.net 376 test :End of /MOTD command.\r\n".as_bytes()); - let c = Connection::new(MemWriter::new(), r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - assert_eq!(data(b.conn), format!("JOIN :#test\r\nJOIN :#test2\r\n")); - } - - #[test] - fn missing_motd_response() { - let r = BufReader::new(":flare.to.ca.fyrechat.net 422 pickles :MOTD File is missing\r\n".as_bytes()); - let c = Connection::new(MemWriter::new(), r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - assert_eq!(data(b.conn), format!("JOIN :#test\r\nJOIN :#test2\r\n")); - } - - #[test] - fn generate_user_list() { - let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2 test3\r\n".as_bytes()); - let c = Connection::new(NullWriter, r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) { - Some(v) => Ok(v.clone()), - None => Err("Could not find vec for channel."), - }; - assert!(vec_res.is_ok()); - let vec = vec_res.unwrap(); - assert_eq!(vec, vec![User::new("test"), User::new("test2"), User::new("test3")]); - } - - #[test] - fn add_to_user_list() { - let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2\r\n:test3!test@test JOIN :#test\r\n".as_bytes()); - let c = Connection::new(NullWriter, r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) { - Some(v) => Ok(v.clone()), - None => Err("Could not find vec for channel."), - }; - assert!(vec_res.is_ok()); - let vec = vec_res.unwrap(); - assert_eq!(vec, vec![User::new("test"), User::new("test2"), User::new("test3")]); - } - - #[test] - fn remove_from_user_list() { - let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2 test3\r\n:test3!test@test PART #test :\r\n".as_bytes()); - let c = Connection::new(NullWriter, r).unwrap(); - let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap(); - b.output().unwrap(); - let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) { - Some(v) => Ok(v.clone()), - None => Err("Could not find vec for channel."), - }; - assert!(vec_res.is_ok()); - let vec = vec_res.unwrap(); - // n.b. ordering is not guaranteed, this only ought to hold because we're removing the last user - assert_eq!(vec, vec![User::new("test"), User::new("test2")]); - } } diff --git a/src/conn.rs b/src/conn.rs index 04242ba..011b4d5 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -35,8 +35,8 @@ impl Connection where T: IrcWriter, U: IrcReader { send.push_str(msg.args.init().connect(" ")[]); } send.push_str(" "); - if msg.colon_flag { send.push_str(":") } - send.push_str(*msg.args.last().unwrap()); + if msg.colon_flag.is_some() { send.push_str(":") } + send.push_str(msg.args.last().unwrap()[]); send.push_str("\r\n"); self.send_internal(send[]) } @@ -49,35 +49,3 @@ impl Connection where T: IrcWriter, U: IrcReader { self.reader.borrow_mut() } } - -#[cfg(test)] -mod test { - use super::Connection; - use std::io::MemWriter; - use std::io::util::NullReader; - use data::{IrcReader, Message}; - - fn data(conn: Connection) -> String where U: IrcReader { - String::from_utf8(conn.writer().deref_mut().get_ref().to_vec()).unwrap() - } - - #[test] - fn new_connection() { - assert!(Connection::new(MemWriter::new(), NullReader).is_ok()); - } - - #[test] - fn send_internal() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - c.send_internal("string of text").unwrap(); - assert_eq!(data(c), format!("string of text")); - } - - #[test] - fn send() { - let c = Connection::new(MemWriter::new(), NullReader).unwrap(); - let args = ["flare.to.ca.fyrechat.net"]; - c.send(Message::new(None, "PING", args, true)).unwrap(); - assert_eq!(data(c), format!("PING :flare.to.ca.fyrechat.net\r\n")); - } -} diff --git a/src/data.rs b/src/data.rs index da8f792..872f1d5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -54,19 +54,19 @@ impl AccessLevel { } #[deriving(Show, PartialEq)] -pub struct Message<'a> { - pub source: Option<&'a str>, - pub command: &'a str, - pub args: &'a [&'a str], - pub colon_flag: bool, +pub struct Message { + pub source: Option, + pub command: String, + pub args: Vec, + pub colon_flag: Option, } -impl<'a> Message<'a> { - pub fn new(source: Option<&'a str>, command: &'a str, args: &'a [&'a str], colon_flag: bool) -> Message<'a> { +impl<'a> Message { + pub fn new(source: Option<&'a str>, command: &'a str, args: Vec<&'a str>, colon_flag: Option) -> Message { Message { - source: source, - command: command, - args: args, + source: source.map(|s: &str| s.into_string()), + command: command.into_string(), + args: args.into_iter().map(|s: &str| s.into_string()).collect(), colon_flag: colon_flag, } } @@ -104,32 +104,3 @@ impl Config { self.owners[].contains(&String::from_str(nickname)) } } - -#[cfg(test)] -mod test { - use super::{Config, Message}; - - #[test] - fn new_message() { - let args = ["flare.to.ca.fyrechat.net"]; - let m = Message::new(None, "PING", args, true); - assert_eq!(m, Message { - source: None, - command: "PING", - args: args, - colon_flag: true, - }); - } - - #[test] - fn load_config() { - assert!(Config::load_utf8("config.json").is_ok()); - } - - #[test] - fn is_owner() { - let cfg = Config::load_utf8("config.json").unwrap(); - assert!(cfg.is_owner("test")); - assert!(!cfg.is_owner("test2")); - } -} diff --git a/src/lib.rs b/src/lib.rs index 58a8189..e2a53ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,28 +6,14 @@ extern crate regex; extern crate serialize; use std::io::{InvalidInput, IoError, IoResult}; -use data::Config; pub mod bot; pub mod conn; pub mod data; -pub trait Bot { - fn send_sanick(&self, old_nick: &str, new_nick: &str) -> IoResult<()>; - fn send_nick(&self, nick: &str) -> IoResult<()>; - fn send_user(&self, username: &str, real_name: &str) -> IoResult<()>; - fn send_join(&self, chan: &str) -> IoResult<()>; - fn send_samode(&self, target: &str, mode: &str) -> IoResult<()>; - fn send_mode(&self, target: &str, mode: &str) -> IoResult<()>; - fn send_oper(&self, name: &str, password: &str) -> IoResult<()>; - fn send_topic(&self, chan: &str, topic: &str) -> IoResult<()>; - fn send_invite(&self, person: &str, chan: &str) -> IoResult<()>; - fn send_privmsg(&self, chan: &str, msg: &str) -> IoResult<()>; - fn send_kick(&self, chan: &str, user: &str, msg: &str) -> IoResult<()>; - fn send_kill(&self, nick: &str, msg: &str) -> IoResult<()>; - fn identify(&self) -> IoResult<()>; - fn output(&mut self) -> IoResult<()>; - fn config(&self) -> &Config; +pub trait Server<'a>: Iterator { + fn send(&self, message: data::Message) -> IoResult<()>; + fn config(&self) -> &data::Config; fn get_users(&self, chan: &str) -> Option>; }