diff options
-rw-r--r-- | README.md | 129 | ||||
-rw-r--r-- | examples/02.rs | 2 |
2 files changed, 87 insertions, 44 deletions
@@ -3,84 +3,127 @@ [![travis-ci.org](https://travis-ci.org/tilpner/irsc.svg?branch=master)](https://travis-ci.org/tilpner/irsc) [![crates.io](http://meritbadge.herokuapp.com/irsc)](https://crates.io/crates/irsc) -*This repository contains code that has not been properly tested yet, continue -at the risk of doing stupid things while discovering parts of this library -don't work.* - ## Overview -Want to build an IRC bot with low resource consumption? You might want to have a look at this library (maybe later, though). - -This library is supposed to be a thin layer over the IRC protocol, doing all the network IO and event parsing for you. Right now, it only works, nothing more. +This library is supposed to be a thin layer over the IRC protocol, doing all the network IO and event parsing for you. ## Features -- Semi-complete implementation of [RFC2812](http://tools.ietf.org/html/rfc2812) +- Somewhat complete implementation of [RFC2812](http://tools.ietf.org/html/rfc2812) - Some CTCP support - SSL for connections +- Callback and Event-Stream API +- Colors/bolding/etc. ### Planned - Higher-level wrapper, directly aimed at writing bots - Lots of tests -- Some documentation (yeah, sure) +- More documentation (yeah, sure) ## Example -Compiles and tested with `rustc 1.2.0-nightly (8937ec100 2015-06-15)` and `63838165c31397fec199bf99c96497a1169c4d52` of this library. +Compiles and tested with `rustc 1.3.0-nightly (912ab64a0 2015-06-25)` and `8537b61f38fd3976d47b153f3548f77896e42eb6` of this library. Run with - cargo run --example 01 --features ssl + cargo run --example 02 --features ssl and join [#botzoo on irc.mozilla.org](http://irc.lc/mozilla/botzoo). ```rust extern crate irsc; -extern crate env_logger; extern crate openssl; +extern crate env_logger; -use irsc::color::bold; use irsc::*; use irsc::Command::*; use irsc::Reply::*; use openssl::ssl::{ Ssl, SslContext, SslMethod }; -static NAME: &'static str = "rusticbot"; -static DESC: &'static str = "A bot, written in Rust."; - -fn callback(server: &mut Client, msg: &Message, event: Option<Event>) { - match event { - Some(Event::Command(PRIVMSG(to, content))) => { - let from = msg.ident().unwrap(); - let response = match msg.msg_type { - MsgType::Irc => format!("{} wrote: {}", from.nickname, bold(&content)), - MsgType::Ctcp => format!("{} emoted: {}", from.nickname, - bold(&content["ACTION ".len()..])) - }; - - // only send to channels, to prevent recursion when we are pm'ed - // technically, there are other prefixes than '#', but ignoring them is fine - if to.starts_with("#") { - server.msg(&to, &response); - } - }, - Some(Event::Reply(RPL_WELCOME(_))) => { - server.join("#botzoo", None); - }, - _ => () - } -} +// Here should be the constants of this bot, like the nickname or description, +// to avoid repetition. However, for better readability, the constants have been +// inlined below. fn main() { - env_logger::init().unwrap(); - let mut s = Client::new(); + // If the environment variable RUST_LOG is set to "info", + // irsc will log incoming and outgoing data in raw form. + // Documentation: http://rust-lang.github.io/log/env_logger/#enabling-logging + env_logger::init().ok().expect("Failed to initialise env_logger"); + + let mut s = OwnedClient::new(); + // Try to use Tlsv1 to connect. This might fail, depending on your version of + // OpenSSL. This example does not try with other methods on failure. let ssl = Ssl::new(&SslContext::new(SslMethod::Tlsv1).unwrap()).unwrap(); + + // Connect using the newly constructed Ssl configuration. + // If Ssl is not desired, use .connect(...) without the ssl argument. s.connect_ssl("irc.mozilla.org", 6697, ssl); - s.register(NAME, NAME, DESC); - // Dedicate this thread to listening and event processing - s.listen(Some(callback)); + // Send the USER and NICK message in one go: + // register(nick, user, description, [password]) + s.register("irsc02", "irsc", "Example bot 02", None); + + // The client must be accessible from anywhere when using the Event API. + // .into_shared() will convert the previously owned client into a wrapper. + let mut shared = s.into_shared(); + + // `shared.commands()` will return a carboxyl stream of tuples of the form + // (SharedClient, Message, Command), representing the incoming Commands. + // Event streams are not lazy, but they must still be alive when the events happen, + // in order to process them. To keep them alive (prevent dropping), we assing them + // to local variables. Make sure to not ignore (variable name `_`) them, as that + // drops them as well. The leading underscore will avoid unused-variable warnings. + let _a = shared.commands() + .map(|(mut cl, msg, c)| { + // PRIVMSGs are the most common way to talk, they are used in queries but also + // to talk in channels (despite the "PRIV"). This if-let makes sure we only handle + // these PRIVMSG events for now, and allows us to access PRIVMSG-specific information, + // like the addressee of the message and its content. + if let PRIVMSG(to, content) = c { + // The ident is unique to the origin of this message, and can be used + // to retrieve the nickname of the sender. This example assumes all + // hostmasks are well formed and always present. + let from = msg.ident().unwrap(); + // This example will echo the input if the bot was mentioned. + // Example: + // tilpner told me: irsc - Foo! + // if "tilpner" wrote "irsc - Foo!" before. + let response = format!("{} told me: {}", from.nickname, color::bold(&content)); + + // Only send to global channels, to prevent recursion when we are pm'ed. + // Technically, there are other prefixes than '#', but ignoring them is fine here. + // Also, we only reply if we were mentioned at the start of the message. + if to.starts_with("#") && content.starts_with("irsc") { + // `to` is not the nick who mentioned us, but the channel we were mentioned in, + // This will send our `response` to that channel. + cl.msg(&to, &response); + } + } + }); + + // `shared.replies()` will return a carboxyl stream of tuples of the form + // (SharedClient, Message, Reply), representing the incoming Replies. + // Again, make sure to keep the mapped stream alive. + let _b = shared.replies() + .map(|(mut cl, _msg, r)| { + // Logging into the IRC server might take some seconds. Some libraries solve + // this by having a fixed timeout after they logged in, before they continue + // to e.g. join channels or identify with the services. + // There is a more precise way, namely waiting for the RPL_WELCOME event (001), + // that is sent by the IRCd after we've connected successfully. + if let RPL_WELCOME(_) = r { + // After we've connected successfully, we join a channel + // without providing a password. + cl.join("#botzoo", None); + } + }); + + // Dedicate this thread to listening and event processing. + // This method will only return after the connection has been closed or an + // error was encountered, which is why it should either listen in a new thread, + // or all necessary setup must be done prior to calling this method. + shared.listen_with_events(); } ``` diff --git a/examples/02.rs b/examples/02.rs index 568d3e7..84c8462 100644 --- a/examples/02.rs +++ b/examples/02.rs @@ -82,7 +82,7 @@ fn main() { if let RPL_WELCOME(_) = r { // After we've connected successfully, we join a channel // without providing a password. - cl.join("#meep!", None); + cl.join("#botzoo", None); } }); |