1use base64::engine::general_purpose::STANDARD;
2use base64::Engine;
3use reqwest::{Error, Response};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::time::Duration;
7use twapi_oauth::calc_oauth_header;
8
9use crate::build_client;
10
11pub(crate) const DEFAULT_BASE_URL: &str = "https://api.twitter.com";
13
14pub async fn get_bearer_token_response(
15 consumer_key: &str,
16 consumer_secret: &str,
17 timeout_sec: Option<Duration>,
18 base_url: Option<&str>,
19) -> Result<Response, Error> {
20 let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
21 let key = STANDARD.encode(format!("{}:{}", consumer_key, consumer_secret));
22 let client = build_client(timeout_sec);
23 client
24 .post(format!("{}/oauth2/token", base_url))
25 .header(
26 "Content-Type",
27 "application/x-www-form-urlencoded;charset=UTF-8",
28 )
29 .header("Authorization", &format!("Basic {}", key))
30 .body("grant_type=client_credentials")
31 .send()
32 .await
33}
34
35pub async fn get_bearer_token(
36 consumer_key: &str,
37 consumer_secret: &str,
38 timeout_sec: Option<Duration>,
39 base_url: Option<&str>,
40) -> Result<Option<String>, Error> {
41 let json: Value =
42 get_bearer_token_response(consumer_key, consumer_secret, timeout_sec, base_url)
43 .await?
44 .json()
45 .await?;
46 match json["access_token"].as_str() {
47 Some(access_token) => Ok(Some(access_token.to_string())),
48 None => Ok(None),
49 }
50}
51
52pub async fn request_token_response(
53 consumer_key: &str,
54 consumer_secret: &str,
55 oauth_callback: &str,
56 x_auth_access_type: Option<&str>,
57 timeout_sec: Option<Duration>,
58 base_url: Option<&str>,
59) -> Result<Response, Error> {
60 let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
61 let uri = format!("{}/oauth/request_token", base_url);
62 let uri = uri.as_str();
63 let mut header_options = vec![("oauth_callback", oauth_callback)];
64 if let Some(x_auth_access_type) = x_auth_access_type {
65 header_options.push(("x_auth_access_type", x_auth_access_type));
66 }
67 let signed = calc_oauth_header(
68 &format!("{}&", consumer_secret),
69 consumer_key,
70 &header_options,
71 "POST",
72 uri,
73 &vec![],
74 );
75 let client = build_client(timeout_sec);
76 client
77 .post(uri)
78 .header("Authorization", &format!("OAuth {}", signed))
79 .send()
80 .await
81}
82
83pub async fn request_token(
84 consumer_key: &str,
85 consumer_secret: &str,
86 oauth_callback: &str,
87 x_auth_access_type: Option<&str>,
88 timeout_sec: Option<Duration>,
89 base_url: Option<&str>,
90) -> Result<HashMap<String, String>, Error> {
91 let response = request_token_response(
92 consumer_key,
93 consumer_secret,
94 oauth_callback,
95 x_auth_access_type,
96 timeout_sec,
97 base_url,
98 )
99 .await?;
100 Ok(parse_oauth_body(response).await)
101}
102
103pub async fn access_token_response(
104 consumer_key: &str,
105 consumer_secret: &str,
106 oauth_token: &str,
107 oauth_token_secret: &str,
108 oauth_verifier: &str,
109 timeout_sec: Option<Duration>,
110 base_url: Option<&str>,
111) -> Result<Response, Error> {
112 let base_url = base_url.unwrap_or(DEFAULT_BASE_URL);
113 let uri = format!("{}/oauth/access_token", base_url);
114 let uri = uri.as_str();
115 let signed = calc_oauth_header(
116 &format!("{}&{}", consumer_secret, oauth_token_secret),
117 consumer_key,
118 &vec![
119 ("oauth_token", oauth_token),
120 ("oauth_verifier", oauth_verifier),
121 ],
122 "POST",
123 uri,
124 &vec![],
125 );
126 let client = build_client(timeout_sec);
127 client
128 .post(uri)
129 .header("Authorization", &format!("OAuth {}", signed))
130 .send()
131 .await
132}
133
134pub async fn access_token(
135 consumer_key: &str,
136 consumer_secret: &str,
137 oauth_token: &str,
138 oauth_token_secret: &str,
139 oauth_verifier: &str,
140 timeout_sec: Option<Duration>,
141 base_url: Option<&str>,
142) -> Result<HashMap<String, String>, Error> {
143 let response = access_token_response(
144 consumer_key,
145 consumer_secret,
146 oauth_token,
147 oauth_token_secret,
148 oauth_verifier,
149 timeout_sec,
150 base_url,
151 )
152 .await?;
153 Ok(parse_oauth_body(response).await)
154}
155
156async fn parse_oauth_body(response: Response) -> HashMap<String, String> {
157 let status = response.status();
158 let mut result = HashMap::new();
159 match response.text().await {
160 Ok(body) => {
161 if status.is_success() {
162 for item in body.split("&") {
163 let mut pair = item.split("=");
164 if let Some(key) = pair.next() {
165 result.insert(key.to_owned(), pair.next().unwrap_or("").to_owned());
166 }
167 }
168 } else {
169 result.insert("twapi_request_body".to_owned(), body.clone());
170 result.insert(
171 "twapi_request_status_code".to_owned(),
172 status.as_str().to_owned(),
173 );
174 }
175 }
176 Err(_) => {}
177 }
178 result
179}