char-slop/tidal-dl

tidal ripper

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

Charlotte Somsimplify3841d9b

main
3.1 KiB100 linesraw
1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum AuthMethod {
3    Device,
4    Pkce,
5}
6
7#[derive(Debug, Clone)]
8pub struct ApiKey {
9    pub name: String,
10    pub client_id: String,
11    pub client_secret: String,
12    pub auth_method: AuthMethod,
13    pub scope: String,
14    pub redirect_uri: String,
15}
16
17const PKCE_CLIENT_ID: &str = "3a5dzbD1VqdaUmqG";
18const PKCE_SCOPE: &str = "r_usr w_usr";
19const PKCE_REDIRECT_URI: &str = "tidal://login/auth";
20
21fn default_scope(method: AuthMethod) -> &'static str {
22    match method {
23        AuthMethod::Pkce => PKCE_SCOPE,
24        AuthMethod::Device => crate::tidal::DEVICE_SCOPE,
25    }
26}
27
28fn embedded_key() -> ApiKey {
29    ApiKey {
30        name: "TIDAL desktop app (public client)".to_string(),
31        client_id: PKCE_CLIENT_ID.to_string(),
32        client_secret: String::new(),
33        auth_method: AuthMethod::Pkce,
34        scope: PKCE_SCOPE.to_string(),
35        redirect_uri: PKCE_REDIRECT_URI.to_string(),
36    }
37}
38
39/// The single client to authenticate with: the embedded PKCE client, with
40/// optional overrides from the environment (see `load_dotenv`).
41pub fn load_key() -> ApiKey {
42    load_dotenv();
43    let mut key = embedded_key();
44    if let Ok(id) = std::env::var("TIDAL_CLIENT_ID") {
45        if !id.is_empty() {
46            key.name = std::env::var("TIDAL_CLIENT_NAME")
47                .unwrap_or_else(|_| "Custom (env)".to_string());
48            key.client_id = id;
49            key.auth_method = match std::env::var("TIDAL_AUTH_METHOD")
50                .unwrap_or_default()
51                .as_str()
52            {
53                "device" => AuthMethod::Device,
54                _ => AuthMethod::Pkce,
55            };
56            key.scope = default_scope(key.auth_method).to_string();
57            key.redirect_uri = PKCE_REDIRECT_URI.to_string();
58        }
59    }
60    if let Ok(v) = std::env::var("TIDAL_CLIENT_SECRET") {
61        key.client_secret = v;
62    }
63    if let Ok(v) = std::env::var("TIDAL_SCOPE") {
64        if !v.is_empty() {
65            key.scope = v;
66        }
67    }
68    if let Ok(v) = std::env::var("TIDAL_REDIRECT_URI") {
69        if !v.is_empty() {
70            key.redirect_uri = v;
71        }
72    }
73    key
74}
75
76fn load_dotenv() {
77    let home = dirs::home_dir().unwrap_or_else(|| ".".into());
78    let candidates = [
79        std::env::var("TIDAL_ENV_FILE").ok(),
80        Some(home.join(".tidal-dl.env").to_string_lossy().into_owned()),
81        Some(".env".to_string()),
82    ];
83    for path in candidates.into_iter().flatten() {
84        if let Ok(text) = std::fs::read_to_string(&path) {
85            for line in text.lines() {
86                let line = line.trim();
87                if line.is_empty() || line.starts_with('#') || !line.contains('=') {
88                    continue;
89                }
90                let (k, v) = line.split_once('=').unwrap();
91                let k = k.trim().to_string();
92                let v = v.trim().trim_matches('"').trim_matches('\'').to_string();
93                if !k.is_empty() && std::env::var(&k).is_err() {
94                    std::env::set_var(k, v);
95                }
96            }
97            break;
98        }
99    }
100}