1use reqwest::{
2 header::{HeaderMap, HeaderValue, CONTENT_TYPE},
3 Client, Url,
4};
5use serde_json::{json, Value};
6use std::io::Read;
7use std::{env, fs, path::Path};
8use std::{
9 error::Error,
10 fs::{File, OpenOptions},
11 io::Write,
12};
13use tiny_http::Server;
14
15pub async fn authenticate() -> Result<(String, String), Box<dyn Error>> {
16 let mut auth_code = String::from("");
17
18 let client_id = env::var("MAL_CLI_CLIENT_ID").expect("Client ID not set.");
19 let client_id = client_id.as_str();
20 let code_challenge = "7tPPwQCPWku8SYxrDr1VyLBHXne7RVNmB8ndAwGvZYTCrD";
21
22 let auth_url = Url::parse_with_params(
23 "https://myanimelist.net/v1/oauth2/authorize",
24 &[
25 ("response_type", "code"),
26 ("client_id", client_id),
27 ("state", "STATE"),
28 ("redirect_uri", "http://localhost:8080"),
29 ("code_challenge", code_challenge),
30 ("code_challenge_method", "plain"),
31 ],
32 )?;
33 open::that(auth_url.as_str())?;
34
35 let server = Server::http("127.0.0.1:8080").unwrap();
37 for rq in server.incoming_requests() {
38 let complete_url = "http://localhost:8080".to_string() + rq.url();
39 let request_url = Url::parse(complete_url.as_str())?;
40
41 auth_code = request_url
42 .query_pairs()
43 .find(|(key, _value)| key == "code")
44 .map(|(_, val)| val)
45 .unwrap()
46 .to_string();
47
48 break;
49 }
50
51 let params = [
53 ("client_id", client_id),
54 ("client_secret", ""),
55 ("code", auth_code.as_str()),
56 ("code_verifier", code_challenge),
57 ("redirect_uri", "http://localhost:8080"),
58 ("grant_type", "authorization_code"),
59 ];
60
61 let client = Client::new();
62 let mut headers = HeaderMap::new();
63 headers.insert(
64 CONTENT_TYPE,
65 HeaderValue::from_static("application/x-www-form-urlencoded"),
66 );
67 let response = client
68 .post("https://myanimelist.net/v1/oauth2/token")
69 .headers(headers)
70 .form(¶ms)
71 .send()
72 .await?;
73
74 let resp: Value = serde_json::from_str(response.text().await?.as_str()).unwrap();
75 Ok((
76 clean_token(resp.get("access_token").unwrap().to_string()),
77 clean_token(resp.get("refresh_token").unwrap().to_string()),
78 ))
79}
80
81pub async fn get_access_token() -> Result<String, Box<dyn Error>> {
82 let home: String = env::var("HOME").unwrap();
83 let token_location: String = format!("{home}/.config/mal-cli");
84
85 let cache = read_token(token_location.clone());
86 match cache {
87 Ok(token) => Ok(clean_token(token.get("token").unwrap().to_string())),
88 _ => {
89 let (access_token, _) = authenticate().await.unwrap();
90 save_token(&access_token, token_location);
91 Ok(access_token)
92 }
93 }
94}
95
96pub async fn reauthenticate() -> Result<String, Box<dyn Error>> {
97 let home: String = env::var("HOME").unwrap();
98 let token_location: String = format!("{home}/.config/mal-cli");
99
100 let (access_token, _) = authenticate().await.unwrap();
101 save_token(&access_token, token_location);
102 Ok(access_token)
103}
104
105fn clean_token(token: String) -> String {
106 token
107 .trim_start_matches("\"")
108 .trim_end_matches("\"")
109 .to_string()
110}
111
112fn save_token(token: &str, token_location: String) {
113 let data = json!({
114 "token": clean_token(token.to_string())
115 });
116
117 let data_string = data.to_string();
118 let mut file = File::create(format!("{token_location}/token.json"))
119 .expect("Unable to create token cache.");
120
121 file.write_all(data_string.as_bytes())
123 .expect("Unable to write token cache.");
124}
125
126fn read_token(token_location: String) -> Result<Value, serde_json::Error> {
127 let binding = token_location.clone();
128 let path = Path::new(&binding);
129 if !path.exists() {
130 match fs::create_dir_all(path) {
132 Ok(_) => (),
133 Err(e) => println!("Error creating directory: {}", e),
134 }
135 }
136
137 if !path.exists() {
139 match fs::create_dir_all(path) {
141 Ok(_) => println!("Directory created successfully."),
142 Err(e) => println!("Error creating directory: {}", e),
143 }
144 }
145 let mut file = OpenOptions::new()
146 .read(true)
147 .create(true)
148 .write(true)
149 .open(format!("{token_location}/token.json"))
150 .expect("Unable to create file..");
151
152 let mut contents = Vec::new();
153 file.read_to_end(&mut contents).unwrap();
154
155 let contents_str = String::from_utf8(contents).unwrap();
156 serde_json::from_str(&contents_str)
157}