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(); });