mal_cli/
list.rs

1use std::error::Error;
2
3use inquire::formatter::OptionFormatter;
4use inquire::Select;
5use reqwest::header::AUTHORIZATION;
6use serde_json::Value;
7
8use crate::{
9    auth::{get_access_token, reauthenticate},
10    mal::{Anime, AnimeList},
11};
12
13pub struct ListArgs {
14    num: Option<i32>,
15}
16
17impl ListArgs {
18    pub fn new(num: Option<i32>) -> Self {
19        Self { num }
20    }
21}
22
23pub async fn show_list(args: ListArgs) -> Result<(), Box<dyn Error + Send>> {
24    let access_token = get_access_token().await.unwrap();
25    let client = reqwest::Client::new();
26
27    let num_anime = match args.num {
28        Some(num) => num,
29        _ => 10,
30    };
31    // post request
32    let response = client
33        .get(format!("https://api.myanimelist.net/v2/users/@me/animelist?fields=list_status&limit={num_anime}"))
34        .header(AUTHORIZATION, format!("Bearer {}", &access_token))
35        .send()
36        .await
37        .unwrap();
38
39    if response.status().is_success() {
40        let response_text = response.text().await.unwrap();
41        let response_json: Value = serde_json::from_str(response_text.as_str()).unwrap();
42
43        let formatter: OptionFormatter<AnimeList> =
44            &|i| format!("\nSelected - {}", (*i.value).clone().get_title());
45        loop {
46            let anime_list = parse_anime_list_from_json(&response_json);
47            let anime = Select::new("Status\tCompleted\tTitle", anime_list)
48                .with_page_size(10)
49                .with_formatter(formatter)
50                .prompt();
51
52            match anime {
53                Ok(anime) => show_anime_details(anime.get_id()).await,
54                Err(_) => {
55                    break;
56                }
57            }
58        }
59    } else {
60        // reuauthenticate and try again
61        let _ = reauthenticate().await;
62        println!("Reauthenticated token. Please try again...")
63    }
64    Ok(())
65}
66
67pub fn parse_anime_list_from_json(json: &Value) -> Vec<AnimeList> {
68    let data: &Value = json.get("data").unwrap();
69
70    data.as_array()
71        .unwrap()
72        .iter()
73        .map(|anime| AnimeList::new(anime))
74        .collect::<Vec<AnimeList>>()
75}
76
77pub async fn show_anime_details(id: i64) {
78    /*
79       Get details about the anime
80    */
81    let access_token = get_access_token().await.unwrap();
82    let client = reqwest::Client::new();
83    let response = client
84            .get(
85                format!(
86                    "https://api.myanimelist.net/v2/anime/{}?fields=id,title,start_date,end_date,synopsis,mean,rank",
87                    id
88                )
89            )
90            .header(AUTHORIZATION, format!("Bearer {}", &access_token))
91            .send()
92            .await
93            .unwrap();
94
95    if response.status().is_success() {
96        let response_text = response.text().await.unwrap();
97        let anime: Anime = serde_json::from_str(&response_text).unwrap();
98        println!("{}", anime);
99    }
100
101    let options: Result<String, inquire::InquireError> = Select::new(
102        "Options",
103        vec!["Go back".to_string(), "Open MAL page".to_string()],
104    )
105    .prompt();
106
107    match options {
108        Ok(choice) => {
109            if choice == "Open MAL page" {
110                open_url(id);
111            }
112        }
113        _ => (),
114    }
115}
116
117pub fn open_url(id: i64) {
118    open::that(format!("https://myanimelist.net/anime/{}", id)).unwrap();
119}