1use crate::Resultable;
2use data_encoding::BASE64;
3use hmac::{Hmac, Mac};
4use itertools::Itertools;
5use serde::{Deserialize, Serialize};
6use std::time::{SystemTime, UNIX_EPOCH};
7use url::form_urlencoded;
8
9type HmacSha1 = Hmac<sha1::Sha1>;
10
11#[derive(Serialize, Deserialize, Default, Clone, Debug)]
13pub struct Token {
14 pub token: String,
15 pub secret: String,
16}
17
18#[derive(Serialize, Deserialize, Default, Clone, Debug)]
20pub struct ApiKey {
21 pub key: String,
22 pub secret: String,
23}
24
25#[derive(Debug, Deserialize)]
26#[serde(untagged)]
27pub enum OauthAccessAnswer {
28 Ok(OauthAccessGranted),
29 Err(OauthErrorDescription),
30}
31
32#[derive(Debug, Deserialize)]
33#[serde(untagged)]
34pub enum OauthTokenAnswer {
35 Ok(OauthTokenGranted),
36 Err(OauthErrorDescription),
37}
38
39impl Resultable<Token, String> for OauthTokenAnswer {
40 fn to_result(self) -> Result<Token, String> {
41 match self {
42 OauthTokenAnswer::Ok(OauthTokenGranted {
43 oauth_callback_confirmed: _,
44 oauth_token: token,
45 oauth_token_secret: secret,
46 }) => Ok(Token { token, secret }),
47 OauthTokenAnswer::Err(e) => Err(e.oauth_problem),
48 }
49 }
50}
51
52#[derive(Debug, Default, Deserialize)]
53pub struct OauthAccessGranted {
54 pub fullname: String,
55 pub username: String,
56 pub user_nsid: String,
57 pub oauth_token: String,
58 pub oauth_token_secret: String,
59}
60
61#[derive(Debug, Default, Deserialize)]
62pub struct OauthTokenGranted {
63 pub oauth_callback_confirmed: String,
64 pub oauth_token: String,
65 pub oauth_token_secret: String,
66}
67
68#[derive(Debug, Default, Deserialize)]
69pub struct OauthErrorDescription {
70 pub oauth_problem: String,
71 #[serde(default)]
72 pub debug_sbs: String,
73}
74
75impl Resultable<OauthAccessGranted, String> for OauthAccessAnswer {
76 fn to_result(self) -> Result<OauthAccessGranted, String> {
77 match self {
78 OauthAccessAnswer::Ok(k) => Ok(k),
79 OauthAccessAnswer::Err(e) => Err(e.oauth_problem),
80 }
81 }
82}
83
84impl Resultable<Token, String> for OauthAccessAnswer {
85 fn to_result(self) -> Result<Token, String> {
86 match self {
87 OauthAccessAnswer::Ok(OauthAccessGranted {
88 fullname: _,
89 username: _,
90 user_nsid: _,
91 oauth_token: token,
92 oauth_token_secret: secret,
93 }) => Ok(Token { token, secret }),
94 OauthAccessAnswer::Err(e) => Err(e.oauth_problem),
95 }
96 }
97}
98
99pub enum RequestTarget<'a> {
103 Get(&'a str),
104 Post(&'a str),
105}
106
107impl<'a> RequestTarget<'a> {
108 fn uri(&'a self) -> &'a str {
109 match self {
110 RequestTarget::Get(val) => val,
111 RequestTarget::Post(val) => val,
112 }
113 }
114}
115
116pub fn build_request(
122 target: RequestTarget,
123 params: &mut Vec<(&'static str, String)>,
124 api: &ApiKey,
125 oauth: Option<&Token>,
126) {
127 let seconds = SystemTime::now()
128 .duration_since(UNIX_EPOCH)
129 .unwrap()
130 .as_secs()
131 .to_string();
132
133 let nonce = seconds[1..9].to_string();
134
135 params.extend(vec![
136 ("oauth_consumer_key", api.key.clone()),
137 ("oauth_nonce", nonce),
138 ("oauth_signature_method", "HMAC-SHA1".to_string()),
139 ("oauth_timestamp", seconds),
140 ("oauth_version", "1.0".to_string()),
141 ]);
142
143 let key: String;
144
145 match &oauth {
146 Some(value) => {
147 params.extend(vec![("oauth_token", value.token.clone())]);
148 key = format!("{}&{}", api.secret, value.secret)
149 }
150 None => key = format!("{}&", api.secret),
151 };
152
153 params.sort_by(|a, b| a.0.cmp(b.0));
154
155 let to_sign = params
156 .iter()
157 .filter(|(k, _)| !vec!["photo"].contains(k))
158 .map(|(a, b)| {
159 format!(
160 "{a}={}",
161 form_urlencoded::byte_serialize(&b.as_bytes()).collect::<String>(),
162 )
163 })
164 .join("&");
165
166 let uri = target.uri();
167 let method = match target {
168 RequestTarget::Get(_) => "GET",
169 RequestTarget::Post(_) => "POST",
170 };
171
172 let raw = format!(
173 "{method}&{}&{}",
174 form_urlencoded::byte_serialize(&uri.as_bytes()).collect::<String>(),
175 form_urlencoded::byte_serialize(&to_sign.as_bytes()).collect::<String>()
176 );
177
178 let mut mac = HmacSha1::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
179 mac.update(raw.as_bytes());
180 let signature: String = BASE64.encode(&mac.finalize().into_bytes());
181
182 params.push(("oauth_signature", signature));
183}