From 3cfc0e5d7e1481c18022d790fa56fc7d8e801285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Birath?= Date: Tue, 22 Jul 2025 18:04:28 +0200 Subject: [PATCH] Update README and configuration for callback URL changes and improve authentication error handling Update README and default configuration for callback URL to comply with Spotify's new requirement for new and (soon) existing applications. Also fixed responses not being updated since it was not passed as a reference to the callback, and made the responses HTML. --- README.md | 15 +++++++++------ source/Config.cpp | 6 +++--- source/Config.h | 1 - source/oauth.cpp | 41 +++++++++++++++++++++++------------------ 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 62b4f2e..525e0b6 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,25 @@ # Spotify Volume Controller ## Overview -Spotify Volume Controller lets you hook two keys (for example your volume controls) directly to Spotify using Spotify's REST API. + +Spotify Volume Controller lets you hook two keys (for example your volume controls) directly to Spotify using Spotify's REST API. ## Usage -To use Spotify Volume Controller you need to create an application using the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/applications). -After creating an application, go into its settings and add a *Redirect URI*. `http://localhost:5000/callback` is default callback URI, but you can use whatever you want as long as you set it in the config file. -Then copy the *client secret* and the *client id* values to the `config-example.json` file as strings. After that rename the file to `config.json` and start the program. Follow the instructions and if everything is correct it should be able to authorize it self. +To use Spotify Volume Controller you need to create an application using the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/applications). +After creating an application go into its settings and add a _Redirect URI_. `http://127.0.0.1:5000/callback` is default callback URI but you can use whatever you want as long as you set it in the config file and follow [Spotify's requirements](https://developer.spotify.com/documentation/web-api/concepts/redirect_uri). For example `localhost` is not allowed to be used. + +Then copy the _client secret_ and the _client id_ values to the `config-example.json` file as strings. After that rename the file to `config.json` and start the program. Follow the instructions and if everything is correct it should be able to authorize it self. ## Changing volume keys + If the default values aren't working or you just want to use different keys just change the `volume_up` and `volume_down` values in the config file. -The values should be the decimal value of the virtual key codes for the keys you want to use. See [here](https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes) for a complete list of virtual key codes. You can also set `print_keys` to `true` to print the decimal values of the virtual key code for each key you press. +The values should be the decimal value of the virtual key codes for the keys you want to use. See [Microsoft's documentation](https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes) for a complete list of virtual key codes. You can also set `print_keys` to `true` to print the decimal values of the virtual key code for each key you press. ## 403 Error when quickly changing volume -This seems to be some kind of rate limiting from Spotify (though they have a separate error code for that, 429, so not sure why this one is `403`). If you are encountering it frequently, try to increase the volume increment using the `volume_increment` config option (default is 1). +This seems to be some kind of rate limiting from Spotify (though they have a separate error code for that, 429, so not sure why this one is `403`). If you are encountering it frequently, try to increase the volume increment using the `volume_increment` config option (default is 1). ## Building and installing diff --git a/source/Config.cpp b/source/Config.cpp index 694b0b2..8d44916 100644 --- a/source/Config.cpp +++ b/source/Config.cpp @@ -33,6 +33,7 @@ constexpr std::chrono::milliseconds default_batch_delay {100}; constexpr uint32_t default_volume_increment = 1; constexpr std::chrono::milliseconds default_poll_rate {250}; constexpr std::chrono::milliseconds min_poll_rate {100}; +constexpr std::string_view default_callback_url = "http://127.0.0.1:5000/callback"; Config::Config() { @@ -68,10 +69,9 @@ void Config::parse_config_file(const std::filesystem::path& path) get_user_input("Please enter your spotify client secret", input, /*not_empty=*/true); m_config[client_secret_key] = input; - get_user_input(fmt::format("Enter callback url, or leave empty for default ({}).", Config::default_callback_url), - input); + get_user_input(fmt::format("Enter callback url, or leave empty for default ({}).", default_callback_url), input); if (input.empty()) { - m_config[redirect_url_key] = Config::default_callback_url; + m_config[redirect_url_key] = default_callback_url; } else { m_config[redirect_url_key] = input; } diff --git a/source/Config.h b/source/Config.h index 7b8d708..a39cfd5 100644 --- a/source/Config.h +++ b/source/Config.h @@ -45,7 +45,6 @@ class Config [[nodiscard]] std::filesystem::path config_directory() const; private: - static constexpr std::string_view default_callback_url = "http://localhost:5000/callback"; json m_config; std::filesystem::path m_directory; diff --git a/source/oauth.cpp b/source/oauth.cpp index 368b885..3af0821 100644 --- a/source/oauth.cpp +++ b/source/oauth.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -90,18 +91,6 @@ void open_uri(const std::string_view uri) [[nodiscard]] std::optional get_authorization_code(const std::string& callback_address, const Config& config) { - cpr::CurlHolder const curl_holder {}; - cpr::Parameters const params = {{"client_id", config.get_client_id()}, - {"redirect_uri", config.get_redirect_url()}, - {"response_type", "code"}, - {"scope", std::string(scopes)}, - {"show_dialog", "true"}}; - - std::string const auth_url = fmt::format("{}?{}", authorization_url, params.GetContent(curl_holder)); - std::cout << "Please accept the prompt in your browser." << '\n'; - std::cout << "If your browser did not open, please go to the following link to accept the prompt:" << '\n'; - std::cout << auth_url << '\n'; - open_uri(auth_url); std::string host {}; int port = 0; std::string part; @@ -151,23 +140,39 @@ void open_uri(const std::string_view uri) } curl_url_cleanup(url_handle); } - + { + cpr::CurlHolder const curl_holder {}; + cpr::Parameters const params = {{"client_id", config.get_client_id()}, + {"redirect_uri", config.get_redirect_url()}, + {"response_type", "code"}, + {"scope", std::string(scopes)}, + {"show_dialog", "true"}}; + std::string const auth_url = fmt::format("{}?{}", authorization_url, params.GetContent(curl_holder)); + std::cout << "Please accept the prompt in your browser." << '\n'; + std::cout << "If your browser did not open, please go to the following link to accept the prompt:" << '\n'; + std::cout << auth_url << '\n'; + open_uri(auth_url); + } std::string authorization_code {}; { std::thread stop_thread; httplib::Server server; server.Get(part, - [&](const httplib::Request& req, httplib::Response res) + [&](const httplib::Request& req, httplib::Response& res) { if (req.has_param("code")) { authorization_code = req.get_param_value("code"); - res.set_content("Successfully authenticated", "text/plain"); + res.set_content("

Successfully authenticated


You can close this tab

", "text/html"); } else if (req.has_param("error")) { std::cerr << "Failed to get authorization code: " << req.get_param_value("error") << '\n'; - res.set_content("Failed to authenticate, check logs", "text/plain"); + res.set_content(fmt::format("

Failed to authenticate due to error \"{}\". Check logs

", + req.get_param_value("error")), + "text/html"); + res.status = cpr::status::HTTP_INTERNAL_SERVER_ERROR; } else { - std::cerr << "Failed to get authorization code: Unkown error" << '\n'; - res.set_content("Failed to authenticate, check logs", "text/plain"); + std::cerr << "Failed to get authorization code: Unknown error" << '\n'; + res.set_content("

Failed to authenticate due to unkown error. Check logs

", "text/html"); + res.status = cpr::status::HTTP_INTERNAL_SERVER_ERROR; } // Based on https://github.com/yhirose/cpp-httplib/blob/master/example/one_time_request.cc stop_thread = std::thread([&]() { server.stop(); });