extern crate htmlescape; extern crate regex; use irc::client::prelude::*; use self::regex::Regex; use plugin::*; use utils; use self::error::*; use error::FrippyError; use error::ErrorKind as FrippyErrorKind; use failure::Fail; use failure::ResultExt; lazy_static! { static ref RE: Regex = Regex::new(r"(^|\s)(https?://\S+)").unwrap(); } #[derive(PluginName, Debug)] pub struct Url { max_kib: usize, } impl Url { /// If a file is larger than `max_kib` KiB the download is stopped pub fn new(max_kib: usize) -> Url { Url { max_kib: max_kib } } fn grep_url(&self, msg: &str) -> Option { let captures = RE.captures(msg)?; debug!("Url captures: {:?}", captures); Some(captures.get(2)?.as_str().to_owned()) } fn get_title<'a>(&self, body: &str) -> Result { let title = body.find("') .map(|offset| tag + offset + 1) .map(|start| { body[start..] .find("") .map(|offset| start + offset) .map(|end| &body[start..end]) }) }) .and_then(|s| s.and_then(|s| s)) .ok_or(ErrorKind::MissingTitle)?; debug!("Title: {:?}", title); htmlescape::decode_html(title).map_err(|_| ErrorKind::HtmlDecoding.into()) } fn url(&self, text: &str) -> Result { let url = self.grep_url(text).ok_or(ErrorKind::MissingUrl)?; let body = utils::download(&url, Some(self.max_kib)).context(ErrorKind::Download)?; let title = self.get_title(&body)?; Ok(title.replace('\n', "|").replace('\r', "|")) } } impl Plugin for Url { fn execute(&self, _: &IrcClient, message: &Message) -> ExecutionStatus { match message.command { Command::PRIVMSG(_, ref msg) => if RE.is_match(msg) { ExecutionStatus::RequiresThread } else { ExecutionStatus::Done }, _ => ExecutionStatus::Done, } } fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> { Ok(match message.command { Command::PRIVMSG(_, ref content) => match self.url(content) { Ok(title) => client .send_privmsg(message.response_target().unwrap(), &title) .context(FrippyErrorKind::Connection)?, Err(e) => Err(e).context(FrippyErrorKind::Url)?, }, _ => (), }) } fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { Ok(client .send_notice( &command.source, "This Plugin does not implement any commands.", ) .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { self.url(&command.tokens[0]) .map_err(|e| e.cause().unwrap().to_string()) } } pub mod error { /// A URL plugin error #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] #[error = "UrlError"] pub enum ErrorKind { /// A download error #[fail(display = "A download error occured")] Download, /// Missing URL error #[fail(display = "No URL was found")] MissingUrl, /// Missing title error #[fail(display = "No title was found")] MissingTitle, /// Html decoding error #[fail(display = "Failed to decode Html characters")] HtmlDecoding, } }