url_bot_rs/plugins/
imgur.rs

1use reqwest::{Url, header, header::HeaderMap};
2use failure::{Error, bail};
3use serde::{Serialize, Deserialize};
4
5use crate::{
6    plugin_conf, config::Rtd,
7    plugins::{TitlePlugin, PluginConfig},
8};
9
10/// Imgur title plugin configuration structure
11#[derive(Serialize, Deserialize, Default, Clone)]
12#[serde(default)]
13pub struct Config {
14    api_key: String,
15}
16
17/// Imgur title plugin
18pub struct ImgurPlugin {}
19
20#[cfg(not(test))]
21static REQUEST_URL: &str = "https://api.imgur.com/3/";
22
23impl TitlePlugin for ImgurPlugin {
24    fn name(&self) -> &'static str { "imgur" }
25
26    fn check(&self, config: &PluginConfig, url: &Url) -> bool {
27        if config.imgur.api_key.is_empty() {
28            false
29        } else {
30            url.domain() == Some("imgur.com") && url.path().starts_with("/gallery/")
31        }
32    }
33
34    fn evaluate(&self, rtd: &Rtd , url: &Url) -> Result<String, Error> {
35        let mut headers = HeaderMap::new();
36
37        let req_url = Url::parse(REQUEST_URL)?
38            .join(&url.path()[1..])? // remove leading /
39            .into_string();
40        let header_content = format!("Client-ID {}", &plugin_conf!(rtd, imgur).api_key);
41
42        headers.insert(header::AUTHORIZATION, header_content.parse()?);
43
44        let client = match rtd.get_client() {
45            Ok(c) => c,
46            _ => bail!("Can't get http client"),
47        };
48
49        let res = client
50            .request_with_headers(&req_url, headers)?
51            .json::<Resp>()?;
52
53        Ok(res.data.title)
54    }
55}
56
57// Structures used for typed JSON parsing
58
59#[derive(Debug, Deserialize)]
60struct Resp {
61    data: Data,
62}
63
64#[derive(Debug, Deserialize)]
65struct Data {
66    title: String,
67}
68
69// Tests
70
71#[cfg(test)]
72static REQUEST_URL: &str = "http://127.0.0.1:28284/3/";
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use std::{
78        thread,
79        time::Duration,
80    };
81    use tiny_http::Response;
82
83    #[test]
84    fn name() {
85        let plugin = ImgurPlugin {};
86        assert_eq!(plugin.name(), "imgur");
87    }
88
89    #[test]
90    fn check() {
91        let plugin = ImgurPlugin {};
92        let mut config = PluginConfig::default();
93        let url = Url::parse("https://imgur.com/gallery/foo").unwrap();
94        let bad_url = Url::parse("https://i.imgur.com/foo").unwrap();
95
96        // No API key set
97        assert_eq!(plugin.check(&config, &url), false);
98        assert_eq!(plugin.check(&config, &bad_url), false);
99
100        // API key is set
101        config.imgur.api_key = String::from("bar");
102        assert_eq!(plugin.check(&config, &url), true);
103        assert_eq!(plugin.check(&config, &bad_url), false);
104    }
105
106    #[test]
107    fn evaluate_no_client() {
108        let plugin = ImgurPlugin {};
109        let rtd = Rtd::new();
110        let url = "https://imgur.com/gallery/0pVuZq8";
111        let res = plugin.evaluate(&rtd, &url.parse().unwrap());
112        assert!(res.is_err());
113        if let Err(e) = res { assert_eq!(&format!("{}", e), "Can't get http client"); }
114    }
115
116    #[test]
117    fn evaluate() {
118        let plugin = ImgurPlugin {};
119        let rtd = Rtd::new().init_http_client().unwrap();
120        let bind = "127.0.0.1:28284";
121        let url = "https://imgur.com/gallery/0pVuZq8";
122        let response = r#"{"data":{"id":"0pVuZq8","title":"Ducks and Dog","description":null,"datetime":1594707178,"cover":"0EDF1TX","cover_width":640,"cover_height":611,"account_url":"s3krit","account_id":123456789,"privacy":"hidden","layout":"blog","views":20291,"link":"https://imgur.com/a/0pVuZq8","ups":402,"downs":6,"points":396,"score":406,"is_album":true,"vote":null,"favorite":false,"nsfw":false,"section":"","comment_count":11,"favorite_count":65,"topic":"No Topic","topic_id":29,"images_count":1,"in_gallery":true,"is_ad":false,"tags":[],"ad_type":0,"ad_url":"","in_most_viral":true,"include_album_ads":false,"images":[{"id":"0EDF1TX","title":null,"description":null,"datetime":1594707145,"type":"image/jpeg","animated":false,"width":640,"height":611,"size":120730,"views":11965,"bandwidth":1444534450,"vote":null,"favorite":false,"nsfw":null,"section":null,"account_url":null,"account_id":null,"is_ad":false,"in_most_viral":false,"has_sound":false,"tags":[],"ad_type":0,"ad_url":"","edited":"0","in_gallery":false,"link":"https://i.imgur.com/0EDF1TX.jpg","comment_count":null,"favorite_count":null,"ups":null,"downs":null,"points":null,"score":null}],"ad_config":{"safeFlags":["in_gallery","sixth_mod_safe","gallery","album"],"highRiskFlags":[],"unsafeFlags":[],"wallUnsafeFlags":[],"showsAds":true}},"success":true,"status":200}"#;
123
124        let server_thread = thread::spawn(move || {
125            let server = tiny_http::Server::http(bind).unwrap();
126            let rq = server.recv().unwrap();
127            if rq.url().to_string().starts_with("/3/") {
128                    let resp = Response::from_string(response);
129                    thread::sleep(Duration::from_millis(10));
130                    rq.respond(resp).unwrap();
131            }
132        });
133
134        thread::sleep(Duration::from_millis(1000));
135
136        let res = plugin.evaluate(&rtd, &url.parse().unwrap()).unwrap();
137        assert_eq!(res, String::from("Ducks and Dog"));
138
139        server_thread.join().unwrap();
140    }
141}