extern crate reqwest; extern crate serde; extern crate serde_json; extern crate regex; use std::io::Read; use irc::client::prelude::*; use irc::error::Error as IrcError; use self::regex::Regex; use plugin::Plugin; use self::reqwest::Client; use self::reqwest::header::Connection; use self::serde_json::Value; register_plugin!(Currency); lazy_static! { static ref RE: Regex = Regex::new(r"([0-9]+) ([A-Za-z]+) (?i)(to) ([A-Za-z]+)").unwrap(); } struct ConvertionRequest<'a> { value: f64, source: &'a str, target: &'a str, } macro_rules! try_option { ($e:expr) => { match $e { Some(v) => v, None => { return None; } } } } impl<'a> ConvertionRequest<'a> { fn send(&self) -> Option { let response = Client::new() .get("https://api.fixer.io/latest") .form(&[("base", self.source)]) .header(Connection::close()) .send(); match response { Ok(mut response) => { let mut body = String::new(); try_option!(response.read_to_string(&mut body).ok()); let convertion_rates: Result = serde_json::from_str(&body); match convertion_rates { Ok(convertion_rates) => { let rates: &Value = try_option!(convertion_rates.get("rates")); let target_rate: &Value = try_option!(rates.get(self.target.to_uppercase())); Some(self.value * try_option!(target_rate.as_f64())) } Err(_) => None, } } Err(_) => None, } } } impl Currency { fn grep_request<'a>(&self, msg: &'a str) -> Option> { match RE.captures(msg) { Some(captures) => { Some(ConvertionRequest { value: { let capture = try_option!(captures.get(1)).as_str(); try_option!(capture.parse().ok()) }, source: try_option!(captures.get(2)).as_str(), target: try_option!(captures.get(4)).as_str(), // 3 is to/TO }) } None => None, } } fn convert(&self, server: &IrcServer, _: &Message, target: &str, msg: &str) -> Result<(), IrcError> { let request = match self.grep_request(msg) { Some(request) => request, None => { return Ok(()); } }; match request.send() { Some(response) => { server.send_privmsg(target, &*format!("{} {} => {:.4} {}", request.value, request.source, response / 1.00000000, request.target)) } None => server.send_privmsg(target, "Error while converting given currency"), } } } impl Plugin for Currency { fn is_allowed(&self, _: &IrcServer, message: &Message) -> bool { match message.command { Command::PRIVMSG(_, ref msg) => RE.is_match(msg), _ => false, } } fn execute(&mut self, server: &IrcServer, message: &Message) -> Result<(), IrcError> { match message.command { Command::PRIVMSG(ref target, ref msg) => self.convert(server, message, target, msg), _ => Ok(()), } } } #[cfg(test)] mod tests { use tests::{make_server, get_server_value}; use irc::client::prelude::*; use plugin::Plugin; use regex::Regex; use super::Currency; #[test] fn test_big_jpy_to_eur() { let server = make_server("PRIVMSG test :5000000 JPY to EUR\r\n"); let mut plugin = Currency::new(); for message in server.iter() { let message = message.unwrap(); assert!(plugin.is_allowed(&server, &message)); assert!(plugin.execute(&server, &message).is_ok()); } let regex = Regex::new(r"=> ([0-9]{2})").unwrap(); let msg = get_server_value(&server); let captures = regex.captures(&*msg).unwrap(); assert!(captures.at(0).is_some()); let result = captures .at(0) .unwrap() .split_whitespace() .last() .unwrap() .parse::(); assert!(result.is_ok()); let value = result.unwrap(); assert!(value > 10 && value < 100); } }