1mod deserialize;
10pub mod error;
11pub mod models;
12#[cfg(test)]
13mod tests;
14
15pub use crate::error::ApiError;
16use crate::models::GReaderError;
17pub use crate::models::{AuthData, GoogleAuth, InoreaderAuth};
18use crate::models::{Feeds, ItemRefs, QuickFeed, Stream, StreamType, Taggings, Unread, User};
19use std::collections::HashMap;
20
21#[cfg(any(feature = "feedhq", feature = "oldreader", feature = "inoreader"))]
22use crate::models::StreamPrefs;
23
24use chrono::{TimeDelta, Utc};
25use log::error;
26use models::{AuthInput, OAuthResponse, PostToken};
27use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
28use reqwest::{Client, StatusCode};
29use serde::Deserialize;
30use std::sync::Arc;
31use tokio::sync::Mutex;
32use url::Url;
33
34#[derive(Clone, Debug)]
35pub struct GReaderApi {
36 base_uri: Url,
37 auth_input: Arc<Mutex<AuthInput>>,
38 auth: Arc<Mutex<AuthData>>,
39}
40
41impl GReaderApi {
42 pub fn new(url: &Url, auth: AuthData) -> Self {
44 GReaderApi {
45 base_uri: url.clone(),
46 auth_input: Arc::new(Mutex::new(AuthInput::Uninitialized)),
47 auth: Arc::new(Mutex::new(auth)),
48 }
49 }
50
51 pub async fn get_auth_data(&self) -> Result<AuthData, ApiError> {
52 Ok(self.auth.lock().await.clone())
53 }
54
55 pub async fn set_aut_data(&self, auth: AuthData) -> Result<(), ApiError> {
56 *(self.auth.lock().await) = auth;
57 Ok(())
58 }
59
60 async fn get_auth_headers(&self) -> Result<HeaderMap, ApiError> {
61 let mut headers = HeaderMap::new();
62
63 let auth_data = self.auth.lock().await.clone();
64
65 match &auth_data {
66 AuthData::Uninitialized => return Err(ApiError::Other("api is uninitialized".into())),
67 AuthData::Google(auth_data) => {
68 if auth_data.auth_token.is_none() {
69 return Err(ApiError::NotLoggedIn)?;
70 }
71
72 if let Some(auth_token) = auth_data.auth_token.as_deref() {
73 headers.insert(
74 AUTHORIZATION,
75 HeaderValue::from_str(&format!("GoogleLogin auth={auth_token}"))
76 .expect("parse header value"),
77 );
78 }
79 }
80 AuthData::Inoreader(auth_data) => {
81 let expires_in = auth_data.expires_at.signed_duration_since(Utc::now());
83 let expired = expires_in.num_seconds() <= 60;
84
85 if expired {
86 return Err(ApiError::TokenExpired)?;
87 }
88
89 headers.insert(
90 AUTHORIZATION,
91 HeaderValue::from_str(&format!("Bearer {}", auth_data.access_token))
92 .expect("parse header value"),
93 );
94 headers.insert(
95 "AppId",
96 HeaderValue::from_str(&auth_data.client_id).expect("parse header value"),
97 );
98 headers.insert(
99 "AppKey",
100 HeaderValue::from_str(&auth_data.client_secret).expect("parse header value"),
101 );
102 }
103 };
104
105 Ok(headers)
106 }
107
108 fn deserialize<T: for<'a> Deserialize<'a>>(json: &str) -> Result<T, ApiError> {
109 let result: T = serde_json::from_str(json).map_err(|source| ApiError::Json {
110 source,
111 json: json.into(),
112 })?;
113 Ok(result)
114 }
115
116 async fn get_post_token(&self, client: &Client) -> Result<Option<String>, ApiError> {
117 let mut auth_data = self.auth.lock().await.clone();
118
119 let post_token = match &mut auth_data {
120 AuthData::Inoreader(_) | AuthData::Uninitialized => Ok(None),
121 AuthData::Google(auth_data) => {
122 if let Some(post_token) = auth_data.post_token.as_mut() {
123 if !post_token.is_valid() {
124 let mut response = self
125 .get_request("reader/api/0/token", vec![], client)
126 .await?;
127 let _ = response.pop();
128 post_token.update(&response);
129 Ok(Some(response))
130 } else {
131 Ok(Some(post_token.token.clone()))
132 }
133 } else {
134 let mut response = self
135 .get_request("reader/api/0/token", vec![], client)
136 .await?;
137 let _ = response.pop();
138 let post_token = PostToken::new(&response);
139 auth_data.post_token.replace(post_token);
140 Ok(Some(response))
141 }
142 }
143 };
144
145 *self.auth.lock().await = auth_data;
146 post_token
147 }
148
149 async fn get_request(
150 &self,
151 query: &str,
152 mut params: Vec<(&str, String)>,
153 client: &Client,
154 ) -> Result<String, ApiError> {
155 let api_url: Url = self.base_uri.join(query)?;
156
157 let mut query_params = Vec::new();
158 query_params.append(&mut params);
159 query_params.push(("output", "json".into()));
160
161 let auth_headers = self.get_auth_headers().await?;
162
163 let response = client
164 .get(api_url.clone())
165 .headers(auth_headers)
166 .query(&query_params)
167 .send()
168 .await?;
169
170 let status = response.status();
171 let response = response.text().await?;
172 if status != StatusCode::OK {
173 if status == StatusCode::UNAUTHORIZED {
174 return Err(ApiError::AccessDenied);
175 } else if status == StatusCode::TOO_MANY_REQUESTS {
176 return Err(ApiError::ApiLimit);
177 }
178
179 let error = if let Ok(greader_error) = serde_json::from_str::<GReaderError>(&response) {
180 greader_error
181 } else {
182 GReaderError {
183 errors: vec![response.to_string()],
184 }
185 };
186
187 error!("GReader API: {}", error.errors.join("; "));
188 return Err(ApiError::parse_error(error));
189 }
190 Ok(response)
191 }
192
193 async fn post_request(
194 &self,
195 query: &str,
196 mut params: Vec<(&str, String)>,
197 mut form_params: Vec<(&str, String)>,
198 body: Option<&str>,
199 client: &Client,
200 ) -> Result<String, ApiError> {
201 let api_url: Url = self.base_uri.join(query)?;
202 let mut query_params = Vec::new();
203 query_params.append(&mut params);
204 query_params.push(("output", "json".into()));
205
206 let mut post_params = Vec::new();
207 post_params.append(&mut form_params);
208 if let Some(post_token) = self.get_post_token(client).await? {
209 post_params.push(("T", post_token));
210 }
211
212 let mut headers = self.get_auth_headers().await?;
213 headers.append(
214 CONTENT_TYPE,
215 HeaderValue::from_static("application/x-www-form-urlencoded"),
216 );
217
218 let mut request = client
219 .post(api_url.clone())
220 .headers(headers)
221 .query(&query_params)
222 .form(&post_params);
223
224 if let Some(body) = body {
225 request = request.body(body.to_owned());
226 }
227
228 if body.is_none() && post_params.is_empty() {
229 request = request.header(CONTENT_LENGTH, 0);
230 }
231
232 let response = request.send().await?;
233
234 let status = response.status();
235 let response = response.text().await?;
236 if status != StatusCode::OK {
237 if status == StatusCode::UNAUTHORIZED {
238 return Err(ApiError::AccessDenied);
239 } else if status == StatusCode::BAD_REQUEST {
240 return Err(ApiError::BadRequest);
241 } else if status == StatusCode::TOO_MANY_REQUESTS {
242 return Err(ApiError::ApiLimit);
243 }
244
245 let error = if let Ok(greader_error) = serde_json::from_str::<GReaderError>(&response) {
246 greader_error
247 } else {
248 GReaderError {
249 errors: vec![response.to_string()],
250 }
251 };
252
253 error!("GReader API: {}", error.errors.join("; "));
254 return Err(ApiError::GReader(error));
255 }
256 Ok(response)
257 }
258
259 fn check_ok_response(response: &str) -> Result<(), ApiError> {
260 if response == "OK" {
261 Ok(())
262 } else {
263 let error: GReaderError = GReaderError {
264 errors: vec![response.to_string()],
265 };
266 Err(ApiError::GReader(error))
267 }
268 }
269
270 pub async fn login(
271 &self,
272 auth_input: &AuthInput,
273 client: &Client,
274 ) -> Result<AuthData, ApiError> {
275 *self.auth.lock().await = match auth_input {
276 AuthInput::Uninitialized => return Err(ApiError::Input),
277 AuthInput::Inoreader(input) => {
278 let mut map: HashMap<String, String> = HashMap::new();
279 map.insert("code".into(), input.auth_code.clone());
280 map.insert("redirect_uri".into(), input.redirect_url.clone());
281 map.insert("client_id".into(), input.client_id.clone());
282 map.insert("client_secret".into(), input.client_secret.clone());
283 map.insert("scope".into(), "".into());
284 map.insert("grant_type".into(), "authorization_code".into());
285
286 let response = client
287 .post("https://www.inoreader.com/oauth2/token")
288 .form(&map)
289 .send()
290 .await?;
291
292 let status = response.status();
293 if status == StatusCode::UNAUTHORIZED {
294 return Err(ApiError::AccessDenied);
295 } else if status == StatusCode::BAD_REQUEST {
296 return Err(ApiError::BadRequest);
297 } else if status == StatusCode::TOO_MANY_REQUESTS {
298 return Err(ApiError::ApiLimit);
299 }
300
301 let result = response.text().await?;
302
303 let oauth_response = Self::deserialize::<OAuthResponse>(&result)?;
304
305 let now = Utc::now();
306 let token_expires =
307 now + TimeDelta::try_seconds(oauth_response.expires_in).unwrap();
308
309 AuthData::Inoreader(InoreaderAuth {
310 client_id: input.client_id.clone(),
311 client_secret: input.client_secret.clone(),
312 access_token: oauth_response.access_token,
313 refresh_token: oauth_response.refresh_token,
314 expires_at: token_expires,
315 })
316 }
317 AuthInput::Google(input) => {
318 let api_url: Url = self.base_uri.join("accounts/ClientLogin")?;
319 let response = client
320 .post(api_url.clone())
321 .query(&[("Email", &input.username), ("Passwd", &input.password)])
322 .send()
323 .await?;
324
325 let status = response.status();
326 let response = response.text().await?;
327 if status == StatusCode::UNAUTHORIZED {
328 return Err(ApiError::AccessDenied);
329 } else if status == StatusCode::BAD_REQUEST {
330 return Err(ApiError::BadRequest);
331 } else if status == StatusCode::TOO_MANY_REQUESTS {
332 return Err(ApiError::ApiLimit);
333 } else if status != StatusCode::OK {
334 return Err(ApiError::Other(format!("Status Code {status}")));
335 }
336
337 let auth_token = Self::parse_login_response(&response)?;
338
339 AuthData::Google(GoogleAuth {
340 username: input.username.clone(),
341 password: input.password.clone(),
342 auth_token: Some(auth_token),
343 post_token: None,
344 })
345 }
346 };
347
348 let _post_token = self.get_post_token(client).await?;
349
350 *self.auth_input.lock().await = auth_input.clone();
351
352 Ok(self.auth.lock().await.clone())
353 }
354
355 fn parse_login_response(response: &str) -> Result<String, ApiError> {
356 let mut map = HashMap::new();
357 for line in response.lines() {
358 if line.trim().is_empty() {
359 continue;
360 }
361
362 let mut split = line.splitn(2, '=');
363
364 if let (Some(key), Some(value)) = (split.next(), split.next()) {
365 map.insert(key, value);
366 } else {
367 return Err(ApiError::Other(format!(
368 "response doesn't contain '=' in line: {line}"
369 )));
370 }
371 }
372
373 match map.get("Auth") {
374 Some(auth) => Ok(auth.to_string()),
375 None => Err(ApiError::Other(format!(
376 "response doesn't contain auth token in line: {response}"
377 ))),
378 }
379 }
380
381 pub async fn user_info(&self, client: &Client) -> Result<User, ApiError> {
382 let response = self
383 .get_request("reader/api/0/user-info", vec![], client)
384 .await?;
385 Self::deserialize(&response)
386 }
387
388 pub async fn unread_count(&self, client: &Client) -> Result<Unread, ApiError> {
389 let response = self
390 .get_request("reader/api/0/unread-count", vec![], client)
391 .await?;
392 Self::deserialize(&response)
393 }
394
395 pub async fn subscription_list(&self, client: &Client) -> Result<Feeds, ApiError> {
396 let response = self
397 .get_request("reader/api/0/subscription/list", vec![], client)
398 .await?;
399 Self::deserialize(&response)
400 }
401
402 pub async fn subscription_create(
403 &self,
404 url: &Url,
405 name: Option<&str>,
406 to_stream: Option<&str>,
407 client: &Client,
408 ) -> Result<(), ApiError> {
409 let mut params = Vec::new();
410 params.push(("ac", "subscribe".into()));
411 params.push(("s", format!("feed/{}", &url.as_str())));
412
413 if let Some(name) = name {
414 params.push(("t", name.into()));
415 }
416 if let Some(to_stream) = to_stream {
417 params.push(("a", to_stream.into()));
418 }
419
420 let response = self
421 .post_request(
422 "reader/api/0/subscription/edit",
423 params,
424 vec![],
425 None,
426 client,
427 )
428 .await?;
429 GReaderApi::check_ok_response(&response)
430 }
431
432 pub async fn subscription_edit(
433 &self,
434 item_id: &str,
435 name: Option<&str>,
436 from_stream: Option<&str>,
437 to_stream: Option<&str>,
438 client: &Client,
439 ) -> Result<(), ApiError> {
440 let mut params = Vec::new();
441 params.push(("ac", "edit".into()));
442 params.push(("s", item_id.into()));
443
444 if let Some(name) = name {
445 params.push(("t", name.into()));
446 }
447 if let Some(from_stream) = from_stream {
448 params.push(("r", from_stream.into()));
449 }
450 if let Some(to_stream) = to_stream {
451 params.push(("a", to_stream.into()));
452 }
453
454 let response = self
455 .post_request(
456 "reader/api/0/subscription/edit",
457 params,
458 vec![],
459 None,
460 client,
461 )
462 .await?;
463 GReaderApi::check_ok_response(&response)
464 }
465
466 pub async fn subscription_delete(
467 &self,
468 stream_id: &str,
469 client: &Client,
470 ) -> Result<(), ApiError> {
471 let params = vec![("ac", "unsubscribe".into()), ("s", stream_id.into())];
472
473 let response = self
474 .post_request(
475 "reader/api/0/subscription/edit",
476 params,
477 vec![],
478 None,
479 client,
480 )
481 .await?;
482 GReaderApi::check_ok_response(&response)
483 }
484
485 pub async fn subscription_quickadd(
486 &self,
487 url: &Url,
488 client: &Client,
489 ) -> Result<QuickFeed, ApiError> {
490 let params = vec![("quickadd", url.as_str().into())];
491
492 let response = self
493 .post_request(
494 "reader/api/0/subscription/quickadd",
495 params,
496 vec![],
497 None,
498 client,
499 )
500 .await?;
501 let subscriptions: QuickFeed = Self::deserialize(&response)?;
502 Ok(subscriptions)
503 }
504
505 pub async fn import(&self, opml: String, client: &Client) -> Result<u64, ApiError> {
507 let response = self
508 .post_request(
509 "reader/api/0/subscription/import",
510 vec![],
511 vec![],
512 Some(&opml),
513 client,
514 )
515 .await?;
516
517 if response.starts_with("OK: ") {
518 let response = response.replace("Ok: ", "");
519 let response = response
520 .parse::<u64>()
521 .map_err(|e| ApiError::Other(format!("failed to parse response u64: {e}")))?;
522 Ok(response)
523 } else {
524 Err(ApiError::GReader(GReaderError {
525 errors: vec![response],
526 }))
527 }
528 }
529
530 pub async fn export(&self, client: &Client) -> Result<String, ApiError> {
532 self.get_request("reader/api/0/subscription/export", vec![], client)
533 .await
534 }
535
536 #[cfg(feature = "feedhq")]
538 pub async fn subscribed(&self, stream_id: &str, client: &Client) -> Result<bool, ApiError> {
539 let params = vec![("s", stream_id.into())];
540
541 let response = self
542 .get_request("reader/api/0/subscribed", params, client)
543 .await?;
544 match &response[..] {
545 "true" => Ok(true),
546 "false" => Ok(false),
547 _ => Err(ApiError::GReader(GReaderError {
548 errors: vec![response.to_string()],
549 })),
550 }
551 }
552
553 #[allow(clippy::too_many_arguments)]
554 pub async fn stream_contents(
555 &self,
556 stream_id: Option<&str>,
557 reverse_order: bool,
558 amount: Option<u64>,
559 continuation: Option<&str>,
560 exclude_stream: Option<&str>,
561 include_stream: Option<&str>,
562 filter_older: Option<i64>,
563 filter_newer: Option<i64>,
564 client: &Client,
565 ) -> Result<Stream, ApiError> {
566 let mut params = Vec::new();
567 if reverse_order {
568 params.push(("r", "o".into()));
569 }
570 if let Some(n) = amount {
571 params.push(("n", n.to_string()));
572 }
573 if let Some(c) = continuation {
574 params.push(("c", c.into()));
575 }
576 if let Some(s) = exclude_stream {
577 params.push(("xt", s.into()));
578 }
579 if let Some(s) = include_stream {
580 params.push(("it", s.into()));
581 }
582 if let Some(t) = filter_older {
583 params.push(("ot", t.to_string()));
584 }
585 if let Some(t) = filter_newer {
586 params.push(("nt", t.to_string()));
587 }
588
589 let query = "reader/api/0/stream/contents";
590 let query = if let Some(stream_id) = stream_id {
591 format!("{}/{}", query, stream_id)
592 } else {
593 query.into()
594 };
595 let response = self
596 .post_request(&query, params, vec![], None, client)
597 .await?;
598
599 Self::deserialize(&response)
600 }
601
602 #[allow(clippy::too_many_arguments)]
603 pub async fn items_ids(
604 &self,
605 stream_id: Option<&str>,
606 amount: Option<u64>,
607 include_all_direct_stream_ids: bool,
608 continuation: Option<&str>,
609 exclude_stream: Option<&str>,
610 include_stream: Option<&str>,
611 filter_older: Option<i64>,
612 filter_newer: Option<i64>,
613 client: &Client,
614 ) -> Result<ItemRefs, ApiError> {
615 let mut params = Vec::new();
616
617 if let Some(amount) = amount {
618 params.push(("n", amount.to_string()));
619 }
620 if let Some(stream_id) = stream_id {
621 params.push(("s", stream_id.into()));
622 }
623 if let Some(c) = continuation {
624 params.push(("c", c.into()));
625 }
626 if include_all_direct_stream_ids {
627 params.push(("includeAllDirectStreamIds", "true".into()));
628 }
629 if let Some(s) = exclude_stream {
630 params.push(("xt", s.into()));
631 }
632 if let Some(s) = include_stream {
633 params.push(("it", s.into()));
634 }
635 if let Some(t) = filter_older {
636 params.push(("ot", t.to_string()));
637 }
638 if let Some(t) = filter_newer {
639 params.push(("nt", t.to_string()));
640 }
641 let response = self
642 .get_request("reader/api/0/stream/items/ids", params, client)
643 .await?;
644
645 Self::deserialize(&response)
646 }
647
648 #[cfg(feature = "feedhq")]
649 pub async fn items_count(
650 &self,
651 stream_id: &str,
652 get_latest_date: bool,
653 client: &Client,
654 ) -> Result<String, ApiError> {
655 let mut params = Vec::new();
656 params.push(("s", stream_id.into()));
657
658 if get_latest_date {
659 params.push(("a", "true".into()));
660 }
661
662 let response = self
663 .get_request("reader/api/0/stream/items/count", params, client)
664 .await?;
665 Ok(response)
666 }
667
668 pub async fn items_contents(
669 &self,
670 item_ids: Vec<String>,
671 client: &Client,
672 ) -> Result<Stream, ApiError> {
673 let params = Vec::new();
674 let mut form_params = Vec::new();
675 for item_id in item_ids {
676 form_params.push(("i", item_id));
677 }
678
679 let response = self
680 .post_request(
681 "reader/api/0/stream/items/contents",
682 params,
683 form_params,
684 None,
685 client,
686 )
687 .await?;
688
689 Self::deserialize(&response)
690 }
691
692 pub async fn tag_list(&self, client: &Client) -> Result<Taggings, ApiError> {
693 let response = self
694 .get_request("reader/api/0/tag/list", vec![], client)
695 .await?;
696 Self::deserialize(&response)
697 }
698
699 pub async fn tag_delete(
700 &self,
701 stream_type: StreamType,
702 id: &str,
703 client: &Client,
704 ) -> Result<(), ApiError> {
705 let form_params = vec![(stream_type.into(), id.into())];
706
707 let response = self
708 .post_request(
709 "reader/api/0/disable-tag",
710 vec![],
711 form_params,
712 None,
713 client,
714 )
715 .await?;
716 GReaderApi::check_ok_response(&response)
717 }
718
719 pub async fn tag_rename(
720 &self,
721 stream_type: StreamType,
722 old_name: &str,
723 new_name: &str,
724 client: &Client,
725 ) -> Result<(), ApiError> {
726 let form_params = vec![
727 (stream_type.into(), old_name.into()),
728 ("dest", new_name.into()),
729 ];
730
731 let response = self
732 .post_request("reader/api/0/rename-tag", vec![], form_params, None, client)
733 .await?;
734 GReaderApi::check_ok_response(&response)
735 }
736
737 pub async fn tag_edit(
739 &self,
740 item_ids: &[&str],
741 tag_add: Option<&str>,
742 tag_remove: Option<&str>,
743 client: &Client,
744 ) -> Result<(), ApiError> {
745 if tag_add.is_none() && tag_remove.is_none() {
746 return Err(ApiError::Input);
747 }
748
749 let mut form_params = Vec::new();
750 for item_id in item_ids {
751 form_params.push(("i", item_id.to_string()));
752 }
753 if let Some(remove) = tag_remove {
754 form_params.push(("r", remove.into()));
755 }
756 if let Some(add) = tag_add {
757 form_params.push(("a", add.into()));
758 }
759
760 let response = self
761 .post_request("reader/api/0/edit-tag", vec![], form_params, None, client)
762 .await?;
763 GReaderApi::check_ok_response(&response)
764 }
765
766 pub async fn mark_all_as_read(
767 &self,
768 stream_id: &str,
769 older_than: Option<u64>,
770 client: &Client,
771 ) -> Result<(), ApiError> {
772 let mut params = Vec::new();
773 let form_params = vec![("s", stream_id.into())];
774
775 if let Some(older_than) = older_than {
776 params.push(("ts", older_than.to_string()));
777 }
778
779 let response = self
780 .post_request(
781 "reader/api/0/mark-all-as-read",
782 params,
783 form_params,
784 None,
785 client,
786 )
787 .await?;
788 GReaderApi::check_ok_response(&response)
789 }
790
791 #[allow(unused)]
792 #[cfg(any(feature = "feedhq", feature = "oldreader"))]
793 pub async fn preference_list(&self, client: &Client) -> Result<(), ApiError> {
794 unimplemented!();
795 }
796
797 #[cfg(any(feature = "feedhq", feature = "oldreader", feature = "inoreader"))]
798 pub async fn preference_stream_list(&self, client: &Client) -> Result<StreamPrefs, ApiError> {
799 let response = self
800 .get_request("reader/api/0/preference/stream/list", vec![], client)
801 .await?;
802
803 Self::deserialize(&response)
804 }
805
806 #[allow(unused)]
807 #[cfg(any(feature = "feedhq", feature = "oldreader"))]
808 pub async fn friends_list(&self, client: &Client) -> Result<(), ApiError> {
809 unimplemented!();
810 }
811 #[allow(unused)]
812 #[cfg(feature = "oldreader")]
813 pub async fn friends_edit(&self, client: &Client) -> Result<(), ApiError> {
814 unimplemented!();
815 }
816
817 #[allow(unused)]
818 #[cfg(feature = "inoreader")]
819 pub async fn inoreader_refresh_token(
820 &self,
821 client: &Client,
822 ) -> Result<InoreaderAuth, ApiError> {
823 let auth_data = match &*self.auth.lock().await {
824 AuthData::Inoreader(auth_data) => auth_data.clone(),
825 _ => return Err(ApiError::Token),
826 };
827
828 let client_id = auth_data.client_id.clone();
829 let client_secret = auth_data.client_secret.clone();
830 let refresh_token = auth_data.refresh_token.clone();
831
832 let mut map: HashMap<String, String> = HashMap::new();
833 map.insert("client_id".into(), client_id);
834 map.insert("client_secret".into(), client_secret);
835 map.insert("grant_type".into(), "refresh_token".into());
836 map.insert("refresh_token".into(), refresh_token);
837
838 let response = client
839 .post("https://www.inoreader.com/oauth2/token")
840 .form(&map)
841 .send()
842 .await?;
843
844 let status = response.status();
845 if status == StatusCode::UNAUTHORIZED {
846 return Err(ApiError::AccessDenied);
847 } else if status == StatusCode::BAD_REQUEST {
848 return Err(ApiError::BadRequest);
849 } else if status == StatusCode::TOO_MANY_REQUESTS {
850 return Err(ApiError::ApiLimit);
851 }
852
853 let result = response.text().await?;
854
855 let oauth_response: OAuthResponse = Self::deserialize(&result)?;
856
857 let now = Utc::now();
858 let token_expires = now + TimeDelta::try_seconds(oauth_response.expires_in).unwrap();
859
860 let inoreader_auth = InoreaderAuth {
861 client_id: auth_data.client_id.clone(),
862 client_secret: auth_data.client_secret.clone(),
863 access_token: oauth_response.access_token,
864 refresh_token: oauth_response.refresh_token,
865 expires_at: token_expires,
866 };
867
868 *self.auth.lock().await = AuthData::Inoreader(inoreader_auth.clone());
869
870 Ok(inoreader_auth)
871 }
872
873 #[allow(unused)]
874 #[cfg(feature = "inoreader")]
875 pub async fn create_active_search(&self, client: &Client) -> Result<(), ApiError> {
876 unimplemented!();
877 }
878
879 #[allow(unused)]
880 #[cfg(feature = "inoreader")]
881 pub async fn delete_active_search(&self, client: &Client) -> Result<(), ApiError> {
882 unimplemented!();
883 }
884
885 #[allow(unused)]
886 #[cfg(feature = "oldreader")]
887 pub async fn add_comment(&self, client: &Client) -> Result<(), ApiError> {
888 unimplemented!();
889 }
890}