Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
6 changes: 3 additions & 3 deletions source/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion source/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
41 changes: 23 additions & 18 deletions source/oauth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cpr/parameters.h>
#include <cpr/payload.h>
#include <cpr/response.h>
#include <cpr/status_codes.h>
#include <curl/curl.h>
#include <curl/urlapi.h>
#include <fmt/core.h>
Expand Down Expand Up @@ -90,18 +91,6 @@ void open_uri(const std::string_view uri)
[[nodiscard]] std::optional<std::string> 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;
Expand Down Expand Up @@ -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("<h1>Successfully authenticated</h1><br><p>You can close this tab</p>", "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("<h1>Failed to authenticate due to error \"{}\". Check logs</h1>",
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("<h1>Failed to authenticate due to unkown error. Check logs</h1>", "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(); });
Expand Down
Loading