char-slop/tidal-dl

tidal ripper

git clone https://git.t4t.associates/char-slop/tidal-dl

Charlotte Somdownload to ~/Downloads/[album] [year]/[song]d6e67a9

main
2.8 KiB102 linesraw
1use anyhow::Result;
2use base64::{engine::general_purpose::STANDARD, Engine as _};
3use serde::{Deserialize, Serialize};
4
5use crate::pkce::DEFAULT_PORT;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Settings {
9    #[serde(default = "default_download_path")]
10    pub download_path: String,
11    #[serde(default = "default_port")]
12    pub auth_callback_port: u16,
13}
14
15impl Default for Settings {
16    fn default() -> Self {
17        Self {
18            download_path: default_download_path(),
19            auth_callback_port: DEFAULT_PORT,
20        }
21    }
22}
23
24fn default_download_path() -> String {
25    if let Some(dir) = dirs::download_dir() {
26        return dir.to_string_lossy().into_owned();
27    }
28    if let Some(home) = dirs::home_dir() {
29        if home.join("Downloads").is_dir() {
30            return home.join("Downloads").to_string_lossy().into_owned();
31        }
32    }
33    "./download".to_string()
34}
35
36fn default_port() -> u16 {
37    DEFAULT_PORT
38}
39
40#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct Token {
43    #[serde(default)]
44    pub userid: Option<u64>,
45    #[serde(default, alias = "country_code")]
46    pub country_code: Option<String>,
47    #[serde(default, alias = "access_token")]
48    pub access_token: Option<String>,
49    #[serde(default, alias = "refresh_token")]
50    pub refresh_token: Option<String>,
51    #[serde(default, alias = "expires_after")]
52    pub expires_after: i64,
53}
54
55pub fn profile_path() -> std::path::PathBuf {
56    crate::pkce::home_dir().join(".tidal-dl.json")
57}
58
59pub fn token_path() -> std::path::PathBuf {
60    crate::pkce::home_dir().join(".tidal-dl.token.json")
61}
62
63pub fn load_settings() -> Settings {
64    let path = profile_path();
65    let Ok(text) = std::fs::read_to_string(&path) else {
66        return Settings::default();
67    };
68    let mut settings: Settings = serde_json::from_str(&text).unwrap_or_default();
69    if matches!(settings.download_path.as_str(), "./download/" | "./download" | "") {
70        settings.download_path = default_download_path();
71    }
72    settings
73}
74
75pub fn save_settings(settings: &Settings) -> Result<()> {
76    let text = serde_json::to_string_pretty(settings)?;
77    std::fs::write(profile_path(), text)?;
78    Ok(())
79}
80
81pub fn load_token() -> Token {
82    let path = token_path();
83    let Ok(raw) = std::fs::read(&path) else {
84        return Token::default();
85    };
86    let text = STANDARD
87        .decode(&raw)
88        .ok()
89        .and_then(|b| String::from_utf8(b).ok())
90        .unwrap_or_else(|| String::from_utf8_lossy(&raw).into_owned());
91    serde_json::from_str(&text).unwrap_or_default()
92}
93
94pub fn save_token(token: &Token) -> Result<()> {
95    let text = serde_json::to_string(token)?;
96    std::fs::write(token_path(), STANDARD.encode(text))?;
97    Ok(())
98}
99
100pub fn clear_token() -> Result<()> {
101    save_token(&Token::default())
102}