From cd708c3ef3f8c070f4b0c566575fb043c4eb1e8e Mon Sep 17 00:00:00 2001 From: Till Hoeppner Date: Sun, 4 Jan 2015 23:34:37 +0100 Subject: I should make smaller commits. --- Cargo.toml | 7 + examples/01.rs | 20 +- src/callback.rs | 14 +- src/ident.rs | 2 +- src/lib.rs | 2 +- src/message.rs | 811 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/server.rs | 152 +++++++---- 7 files changed, 943 insertions(+), 65 deletions(-) create mode 100644 src/message.rs diff --git a/Cargo.toml b/Cargo.toml index f3d1253..5dcedd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,10 @@ description = "A lightweight library for building IRC bots." repository = "https://github.com/hoeppnertill/irsc" keywords = ["irc", "internet", "protocol"] license = "WTFPL" + +[features] +ssl = ["openssl"] + +[dependencies.openssl] +version = "*" +optional = true diff --git a/examples/01.rs b/examples/01.rs index 0bf51f5..7cd7dc6 100644 --- a/examples/01.rs +++ b/examples/01.rs @@ -2,6 +2,10 @@ extern crate irsc; +use std::borrow::ToOwned; + +use std::sync::{Once, ONCE_INIT}; + use irsc::server::Server; use irsc::color::bold; use irsc::event; @@ -10,7 +14,9 @@ use irsc::event::{ Event, ParseResult, PrivMsg }; static NAME: &'static str = "rusticbot"; static DESC: &'static str = "A bot, written in Rust."; -fn callback(arg: (Server, Event)) { +static START: Once = ONCE_INIT; + +fn callback(arg: &(Server, Event)) { let (mut server, event) = arg; match event.command[] { event::PRIVMSG => { @@ -18,20 +24,24 @@ fn callback(arg: (Server, Event)) { let response = format!("You wrote: {}", bold(privmsg.content[])); server.msg(privmsg.from.nickname[], response[]).unwrap(); }, + event::MODE => { + START.doit(|| { + server.msg("Syna", "Hey, I'm poking you! *pokes you*").unwrap(); + //server.msg("Xasin", "Hey, I'm poking you! *pokes you*").unwrap(); + }) + }, _ => () } } fn main() { let mut s = Server::new(); - s.connect("irc.freenode.org".into_string(), 6667).unwrap(); + s.connect("irc.tulpa.info".to_owned(), 6667).unwrap(); s.nick(NAME).unwrap(); s.user(NAME, "*", "*", DESC).unwrap(); s.join("#botzoo").unwrap(); - s.msg("flan3002", "Hey, I'm your example bot!").unwrap(); - - s.events.lock().register(&(callback as fn((Server,Event)))); + s.events.lock().register(&(callback as fn(&(Server, Event)))); // Dedicate this thread to listening and event processing s.listen().unwrap(); diff --git a/src/callback.rs b/src/callback.rs index c62ebf8..b2027f6 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,15 +1,15 @@ -pub struct Callback { - items: Vec +pub struct Callback { + items: Vec } -impl Callback { +impl Callback { pub fn new() -> Callback { Callback { items: Vec::new() } } - pub fn register(&mut self, f: &fn(A)) { - self.items.push(*f) + pub fn register(&mut self, f: fn(&mut A)) { + self.items.push(f) } - pub fn fire(&self, v: &A) { - for _ in self.items.iter().map(|&c| c(v.clone())) {} + pub fn fire(&self, v: &mut A) { + for _ in self.items.iter().map(|&c| c(v)) {} } } diff --git a/src/ident.rs b/src/ident.rs index 6a01d12..099a129 100644 --- a/src/ident.rs +++ b/src/ident.rs @@ -3,7 +3,7 @@ use std::borrow::ToOwned; static PATTERN: Regex = regex!(":(.*)!(.*)@(.*)"); -#[deriving(Show, Clone)] +#[derive(Show, Clone)] pub struct Ident { pub nickname: String, pub user: String, diff --git a/src/lib.rs b/src/lib.rs index 6d0b42d..1c60750 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,4 +11,4 @@ pub mod server; pub mod color; pub mod ident; pub mod callback; -pub mod event; +pub mod message; diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 0000000..edc1874 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,811 @@ +#![allow(non_camel_case_types)] + +use std::str::FromStr; +use std::borrow::ToOwned; + +#[derive(Clone)] +pub struct Message { + pub prefix: Option, + pub command: Command, + pub content: Vec, + pub suffix: Option +} + +impl Message { + pub fn new(prefix: Option, command: Command, content: Vec, suffix: Option) -> Message { + Message { + prefix: prefix, + command: command, + content: content, + suffix: suffix + } + } + + pub fn parse(i: &str) -> Option { + let len = i.len(); + let mut s = i; + let prefix = if len >= 1 && s.char_at(0) == ':' { + s.find(' ').map(|i| { + let p = s.slice_chars(1, i).to_owned(); + s = s[i..]; + p + }) + } else { None }; + + let command = s.find(' ').map(|i| { + let p = s.slice_chars(0, i).to_owned(); + s = s[i..]; + p + }).and_then(|c| c.parse()); + + let mut content = Vec::with_capacity(15); + let mut suffix = None; + while s.len() > 0 { + if s.char_at(0) == ':' { + suffix = Some(s.slice_from(1).to_owned()); + break + } + s.find(' ').map(|i| { + content.push(s.slice_chars(0, i).to_owned()); + s = s[i..]; + }); + } + + command.map(move |c| Message::new(prefix, c, content, suffix)) + } + + pub fn format(&self) -> String { + let mut s = String::with_capacity(512); + if let Some(ref p) = self.prefix { + s.push(':'); + s.push_str(p[]); + s.push(' '); + } + + s.push_str(format!("{} ", self.command)[]); + + if let Some(ref p) = self.suffix { + s.push(':'); + s.push_str(p[]); + } + + s + } +} + +#[derive(Copy, Clone, Show, PartialEq, Eq, Hash)] +pub enum Command { + PASS, + NICK, + USER, + OPER, + MODE, + SERVICE, + QUIT, + SQUIT, + JOIN, + PART, + TOPIC, + NAMES, + LIST, + INVITE, + KICK, + PRIVMSG, + NOTICE, + MOTD, + LUSERS, + VERSION, + STATS, + LINKS, + TIME, + CONNECT, + TRACE, + ADMIN, + INFO, + SERVLIST, + SQUERY, + WHO, + WHOIS, + WHOWAS, + KILL, + PING, + PONG, + ERROR, + AWAY, + REHASH, + DIE, + RESTART, + SUMMON, + USERS, + WALLOPS, + USERHOST, + ISON +} + +impl FromStr for Command { + fn from_str(s: &str) -> Option { + use self::Command::*; + match s { + "PASS" => Some(PASS), + "NICK" => Some(NICK), + "USER" => Some(USER), + "OPER" => Some(OPER), + "MODE" => Some(MODE), + "SERVICE" => Some(SERVICE), + "QUIT" => Some(QUIT), + "SQUIT" => Some(SQUIT), + "JOIN" => Some(JOIN), + "PART" => Some(PART), + "TOPIC" => Some(TOPIC), + "NAMES" => Some(NAMES), + "LIST" => Some(LIST), + "INVITE" => Some(INVITE), + "KICK" => Some(KICK), + "PRIVMSG" => Some(PRIVMSG), + "NOTICE" => Some(NOTICE), + "MOTD" => Some(MOTD), + "LUSERS" => Some(LUSERS), + "VERSION" => Some(VERSION), + "STATS" => Some(STATS), + "LINKS" => Some(LINKS), + "TIME" => Some(TIME), + "CONNECT" => Some(CONNECT), + "TRACE" => Some(TRACE), + "ADMIN" => Some(ADMIN), + "INFO" => Some(INFO), + "SERVLIST" => Some(SERVLIST), + "SQUERY" => Some(SQUERY), + "WHO" => Some(WHO), + "WHOIS" => Some(WHOIS), + "WHOWAS" => Some(WHOWAS), + "KILL" => Some(KILL), + "PING" => Some(PING), + "PONG" => Some(PONG), + "ERROR" => Some(ERROR), + "AWAY" => Some(AWAY), + "REHASH" => Some(REHASH), + "DIE" => Some(DIE), + "RESTART" => Some(RESTART), + "SUMMON" => Some(SUMMON), + "USERS" => Some(USERS), + "WALLOPS" => Some(WALLOPS), + "USERHOST" => Some(USERHOST), + "ISON" => Some(ISON), + _ => None + } + } +} + +#[derive(Show, Copy, PartialEq, Eq)] +pub enum Response { + /// "Welcome to the Internet Relay Network !@" + RPL_WELCOME = 001, + /// "Your host is , running version " + RPL_YOURHOST = 002, + /// "This server was created " + RPL_CREATED = 003, + /// " " + RPL_MYINFO = 004, + /// "Try server , port " + /// Sent by the server to a user to suggest an alternative + /// server. This is often used when the connection is + /// refused because the server is already full. + RPL_BOUNCE = 005, + /// ":*1 *( " " )" + /// - Reply format used by USERHOST to list replies to + /// the query list. The reply string is composed as + /// follows: + /// + /// reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname + /// + /// The '*' indicates whether the client has registered + /// as an Operator. The '-' or '+' characters represent + /// whether the client has set an AWAY message or not + /// respectively. + RPL_USERHOST = 302, + /// ":*1 *( " " )" + /// - Reply format used by ISON to list replies to the query list. + RPL_ISON = 303, + /// " :" + RPL_AWAY = 301, + /// ":You are no longer marked as being away" + RPL_UNAWAY = 305, + /// ":You have been marked as being away" + /// - These replies are used with the AWAY command (if + /// allowed). RPL_AWAY is sent to any client sending a + /// PRIVMSG to a client which is away. RPL_AWAY is only + /// sent by the server to which the client is connected. + /// Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the + /// client removes and sets an AWAY message./ + RPL_NOWAWAY = 306, + /// " * :" + RPL_WHOISUSER = 311, + /// " :" + RPL_WHOISSERVER = 312, + /// " :is an IRC operator" + RPL_WHOISOPERATOR = 313, + /// " :seconds idle" + RPL_WHOISIDLE = 317, + /// " :End of WHOIS list" + RPL_ENDOFWHOIS = 318, + /// " :*( ( "@" / "+" ) " " )" + /// - Replies 311 - 313, 317 - 319 are all replies + /// generated in response to a WHOIS message. Given that + /// there are enough parameters present, the answering + /// server MUST either formulate a reply out of the above + /// numerics (if the query nick is found) or return an + /// error reply. The '*' in RPL_WHOISUSER is there as + /// the literal character and not as a wild card. For + /// each reply set, only RPL_WHOISCHANNELS may appear + /// more than once (for long lists of channel names). + /// The '@' and '+' characters next to the channel name + /// indicate whether a client is a channel operator or + /// has been granted permission to speak on a moderated + /// channel. The RPL_ENDOFWHOIS reply is used to mark + /// the end of processing a WHOIS message. + RPL_WHOISCHANNELS = 319, + /// " * :" + RPL_WHOWASUSER = 314, + /// " :End of WHOWAS" + /// - When replying to a WHOWAS message, a server MUST use + /// the replies RPL_WHOWASUSER, RPL_WHOISSERVER or + /// ERR_WASNOSUCHNICK for each nickname in the presented + /// list. At the end of all reply batches, there MUST + /// be RPL_ENDOFWHOWAS (even if there was only one reply + /// and it was an error). + RPL_ENDOFWHOWAS = 369, + /// Obsolete. Not used. + #[deprecated = "Obsolete. Not used."] + RPL_LISTSTART = 321, + /// " <# visible> :" + RPL_LIST = 322, + /// ":End of LIST" + /// - Replies RPL_LIST, RPL_LISTEND mark the actual replies + /// with data and end of the server's response to a LIST + /// command. If there are no channels available to return, + /// only the end reply MUST be sent. + RPL_LISTEND = 323, + /// " " + RPL_UNIQOPIS = 325, + /// " " + RPL_CHANNELMODEIS = 324, + /// " :No topic is set" + RPL_NOTOPIC = 331, + /// " :" + /// - When sending a TOPIC message to determine the + /// channel topic, one of two replies is sent. If + /// the topic is set, RPL_TOPIC is sent back else + /// RPL_NOTOPIC. + RPL_TOPIC = 332, + /// " " + /// - Returned by the server to indicate that the + /// attempted INVITE message was successful and is + /// being passed onto the end client. + RPL_INVITING = 341, + /// " :Summoning user to IRC" + /// - Returned by a server answering a SUMMON message to + /// indicate that it is summoning that user. + RPL_SUMMONING = 342, + /// " " + RPL_INVITELIST = 346, + /// " :End of channel invite list" + /// - When listing the 'invitations masks' for a given channel, + /// a server is required to send the list back using the + /// RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A + /// separate RPL_INVITELIST is sent for each active mask. + /// After the masks have been listed (or if none present) a + /// RPL_ENDOFINVITELIST MUST be sent. + RPL_ENDOFINVITELIST = 347, + /// " " + RPL_EXCEPTLIST = 348, + /// " :End of channel exception list" + /// - When listing the 'exception masks' for a given channel, + /// a server is required to send the list back using the + /// RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A + /// separate RPL_EXCEPTLIST is sent for each active mask. + /// After the masks have been listed (or if none present) + /// a RPL_ENDOFEXCEPTLIST MUST be sent./ + RPL_ENDOFEXCEPTLIST = 349, + /// ". :" + /// - Reply by the server showing its version details. + /// The is the version of the software being + /// used (including any patchlevel revisions) and the + /// is used to indicate if the server is + /// running in "debug mode". + /// + /// The "comments" field may contain any comments about + /// the version or further version details. + RPL_VERSION = 351, + /// " + /// ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] + /// : " + RPL_WHOREPLY = 352, + /// " :End of WHO list" + /// - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used + /// to answer a WHO message. The RPL_WHOREPLY is only + /// sent if there is an appropriate match to the WHO + /// query. If there is a list of parameters supplied + /// with a WHO message, a RPL_ENDOFWHO MUST be sent + /// after processing each list item with being + /// the item. + RPL_ENDOFWHO = 315, + /// "( "=" / "*" / "@" ) + /// :[ "@" / "+" ] *( " " [ "@" / "+" ] ) + /// - "@" is used for secret channels, "*" for private + /// channels, and "=" for others (public channels). + RPL_NAMREPLY = 353, + /// " :End of NAMES list" + /// - To reply to a NAMES message, a reply pair consisting + /// of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the + /// server back to the client. If there is no channel + /// found as in the query, then only RPL_ENDOFNAMES is + /// returned. The exception to this is when a NAMES + /// message is sent with no parameters and all visible + /// channels and contents are sent back in a series of + /// RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark + /// the end. + RPL_ENDOFNAMES = 366, + /// " : " + RPL_LINKS = 364, + /// " :End of LINKS list" + /// - In replying to the LINKS message, a server MUST send + /// replies back using the RPL_LINKS numeric and mark the + /// end of the list using an RPL_ENDOFLINKS reply. + RPL_ENDOFLINKS = 365, + /// " " + RPL_BANLIST = 367, + /// " :End of channel ban list" + /// - When listing the active 'bans' for a given channel, + /// a server is required to send the list back using the + /// RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate + /// RPL_BANLIST is sent for each active banmask. After the + /// banmasks have been listed (or if none present) a + /// RPL_ENDOFBANLIST MUST be sent. + RPL_ENDOFBANLIST = 368, + /// ":" + RPL_INFO = 371, + /// ":End of INFO list" + /// - A server responding to an INFO message is required to + /// send all its 'info' in a series of RPL_INFO messages + /// with a RPL_ENDOFINFO reply to indicate the end of the + /// replies. + RPL_ENDOFINFO = 374, + /// ":- Message of the day - " + RPL_MOTDSTART = 375, + /// ":- " + RPL_MOTD = 372, + /// ":End of MOTD command" + /// - When responding to the MOTD message and the MOTD file + /// is found, the file is displayed line by line, with + /// each line no longer than 80 characters, using + /// RPL_MOTD format replies. These MUST be surrounded + /// by a RPL_MOTDSTART (before the RPL_MOTDs) and an + /// RPL_ENDOFMOTD (after). + RPL_ENDOFMOTD = 376, + /// ":You are now an IRC operator" + /// - RPL_YOUREOPER is sent back to a client which has + /// just successfully issued an OPER message and gained + /// operator status. + RPL_YOUREOPER = 381, + /// " :Rehashing" + /// - If the REHASH option is used and an operator sends + /// a REHASH message, an RPL_REHASHING is sent back to + /// the operator. + RPL_REHASHING = 382, + /// "You are service " + /// - Sent by the server to a service upon successful + /// registration. + RPL_YOURESERVICE = 383, + /// " :" + /// - When replying to the TIME message, a server MUST send + /// the reply using the RPL_TIME format above. The string + /// showing the time need only contain the correct day and + /// time there. There is no further requirement for the + /// time string. + RPL_TIME = 391, + /// ":UserID Terminal Host" + RPL_USERSSTART = 392, + /// ": " + RPL_USERS = 393, + /// ":End of users" + RPL_ENDOFUSERS = 394, + /// ":Nobody logged in" + /// - If the USERS message is handled by a server, the + /// replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and + /// RPL_NOUSERS are used. RPL_USERSSTART MUST be sent + /// first, following by either a sequence of RPL_USERS + /// or a single RPL_NOUSER. Following this is + /// RPL_ENDOFUSERS. + RPL_NOUSERS = 395, + /// "Link + /// V + /// + /// " + RPL_TRACELINK = 200, + /// "Try. " + RPL_TRACECONNECTING = 201, + /// "H.S. " + RPL_TRACEHANDSHAKE = 202, + /// "???? []" + RPL_TRACEUNKNOWN = 203, + /// "Oper " + RPL_TRACEOPERATOR = 204, + /// "User " + RPL_TRACEUSER = 205, + /// "Serv S C + /// @ V" + RPL_TRACESERVER = 206, + /// "Service " + RPL_TRACESERVICE = 207, + /// " 0 " + RPL_TRACENEWTYPE = 208, + /// "Class " + RPL_TRACECLASS = 209, + /// Unused. + #[deprecated = "Unused."] + RPL_TRACERECONNECT = 210, + /// "File " + RPL_TRACELOG = 261, + /// " :End of TRACE" + /// - The RPL_TRACE* are all returned by the server in + /// response to the TRACE message. How many are + /// returned is dependent on the TRACE message and + /// whether it was sent by an operator or not. There + /// is no predefined order for which occurs first. + /// Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and + /// RPL_TRACEHANDSHAKE are all used for connections + /// which have not been fully established and are either + /// unknown, still attempting to connect or in the + /// process of completing the 'server handshake'. + /// RPL_TRACELINK is sent by any server which handles + /// a TRACE message and has to pass it on to another + /// server. The list of RPL_TRACELINKs sent in + /// response to a TRACE command traversing the IRC + /// network should reflect the actual connectivity of + /// the servers themselves along that path. + /// RPL_TRACENEWTYPE is to be used for any connection + /// which does not fit in the other categories but is + /// being displayed anyway. + /// RPL_TRACEEND is sent to indicate the end of the list./ + RPL_TRACEEND = 262, + /// " + /// + ///