#![feature(custom_derive,plugin)] #![plugin(serde_macros)] extern crate hyper; extern crate irc; #[macro_use] extern crate lazy_static; extern crate rand; extern crate serde; extern crate serde_json; use std::sync::Arc; use std::collections::HashMap; use std::io::Read; use std::thread; use hyper::Client; use hyper::header::Connection; use irc::client::prelude::*; use rand::{thread_rng, sample}; lazy_static! { static ref EMOTE_HAPPY: Vec<&'static str> = vec![":D", ":)", "^_^"]; static ref EMOTES: HashMap<&'static str, &'static [&'static str]> = { let mut m = HashMap::new(); m.insert("happy", &(*EMOTE_HAPPY)[..]); m.insert("smile", &(*EMOTE_HAPPY)[..]); m.insert(":)", &(*EMOTE_HAPPY)[..]); m.insert(":D", &(*EMOTE_HAPPY)[..]); m }; } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct WikiPage { ns: u32, pageid: u32, title: String, extract: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct WikiRedirect { from: String, to: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct WikiQueryResponse { batchcomplete: String, query: WikiQuery, } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct WikiQuery { redirects: Option>, normalized: Option>, pages: HashMap, } const CHANNEL: &'static str = "#opensourcecornell"; fn user_agent() -> hyper::header::UserAgent { hyper::header::UserAgent("lidavidm_irc_bot/0.1 (https://git.lidavidm.me; li.davidm96@gmail.com) hyper/0.7.2".to_owned()) } fn query_page(article: &str) -> serde_json::Result{ let client = Client::new(); let mut res = client .get(&format!("https://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exintro=&explaintext=&titles={}&redirects=", article.replace(" ", "%20"))) .header(user_agent()) .header(Connection::close()) .send().unwrap(); let mut body = String::new(); res.read_to_string(&mut body).unwrap(); serde_json::from_str(&body) } fn emote_action(action: &str) -> Option<&str> { if action == "shrugs" { Some("¯\\_(ツ)_/¯") } else if let Some(x) = EMOTES.get(action) { let mut rng = thread_rng(); Some(sample(&mut rng, *x, 1)[0]) } else { None } } fn extract_username(prefix: Option) -> Option { prefix.and_then(|p| p.split("!").next().map(|s| s.to_owned())) } struct IrcBot { server: Arc>, } impl IrcBot { fn new(server: Arc>) -> IrcBot { IrcBot { server: server, } } fn wiki_query(&self, origin: &str, article: &str) { let response = query_page(article); if let Ok(q) = response { for page in q.query.pages.values() { let url = format!("https://en.wikipedia.org/?curid={}", page.pageid); self.server.send_privmsg(origin, &format!("{}: {}", page.title, url)).unwrap(); self.server.send_privmsg(origin, &page.extract).unwrap(); break; } } else { self.server.send_privmsg(origin, "Sorry, I couldn't retrieve the wiki page.").unwrap(); println!("{:?}", response); } } fn emote(&self, username: &str, action: &str) { if let Some(reply) = emote_action(action) { self.server.send_privmsg( CHANNEL, &(username.to_owned() + " is " + reply)[..]).unwrap(); } } fn handle_privmsg(&self, origin: &str, username: &str, msg: &str) { if msg.starts_with("!wiki") { let article = msg[5..].trim(); self.wiki_query(origin, article); } else if msg.starts_with("\u{1}ACTION") { let action = msg[8..msg.len() - 1].trim(); self.emote(&username, action); } else if msg.starts_with("/me") { let action = msg[3..].trim(); self.emote(&username, action); } } fn process_messages(&self) { for message in self.server.iter() { let message = message.unwrap(); println!("{:?}", message); if &message.command[..] == "PRIVMSG" { if let Some(msg) = message.suffix { if let Some(username) = extract_username(message.prefix) { let origin = &message.args[0]; self.handle_privmsg(origin, &username, &msg); } } } } } } fn main() { let config = Config { nickname: Some(format!("lidavidm_prime")), server: Some(format!("irc.freenode.net")), channels: Some(vec![CHANNEL.to_string()]), .. Default::default() }; let server = Arc::new(IrcServer::from_config(config).unwrap()); server.identify().unwrap(); let mut handles = Vec::new(); for _ in 0..4 { let s = server.clone(); handles.push(thread::spawn(move || { let bot = IrcBot::new(s.clone()); bot.process_messages(); })); } for handle in handles.into_iter() { handle.join(); } }