mal/config/
oauth_config.rs

1use super::*;
2use figlet_rs::FIGfont;
3use serde::{Deserialize, Serialize};
4use serde_yaml;
5use std::{
6    fs,
7    io::{stdin, Write},
8    path::Path,
9};
10#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
11pub struct AuthConfig {
12    pub client_id: String,
13    pub user_agent: Option<String>,
14    pub port: Option<u16>,
15}
16
17impl AuthConfig {
18    // TODO: Strip whitespace from user_agent as it can cause code to panic
19    pub fn load() -> Result<Self, ConfigError> {
20        let paths = Self::get_paths()?;
21        if paths.config_file_path.exists() {
22            let config_string = fs::read_to_string(&paths.config_file_path)?;
23            let config_yml: AuthConfig = serde_yaml::from_str(&config_string)?;
24
25            Ok(config_yml)
26        } else {
27            let standard_font = FIGfont::standard().unwrap();
28            let figlet = standard_font.convert("MAL-CLI");
29            let banner = figlet.unwrap().to_string();
30            println!("{}", banner);
31            println!(
32                "Config will be saved to {}",
33                paths.config_file_path.display()
34            );
35
36            println!("\nHow to get setup:\n");
37
38            let instructions = [
39                "Go to the myanimelist api page - https://myanimelist.net/apiconfig",
40                "Click `Create ID` and create an app",
41                &format!(
42                    "Add `http://127.0.0.1:{}` to the Redirect URIs",
43                    DEFAULT_PORT
44                ),
45                "You are now ready to authenticate with myanimelist!",
46            ];
47
48            let mut number = 1;
49            for item in instructions.iter() {
50                println!("   {}. {}", number, item);
51                number += 1;
52            }
53
54            let mut client_id = String::new();
55            loop {
56                println!("\nEnter your client ID: ");
57                stdin().read_line(&mut client_id)?;
58                let trimmed_client_id = client_id.trim().to_string();
59                if trimmed_client_id.len() == 32
60                    && trimmed_client_id.chars().all(|c| c.is_ascii_hexdigit())
61                {
62                    client_id = trimmed_client_id;
63                    break;
64                } else {
65                    println!("Invalid client ID format. try again");
66                    client_id.clear();
67                }
68            }
69            let mut user_agent = String::new();
70            println!("\nEnter User Agent (default {}): ", DEFAULT_USER_AGENT);
71            stdin().read_line(&mut user_agent)?;
72
73            let user_agent = match user_agent.trim().len() {
74                0 => DEFAULT_USER_AGENT.to_string(),
75                _ => user_agent.trim().to_string(),
76            };
77
78            let mut port = String::new();
79            println!("\nEnter port of redirect uri (default {}): ", DEFAULT_PORT);
80            stdin().read_line(&mut port)?;
81            let port = port.trim().parse::<u16>().unwrap_or(DEFAULT_PORT);
82
83            let config_yml = AuthConfig {
84                client_id,
85                user_agent: Some(user_agent),
86                port: Some(port),
87            };
88
89            let content_yml = serde_yaml::to_string(&config_yml)?;
90
91            let mut new_config = fs::File::create(&paths.config_file_path)?;
92            write!(new_config, "{}", content_yml)?;
93
94            Ok(config_yml)
95        }
96    }
97
98    pub fn get_redirect_uri(&self) -> String {
99        format!("127.0.0.1:{}", self.get_port())
100    }
101
102    pub fn get_port(&self) -> u16 {
103        self.port.unwrap_or(DEFAULT_PORT)
104    }
105
106    pub fn get_user_agent(&self) -> String {
107        match &self.user_agent {
108            Some(s) => s.clone(),
109            None => DEFAULT_USER_AGENT.to_string(),
110        }
111    }
112
113    pub fn get_paths() -> Result<ConfigPaths, ConfigError> {
114        match dirs::home_dir() {
115            Some(home) => {
116                let path = Path::new(&home);
117                let home_config_dir = path.join(CONFIG_DIR);
118                let app_config_dir = home_config_dir.join(APP_CONFIG_DIR);
119
120                if !home_config_dir.exists() {
121                    fs::create_dir(&home_config_dir)?;
122                }
123
124                if !app_config_dir.exists() {
125                    fs::create_dir(&app_config_dir)?;
126                }
127
128                let config_file_path = &app_config_dir.join(OAUTH_FILE);
129                let token_cache_path = &app_config_dir.join(TOKEN_CACHE_FILE);
130
131                let paths = ConfigPaths {
132                    config_file_path: config_file_path.to_path_buf(),
133                    auth_cache_path: token_cache_path.to_path_buf(),
134                };
135
136                Ok(paths)
137            }
138            None => Err(ConfigError::PathError),
139        }
140    }
141}