1use crate::*;
2use futures::Future;
3use std::process::Command;
4use tokio::sync::{mpsc, oneshot};
5use warp::Filter;
6
7#[derive(Debug, Default, Deserialize, Clone)]
8#[serde(default)]
9struct CallbackQuery {
10 oauth_token: String,
11 oauth_verifier: String,
12}
13
14fn setup_server() -> (u32, impl Future<Output = CallbackQuery>) {
15 let (answer_tx, mut answer_rx) = mpsc::unbounded_channel::<CallbackQuery>();
16 let authorization = warp::get()
17 .and(warp::path!("authorization"))
18 .and(warp::query::<CallbackQuery>())
19 .and(warp::any().map(move || answer_tx.clone()))
20 .and_then(
21 |data, sender: mpsc::UnboundedSender<CallbackQuery>| async move {
22 sender.send(data).ok();
23 Result::<String, warp::Rejection>::Ok("<!DOCTYPE html><html><head>Authentication succeeded. You may close this window.<script>window.close();</script></head><body></body></html>".into())
24 },
25 ).map(|reply| {
26 warp::reply::with_header(reply, "Content-Type", "text/html")
27 });
28
29 let (plug_tx, plug_rx) = oneshot::channel();
30 let (_addr, server) =
31 warp::serve(authorization).bind_with_graceful_shutdown(([127, 0, 0, 1], 8200), async {
32 plug_rx.await.ok();
33 });
34
35 tokio::spawn(server);
36
37 (8200, async move {
38 let query = answer_rx.recv().await.unwrap();
39 plug_tx.send(()).ok();
40 query
41 })
42}
43
44impl FlickrAPI {
45 pub async fn login(self) -> Result<Self, Box<dyn Error>> {
51 let (port, answer) = setup_server();
53 let callback_url = format!("http://localhost:{}/authorization", port);
54
55 let response: oauth::OauthTokenAnswer = {
57 let mut params = vec![("oauth_callback", callback_url)];
58 oauth::build_request(
59 oauth::RequestTarget::Get(URL_REQUEST),
60 &mut params,
61 &self.data.key,
62 None,
63 );
64 let request = reqwest::Url::parse_with_params(URL_REQUEST, ¶ms)?;
65 let query = self.data.client.get(request).send().await?;
66 serde_urlencoded::from_str(&query.text().await?)?
67 };
68
69 let request_token = response.to_result()?;
70
71 {
73 let params = vec![
74 ("oauth_token", request_token.token.clone()),
75 ("perms", "write".to_string()),
76 ];
77 let url = reqwest::Url::parse_with_params(URL_AUTHORIZE, params)?.to_string();
78
79 log::info!("OAuth link: {url}");
80
81 #[cfg(target_os = "macos")]
82 Command::new("open").args(vec![url]).spawn()?;
83
84 #[cfg(target_os = "linux")]
85 Command::new("xdg-open").args(vec![url]).spawn()?;
86 }
87
88 let callback_data = answer.await;
90
91 let response: oauth::OauthAccessAnswer = {
93 let mut params = vec![("oauth_verifier", callback_data.oauth_verifier)];
94 oauth::build_request(
95 oauth::RequestTarget::Get(URL_ACCESS),
96 &mut params,
97 &self.data.key,
98 Some(&request_token),
99 );
100 let access = reqwest::Url::parse_with_params(URL_ACCESS, ¶ms)?;
101 let query = self.data.client.get(access).send().await?;
102 serde_urlencoded::from_str(&query.text().await?)?
103 };
104
105 let token = response.to_result()?;
106 let mut data = (*self.data).clone();
107 data.token = Some(token);
108
109 Ok(FlickrAPI {
110 data: Rc::new(data),
111 })
112 }
113}