Repository: aome510/spotify-player (language: rust)

Issue:
FR: Store `client_id` outside dotfiles
**Is your feature request related to a problem? Please describe.**
I do version control and publish my `.config` directory in a GitHub repo, as quite a lot of people do. Thus, Due to the inclusion of the `client_id`, this results in having to gitignore the `~/.config/spotify_player/app.toml`, as otherwise the id would be leaked.

**Describe the solution you'd like**
Save `client_id` outside the `app.toml`, so the remainder of the spotify-player-config can be stored in a public git repo without any trouble.

The client_id could then be read from an environment variable, which one could save in `.zshenv`.

Alternatively, support something like the `password_cmd` from `spotifyd`: https://docs.spotifyd.rs/config/File.html
```toml
# This is an alternative to the `password` field. Both
# can't be used simultaneously.
password_cmd = "command_that_writes_password_to_stdout"
```

**Describe alternatives you've considered**
- gitignoring the `app.toml` and manually updating it all the time.
- symlinking the `app.toml` into a private cloud storage.



Discussion / hints:

Fix patch:
diff --git a/docs/config.md b/docs/config.md
index 300fbc17..d60b7c03 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -25,6 +25,7 @@ All configuration files should be placed inside the application's configuration
 | Option                            | Description                                                                              | Default                                                 |
 | --------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------- |
 | `client_id`                       | the Spotify client's ID                                                                  | `65b708073fc0480ea92a077233ca87bd`                      |
+| `client_id_command`                   | a shell command that prints the Spotify client ID to stdout (overrides `client_id`)      | `None`                                                  |
 | `client_port`                     | the port that the application's client is running on to handle CLI commands              | `8080`                                                  |
 | `tracks_playback_limit`           | the limit for the number of tracks played in a **tracks** playback                       | `50`                                                    |
 | `playback_format`                 | the format of the text in the playback's window                                          | `{status} {track} • {artists}\n{album}\n{metadata}`     | 
diff --git a/spotify_player/src/cli/handlers.rs b/spotify_player/src/cli/handlers.rs
index 95a466fe..9f972995 100644
--- a/spotify_player/src/cli/handlers.rs
+++ b/spotify_player/src/cli/handlers.rs
@@ -158,8 +158,8 @@ fn try_connect_to_client(socket: &UdpSocket, configs: &config::Configs) -> Resul
             let session = rt.block_on(new_session(&auth_config, false))?;
 
             // create a Spotify API client
-            let client =
-                client::Client::new(session, auth_config, configs.app_config.client_id.clone());
+            let client_id = configs.app_config.get_client_id()?;
+            let client = client::Client::new(session, auth_config, client_id);
             rt.block_on(client.refresh_token())?;
 
             // create a client socket for handling CLI commands
diff --git a/spotify_player/src/config/mod.rs b/spotify_player/src/config/mod.rs
index 36f91e4c..397d596b 100644
--- a/spotify_player/src/config/mod.rs
+++ b/spotify_player/src/config/mod.rs
@@ -48,6 +48,7 @@ impl Configs {
 pub struct AppConfig {
     pub theme: String,
     pub client_id: String,
+    pub client_id_command: Option<Command>,
 
     pub client_port: u16,
 
@@ -141,6 +142,26 @@ pub struct Command {
     pub args: Vec<String>,
 }
 
+impl Command {
+    /// Execute a command, returning stdout if succeeded or stderr if failed
+    pub fn execute(&self, extra_args: Option<Vec<String>>) -> anyhow::Result<String> {
+        let mut args = self.args.clone();
+        args.extend(extra_args.unwrap_or_default());
+
+        let output = std::process::Command::new(&self.command)
+            .args(&args)
+            .output()?;
+
+        if !output.status.success() {
+            let stderr = std::str::from_utf8(&output.stderr)?.to_string();
+            anyhow::bail!(stderr);
+        }
+
+        let stdout = std::str::from_utf8(&output.stdout)?.to_string();
+        Ok(stdout)
+    }
+}
+
 #[derive(Debug, Deserialize, Serialize, ConfigParse, Clone)]
 /// Application device configurations
 pub struct DeviceConfig {
@@ -229,6 +250,7 @@ impl Default for AppConfig {
             theme: "dracula".to_owned(),
             // official Spotify web app's client id
             client_id: "65b708073fc0480ea92a077233ca87bd".to_string(),
+            client_id_command: None,
 
             client_port: 8080,
 
@@ -386,6 +408,14 @@ impl AppConfig {
             ..Default::default()
         }
     }
+
+    /// Returns stdout of `client_id_command` if set, otherwise it returns the the value of `client_id`
+    pub fn get_client_id(&self) -> Result<String> {
+        match self.client_id_command {
+            Some(ref cmd) => cmd.execute(None),
+            None => Ok(self.client_id.clone()),
+        }
+    }
 }
 
 /// gets the application's configuration folder path
diff --git a/spotify_player/src/main.rs b/spotify_player/src/main.rs
index 8046da57..de305c50 100644
--- a/spotify_player/src/main.rs
+++ b/spotify_player/src/main.rs
@@ -117,7 +117,8 @@ async fn start_app(state: &state::SharedState) -> Result<()> {
     let session = auth::new_session(&auth_config, !state.is_daemon).await?;
 
     // create a Spotify API client
-    let client = client::Client::new(session, auth_config, configs.app_config.client_id.clone());
+    let client_id = configs.app_config.get_client_id()?;
+    let client = client::Client::new(session, auth_config, client_id);
     client.refresh_token().await?;
 
     // initialize Spotify-related stuff
diff --git a/spotify_player/src/streaming.rs b/spotify_player/src/streaming.rs
index 858f8d92..5d159ac5 100644
--- a/spotify_player/src/streaming.rs
+++ b/spotify_player/src/streaming.rs
@@ -143,18 +143,7 @@ fn execute_player_event_hook_command(
     cmd: &config::Command,
     event: PlayerEvent,
 ) -> anyhow::Result<()> {
-    let mut args = cmd.args.clone();
-    args.extend(event.args());
-
-    let output = std::process::Command::new(&cmd.command)
-        .args(&args)
-        .output()?;
-
-    // running the player hook command failed, report the command's stderr as an error
-    if !output.status.success() {
-        let stderr = std::str::from_utf8(&output.stderr)?.to_string();
-        anyhow::bail!(stderr);
-    }
+    cmd.execute(Some(event.args()))?;
 
     Ok(())
 }


Test patch: