1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
extern crate htmlescape;
use irc::client::prelude::*;
use 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<String> {
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<String, UrlError> {
let title = body.find("<title")
.map(|tag| {
body[tag..]
.find('>')
.map(|offset| tag + offset + 1)
.map(|start| {
body[start..]
.find("</title>")
.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<String, UrlError> {
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.trim().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<String, String> {
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,
}
}
|