From 02e45a0aff7e6d4e2addfbc75c25f90ff69f8dda Mon Sep 17 00:00:00 2001 From: Chaerne <99126886+FinianLandes@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:49:02 +0100 Subject: [PATCH] Added Direct Login Support --- src/SpotifyESP32.cpp | 1520 ++++++++++++++++++++++++++++++++++++++++++ src/SpotifyESP32.h | 818 +++++++++++++++++++++++ src/config.h | 6 + 3 files changed, 2344 insertions(+) create mode 100644 src/SpotifyESP32.cpp create mode 100644 src/SpotifyESP32.h create mode 100644 src/config.h diff --git a/src/SpotifyESP32.cpp b/src/SpotifyESP32.cpp new file mode 100644 index 0000000..64234e2 --- /dev/null +++ b/src/SpotifyESP32.cpp @@ -0,0 +1,1520 @@ +#include "SpotifyESP32.h" +namespace Spotify_types { + bool SHUFFLE_ON = true; + bool SHUFFLE_OFF = false; + char* REPEAT_OFF = "off"; + char* REPEAT_TRACK = "track"; + char* REPEAT_CONTEXT = "context"; + char* TYPE_ALBUM = "album"; + char* TYPE_ARTIST = "artist"; + char* TYPE_TRACK = "track"; + char* TYPE_PLAYLIST = "playlist"; + char* TOP_TYPE_ARTIST = "artists"; + char* TOP_TYPE_TRACKS = "tracks"; + char* GROUP_ALBUM = "album"; + char* GROUP_SINGLE = "single"; + char* GROUP_APPEARS_ON = "appears_on"; + char* GROUP_COMPILATION = "compilation"; + char* TIME_RANGE_SHORT = "short_term"; + char* TIME_RANGE_MEDIUM = "medium_term"; + char* TIME_RANGE_LONG = "long_term"; + int SIZE_OF_ID = 40; + int SIZE_OF_URI = 50; +} + +Spotify::Spotify(const char* client_id,const char* client_secret,int server_port, bool debug_on, int max_num_retry): _server(server_port){ + _retry = 0; + _client_id = client_id; + _client_secret = client_secret; + _port = server_port; + _debug_on = debug_on; + if(max_num_retry>0){ + _max_num_retry = max_num_retry; + } + else{ + _max_num_retry = 1; + } +} + +Spotify::Spotify(const char* client_id,const char* client_secret,const char* refresh_token, bool debug_on, int max_num_retry) +{ + _port = 80; + _retry = 0; + _client_id = client_id; + _client_secret = client_secret; + strcpy(_refresh_token, refresh_token); + _debug_on = debug_on; + if(max_num_retry>0){ + _max_num_retry = max_num_retry; + } + else{ + _max_num_retry = 1; + } +} + +std::function callback_fn(Spotify *spotify) { + return [spotify]() { + return spotify->server_on_root(); + }; +}; + +bool Spotify::get_refresh_token(){ + char url[40] = "https://accounts.spotify.com/api/token"; + HTTPClient http; + + String authorization = String(_client_id) + ":" + String(_client_secret); + authorization.trim(); + authorization = "Basic " + base64::encode(authorization); + + http.begin(url, _spotify_root_ca); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); + http.addHeader("Authorization", authorization); + + String payload = "grant_type=authorization_code&code=" + String(_auth_code) + "&redirect_uri=" + String(_redirect_uri); + int http_code = http.POST(payload); + + if (_debug_on) { + Serial.printf("POST \"Refresh token\" Status: %d \n", http_code); + Serial.printf("Reply: %s\n", http.getString().c_str()); + } + if ((http_code >= 200) && (http_code <= 299)) { + String response = http.getString(); + StaticJsonDocument<1000> doc; + deserializeJson(doc, response); + const char* temp = doc["refresh_token"]; + strncpy(_refresh_token, temp, sizeof(_refresh_token)); + Serial.printf("Refresh Token: %s\n", _refresh_token); + } + http.end(); + return strcmp(_refresh_token, "") != 0; +} + +void Spotify::server_on_root() { + if (strcmp(_refresh_token, "") == 0) { + if (_server.args() == 0) { + char page[900]; + snprintf(page,sizeof(page),_login_page, _client_id, _redirect_uri); + _server.send(200, "text/html", String(page)); + } else { + if (_server.hasArg("code")) { + strncpy(_auth_code, _server.arg("code").c_str(), sizeof(_auth_code)); + if(_debug_on){ + Serial.printf("Auth Code: %s\n", _auth_code); + } + if(get_refresh_token()){ + char message[500]; + sprintf(message, "Setup Complete, Refresh Token: %s
You can now close this page", _refresh_token); + _server.send(200, "text/html", message); + } + else{ + _server.send(200, "text/html", "Something went wrong, please try again"); + } + _server.stop(); + } else { + char page[900]; + snprintf(page,sizeof(page),_login_page, _client_id, _redirect_uri); + _server.send(200, "text/html", String(page)); + } + } + } else { + _server.send(200, "text/html", "Spotify setup complete"); + _server.stop(); + } +} + +void Spotify::begin(){ + if(strcmp(_refresh_token, "") == 0){ + if(_port == 80){ + sprintf(_redirect_uri, "http://%s/", WiFi.localIP().toString().c_str()); + Serial.printf("Go to this url in your Browser to login to spotify: %s\n", _redirect_uri); + } + else{ + sprintf(_redirect_uri, "http://%s:%d/", WiFi.localIP().toString().c_str(), _port); + Serial.printf("Go to this url in your Browser to login to spotify: %s\n", _redirect_uri); + } + _server.on("/", HTTP_GET, [this](){ + if(_debug_on){ + Serial.println("Send response to Root"); + } + auto root = callback_fn(this); + root(); + }); + _server.begin(); + if(_debug_on){ + Serial.println("Server started"); + } + } + else{ + if(_debug_on){ + Serial.println("Refresh token already set"); + } + } +} + +void Spotify::handle_client(){ + _server.handleClient(); +} + +bool Spotify::is_auth(){ + return strcmp(_refresh_token, "") != 0; +} + +//Basic functions +response Spotify::RestApiPut(char* rest_url,int payload_size, char* payload){ + response response_obj; + init_response(&response_obj); + + HTTPClient http; + http.begin(rest_url,_spotify_root_ca); + http.addHeader("Accept", "application/json"); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization","Bearer "+_access_token); + http.addHeader("content-Length", String(payload_size)); + + int http_code=http.PUT(payload); + + response_obj.status_code = http_code; + String reply = ""; + DynamicJsonDocument doc(2000); + + if(http.getSize()>0){ + reply = http.getString(); + deserializeJson(doc, reply); + } + + if(_debug_on){ + const char* endpoint = extract_endpoint(rest_url); + Serial.printf("PUT \"%s\" Status: %i\n", endpoint, http_code); + Serial.printf("Reply: %s\n", reply.c_str()); + } + if ((http_code >= 200)&&(http_code <= 299)){ + response_obj.reply = "Success"; + } + else if(_retry<=_max_num_retry){ + String message = doc["error"]["message"].as(); + if(message == "Only valid bearer authentication supported"){ + _retry++; + if(get_token()){ + response_obj = RestApiPut(rest_url,payload_size, payload); + } + } + else{ + response_obj.reply = message; + } + } + http.end(); + _retry = 0; + + return response_obj; + +} +response Spotify::RestApiGet(char* rest_url){ + response response_obj; + init_response(&response_obj); + + HTTPClient http; + http.begin(rest_url,_spotify_root_ca); + http.addHeader("Accept", "application/json"); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization","Bearer "+_access_token); + int http_code = http.GET(); + + response_obj.status_code = http_code; + if(_debug_on){ + const char* endpoint = extract_endpoint(rest_url); + Serial.printf("PUT \"%s\" Status: %i\n", endpoint, http_code); + Serial.printf("Reply: %s\n", http.getString().c_str()); + } + if ((http_code >=200)&&(http_code<=299)){ + String response = http.getString(); + response_obj.reply = response; + } + else if(_retry<=_max_num_retry){ + _retry++; + if(get_token()){ + response_obj = RestApiGet(rest_url); + } + } + http.end(); + _retry = 0; + + return response_obj; +} +response Spotify::RestApiPost(char* rest_url,int payload_size, char* payload){ + response response_obj; + init_response(&response_obj); + + HTTPClient http; + http.begin(rest_url,_spotify_root_ca); + http.addHeader("Accept", "application/json"); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization","Bearer "+_access_token); + http.addHeader("content-Length", String(payload_size)); + int http_code=http.POST(payload); + + response_obj.status_code = http_code; + String reply = ""; + DynamicJsonDocument doc(2000); + + if(http.getSize()>0){ + reply = http.getString(); + deserializeJson(doc, reply); + } + + if(_debug_on){ + const char* endpoint = extract_endpoint(rest_url); + Serial.printf("PUT \"%s\" Status: %i\n", endpoint, http_code); + Serial.printf("Reply: %s\n", reply.c_str()); + } + if ((http_code >= 200)&&(http_code <= 299)){ + response_obj.reply = "Success"; + } + else if(_retry<=_max_num_retry){ + String message = doc["error"]["message"].as(); + if(message == "Only valid bearer authentication supported"){ + _retry++; + if(get_token()){ + response_obj = RestApiPost(rest_url,payload_size, payload); + } + } + else{ + response_obj.reply = message; + } + } + http.end(); + _retry = 0; + + return response_obj; +} +response Spotify::RestApiDelete(char* rest_url, char* payload){ + response response_obj; + init_response(&response_obj); + + HTTPClient http; + http.begin(rest_url, _spotify_root_ca); + http.addHeader("Accept", "application/json"); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Authorization", "Bearer " + _access_token); + + int http_code = http.sendRequest("DELETE", payload); + + response_obj.status_code = http_code; + String reply = ""; + DynamicJsonDocument doc(2000); + + if (http.getSize() > 0) { + reply = http.getString(); + deserializeJson(doc, reply); + } + + if(_debug_on){ + const char* endpoint = extract_endpoint(rest_url); + Serial.printf("PUT \"%s\" Status: %i\n", endpoint, http_code); + Serial.printf("Reply: %s\n", reply.c_str()); + } + + if ((http_code >= 200) && (http_code <= 299)) { + response_obj.reply = "Success"; + } else if (_retry <= _max_num_retry) { + String message = doc["error"]["message"].as(); + if (message == "Only valid bearer authentication supported") { + _retry++; + if (get_token()) { + response_obj = RestApiDelete(rest_url, payload); + } + } else { + response_obj.reply = message; + } + } + + http.end(); + _retry = 0; + + return response_obj; + +} +bool Spotify::get_token(){ + bool reply = false; + HTTPClient http; + char url[40] = "https://accounts.spotify.com/api/token"; + String authorization = String(_client_id) + ":" + String(_client_secret); + authorization.trim(); + authorization = "Basic " + base64::encode(authorization); + http.begin(url, _spotify_root_ca); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); + http.addHeader("Authorization", authorization); + + String payload = "grant_type=refresh_token&refresh_token=" + String(_refresh_token); + int http_code = http.POST(payload); + if (_debug_on) { + Serial.printf("POST \"token\" Status: %d \n", http_code); + Serial.printf("Reply: %s\n", http.getString().c_str()); + } + if ((http_code >=200)&&(http_code<=299)) { + String response = http.getString(); + StaticJsonDocument<500> doc; + deserializeJson(doc, response); + _access_token = doc["access_token"].as(); + reply = true; + } + else{ + reply = false; + } + http.end(); + return reply; +} + +void Spotify::init_response(response* response_obj){ + response_obj -> status_code = -1; + response_obj -> reply ="If you see this something went wrong"; +} +char* Spotify::array_to_char(int size, char** array, char* result) { + result[0] = '\0'; + for (int i = 0; i < size; ++i) { + strcat(result, array[i]); + if (i != size - 1) { + strcat(result, ","); + } + } + return result; +} +void Spotify::array_to_json_array(int size, char** array, char* data, int data_size){ + DynamicJsonDocument doc(_max_char_size); + JsonArray json_array = doc.to(); + for (int i = 0; i char_params; + std::map float_params; + populate_float_values(float_params, recom); + populate_char_values(char_params, recom); + + for (const auto& param : char_params) { + char value[100]; + sprintf(value, "&%s%s",param.first, param.second); + strcat(url, value); + } + for(const auto& param : float_params){ + char value[100]; + sprintf(value, "&%s=%f",param.first, param.second); + strcat(url, value); + } + + return RestApiGet(url); +} +bool Spotify::is_valid_value(float param) { + return param >= 0.0 && param <= 1.0; +} +bool Spotify::is_valid_value(int param) { + return param >0; +} +void Spotify::populate_float_values(std::map& float_params, recommendations& recom){ + if (is_valid_value(recom.min_acousticness)) { + float_params["min_acousticness"] = recom.min_acousticness; + } + if (is_valid_value(recom.max_acousticness)) { + float_params["max_acousticness"] = recom.max_acousticness; + } + if (is_valid_value(recom.target_acousticness)) { + float_params["target_acousticness"] = recom.target_acousticness; + } + + if (is_valid_value(recom.min_danceability)) { + float_params["min_danceability"] = recom.min_danceability; + } + if (is_valid_value(recom.max_danceability)) { + float_params["max_danceability"] = recom.max_danceability; + } + if (is_valid_value(recom.target_danceability)) { + float_params["target_danceability"] = recom.target_danceability; + } + + if (is_valid_value(recom.min_duration_ms)) { + float_params["min_duration_ms"] = recom.min_duration_ms; + } + if (is_valid_value(recom.max_duration_ms)) { + float_params["max_duration_ms"] = recom.max_duration_ms; + } + if (is_valid_value(recom.target_duration_ms)) { + float_params["target_duration_ms"] = recom.target_duration_ms; + } + + if (is_valid_value(recom.min_energy)) { + float_params["min_energy"] = recom.min_energy; + } + if (is_valid_value(recom.max_energy)) { + float_params["max_energy"] = recom.max_energy; + } + if (is_valid_value(recom.target_energy)) { + float_params["target_energy"] = recom.target_energy; + } + + if (is_valid_value(recom.min_instrumentalness)) { + float_params["min_instrumentalness"] = recom.min_instrumentalness; + } + if (is_valid_value(recom.max_instrumentalness)) { + float_params["max_instrumentalness"] = recom.max_instrumentalness; + } + if (is_valid_value(recom.target_instrumentalness)) { + float_params["target_instrumentalness"] = recom.target_instrumentalness; + } + + if (is_valid_value(recom.min_key)) { + float_params["min_key"] = recom.min_key; + } + if (is_valid_value(recom.max_key)) { + float_params["max_key"] = recom.max_key; + } + if (is_valid_value(recom.target_key)) { + float_params["target_key"] = recom.target_key; + } + + if (is_valid_value(recom.min_liveness)) { + float_params["min_liveness"] = recom.min_liveness; + } + if (is_valid_value(recom.max_liveness)) { + float_params["max_liveness"] = recom.max_liveness; + } + if (is_valid_value(recom.target_liveness)) { + float_params["target_liveness"] = recom.target_liveness; + } + + if (is_valid_value(recom.min_loudness)) { + float_params["min_loudness"] = recom.min_loudness; + } + if (is_valid_value(recom.max_loudness)) { + float_params["max_loudness"] = recom.max_loudness; + } + if (is_valid_value(recom.target_loudness)) { + float_params["target_loudness"] = recom.target_loudness; + } + + if (is_valid_value(recom.min_mode)) { + float_params["min_mode"] = recom.min_mode; + } + if (is_valid_value(recom.max_mode)) { + float_params["max_mode"] = recom.max_mode; + } + if (is_valid_value(recom.target_mode)) { + float_params["target_mode"] = recom.target_mode; + } + + if (is_valid_value(recom.min_popularity)) { + float_params["min_popularity"] = recom.min_popularity; + } + if (is_valid_value(recom.max_popularity)) { + float_params["max_popularity"] = recom.max_popularity; + } + if (is_valid_value(recom.target_popularity)) { + float_params["target_popularity"] = recom.target_popularity; + } + + if (is_valid_value(recom.min_speechiness)) { + float_params["min_speechiness"] = recom.min_speechiness; + } + if (is_valid_value(recom.max_speechiness)) { + float_params["max_speechiness"] = recom.max_speechiness; + } + if (is_valid_value(recom.target_speechiness)) { + float_params["target_speechiness"] = recom.target_speechiness; + } + + if (is_valid_value(recom.min_tempo)) { + float_params["min_tempo"] = recom.min_tempo; + } + if (is_valid_value(recom.max_tempo)) { + float_params["max_tempo"] = recom.max_tempo; + } + if (is_valid_value(recom.target_tempo)) { + float_params["target_tempo"] = recom.target_tempo; + } + + if (is_valid_value(recom.min_time_signature)) { + float_params["min_time_signature"] = recom.min_time_signature; + } + if (is_valid_value(recom.max_time_signature)) { + float_params["max_time_signature"] = recom.max_time_signature; + } + if (is_valid_value(recom.target_time_signature)) { + float_params["target_time_signature"] = recom.target_time_signature; + } + + if (is_valid_value(recom.min_valence)) { + float_params["min_valence"] = recom.min_valence; + } + if (is_valid_value(recom.max_valence)) { + float_params["max_valence"] = recom.max_valence; + } + if (is_valid_value(recom.target_valence)) { + float_params["target_valence"] = recom.target_valence; + } +} +void Spotify::populate_char_values(std::map& char_params, recommendations& recom){ + char arr[_max_char_size]; + if(is_valid_value(recom.seed_artists_size)){ + char_params["seed_artists"] = array_to_char(recom.seed_artists_size, recom.seed_artists, arr); + } + if(is_valid_value(recom.seed_genres_size)){ + char_params["seed_genres"] = array_to_char(recom.seed_genres_size, recom.seed_genres, arr); + } + if(is_valid_value(recom.seed_tracks_size)){ + char_params["seed_tracks"] = array_to_char(recom.seed_tracks_size, recom.seed_tracks,arr); + } +} +#endif +#ifdef ENABLE_USER +//Users +response Spotify::get_current_user_profile() { + char url[] = "https://api.spotify.com/v1/me"; + + return RestApiGet(url); +} +response Spotify::get_user_top_items(char* type, char* time_range, int limit, int offset) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/me/top/%s?time_range=%s&limit=%d&offset=%d", type, time_range, limit, offset); + + return RestApiGet(url); +} +response Spotify::get_user_profile(char* user_id) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/users/%s", user_id); + + return RestApiGet(url); +} +response Spotify::follow_playlist(char* playlist_id, bool is_public) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/playlists/%s/followers", playlist_id); + char payload[100]; + int payload_size = 0; + sprintf(payload, "{\"public\":%s}", is_public ? "true" : "false"); + payload_size = strlen(payload); + + return RestApiPut(url, payload_size, payload); +} +response Spotify::unfollow_playlist(char* playlist_id) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/playlists/%s/followers", playlist_id); + + return RestApiDelete(url); +} +response Spotify::get_followed_artists(char* type, char* after, int limit) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/me/following?type=%s&after=%s&limit=%d", type, after, limit); + + return RestApiGet(url); +} +response Spotify::follow_artists_or_users(char* type,int size, char** artist_user_ids) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/me/following?type=%s", type); + char payload[_max_char_size]; + int payload_size = 0; + array_to_json_array(size, artist_user_ids, payload); + payload_size = strlen(payload); + + return RestApiPut(url, payload_size, payload); +} +response Spotify::unfollow_artists_or_users(char* type,int size, char** artist_user_ids) { + char url[100]; + sprintf(url, "https://api.spotify.com/v1/me/following?type=%s", type); + char payload[_max_char_size]; + array_to_json_array(size, artist_user_ids, payload); + + return RestApiDelete(url, payload); +} +response Spotify::check_if_user_follows_artists_or_users(char* type,int size, char** artist_user_ids) { + char url[_max_char_size]; + char arr[_max_char_size]; + sprintf(url, "https://api.spotify.com/v1/me/following/contains?type=%s&ids=%s", type, array_to_char(size, artist_user_ids, arr)); + + return RestApiGet(url); +} +response Spotify::check_if_users_follow_playlist(char* playlist_id,int size, char** user_ids) { + char url[_max_char_size]; + char arr[_max_char_size]; + sprintf(url, "https://api.spotify.com/v1/playlists/%s/followers/contains?ids=%s", playlist_id, array_to_char(size, user_ids, arr)); + + return RestApiGet(url); +} +#endif +#ifdef ENABLE_SIMPIFIED +//Simplified functions, formatting functions +String Spotify::current_track_name(){ + String track_name = "Something went wrong"; + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + track_name = doc["item"]["name"].as(); + } + return track_name; + +} +String Spotify::current_track_id(){ + String track_id = "Something went wrong"; + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + track_id = doc["item"]["id"].as(); + } + return track_id; +} +String Spotify::current_device_id(){ + String device_id = "Something went wrong"; + response data = available_devices(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(2000); + deserializeJson(doc,data.reply); + JsonArray devices = doc["devices"].as(); + for (JsonVariant device : devices) { + JsonObject deviceObj = device.as(); + + if (deviceObj["is_active"].as()) { + device_id = deviceObj["id"].as(); + break; + } + } + } + return device_id; +} +String Spotify::current_artist_names(){ + String artist_names = "Something went wrong"; + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + JsonArray array = doc["item"]["artists"]; + int len = array.size(); + artist_names = ""; + for (int i = 0; i(); + if (i=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(2000); + deserializeJson(doc,data.reply); + JsonArray devices = doc["devices"].as(); + for (JsonVariant device : devices) { + JsonObject deviceObj = device.as(); + + if (deviceObj["is_active"].as()) { + sprintf(device_id, "%s", deviceObj["id"].as().c_str()); + break; + } + } + } + return device_id; +} +char* Spotify::current_track_name(char * track_name){ + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + sprintf(track_name, "%s", doc["item"]["name"].as().c_str()); + } + return track_name; +} +char* Spotify::current_track_id(char * track_id){ + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + sprintf(track_id, "%s", doc["item"]["id"].as().c_str()); + } + return track_id; +} +char* Spotify::current_artist_names(char * artist_names){ + response data = currently_playing(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + JsonArray array = doc["item"]["artists"]; + int len = array.size(); + artist_names[0] = '\0'; + for (int i = 0; i().c_str()); + if (i=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + is_playing = doc["is_playing"].as(); + } + return is_playing; +} +bool Spotify::volume_modifyable(){ + bool volume_modifyable = false; + response data = current_playback_state(); + if((data.status_code>=200)&&(data.status_code<=299)){ + DynamicJsonDocument doc(10000); + deserializeJson(doc,data.reply); + volume_modifyable = doc["device"]["supports_volume"]; + } + return volume_modifyable; +} +#endif +char Spotify::convert_id_to_uri(char* id, char* type){ + char uri[45]; + sprintf(uri, "spotify:%s:%s", type, id); + return *uri; +} +char* Spotify::convert_id_to_uri(char* id, char* type,char * uri){ + sprintf(uri, "spotify:%s:%s", type, id); + return uri; +} +void print_response(response response_obj){ + Serial.printf("Status: %d\n", response_obj.status_code); + Serial.printf("Reply: %s\n", response_obj.reply); +} \ No newline at end of file diff --git a/src/SpotifyESP32.h b/src/SpotifyESP32.h new file mode 100644 index 0000000..ade62c8 --- /dev/null +++ b/src/SpotifyESP32.h @@ -0,0 +1,818 @@ +#ifndef SpotifyESP32 +#define SpotifyESP32 + +#define ENABLE_PLAYER +#define ENABLE_ALBUM +#define ENABLE_ARTIST +#define ENABLE_AUDIOBOOKS +#define ENABLE_CATEGORIES +#define ENABLE_CHAPTERS +#define ENABLE_EPISODES +#define ENABLE_GENRES +#define ENABLE_MARKETS +#define ENABLE_PLAYLISTS +#define ENABLE_SEARCH +#define ENABLE_SHOWS +#define ENABLE_TRACKS +#define ENABLE_USER +#define ENABLE_SIMPIFIED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + + + +namespace Spotify_types { + extern bool SHUFFLE_ON; + extern bool SHUFFLE_OFF; + extern char* REPEAT_OFF; + extern char* REPEAT_TRACK; + extern char* REPEAT_CONTEXT; + extern char* TYPE_ALBUM; + extern char* TYPE_ARTIST; + extern char* TYPE_TRACK; + extern char* TYPE_PLAYLIST; + extern char* TOP_TYPE_ARTIST; + extern char* TOP_TYPE_TRACKS; + extern char* GROUP_ALBUM; + extern char* GROUP_SINGLE; + extern char* GROUP_APPEARS_ON; + extern char* GROUP_COMPILATION; + extern char* TIME_RANGE_SHORT; + extern char* TIME_RANGE_MEDIUM; + extern char* TIME_RANGE_LONG; + extern int SIZE_OF_ID; + extern int SIZE_OF_URI; + +} + +/// @brief Response object containing http status code and reply +typedef struct{ + int status_code; + String reply; +} response; + +/// @brief Recommendation object, used to create recommendations +struct recommendations { + char** seed_artists; + int seed_artists_size = 0; + char** seed_genres; + int seed_genres_size = 0; + char** seed_tracks; + int seed_tracks_size = 0; + float min_acousticness = -1.0; + float max_acousticness = -1.0; + float target_acousticness = -1.0; + float min_danceability = -1.0; + float max_danceability = -1.0; + float target_danceability = -1.0; + float min_duration_ms = -1.0; + float max_duration_ms = -1.0; + float target_duration_ms = -1.0; + float min_energy = -1.0; + float max_energy = -1.0; + float target_energy = -1.0; + float min_instrumentalness = -1.0; + float max_instrumentalness = -1.0; + float target_instrumentalness = -1.0; + float min_key = -1.0; + float max_key = -1.0; + float target_key = -1.0; + float min_liveness = -1.0; + float max_liveness = -1.0; + float target_liveness = -1.0; + float min_loudness = -1.0; + float max_loudness = -1.0; + float target_loudness = -1.0; + float min_mode = -1.0; + float max_mode = -1.0; + float target_mode = -1.0; + float min_popularity = -1.0; + float max_popularity = -1.0; + float target_popularity = -1.0; + float min_speechiness = -1.0; + float max_speechiness = -1.0; + float target_speechiness = -1.0; + float min_tempo = -1.0; + float max_tempo = -1.0; + float target_tempo = -1.0; + float min_time_signature = -1.0; + float max_time_signature = -1.0; + float target_time_signature = -1.0; + float min_valence = -1.0; + float max_valence = -1.0; + float target_valence = -1.0; +}; + +/// @brief Print response object +/// @param response_obj Response object to print +void print_response(response response_obj); +class Spotify { + public: + ///@brief Constructor for Spotify object without refresh token + ///@param client_id Client id from Spotify(Required) + ///@param client_secret Client secret from Spotify(Required) + ///@param server_port Port for the server to run on(default 80, if 80 is already used use anything above 1024) + ///@param debug_on Debug mode on or off(default off) + ///@param max_num_retry Max number of retries for a request(default 3) + Spotify(const char* client_id,const char* client_secret, int server_port = 80, bool debug_on = false, int max_num_retry = 3); + ///@brief Constructor for Spotify object with refresh token + ///@param client_id Client id from Spotify(Required) + ///@param client_secret Client secret from Spotify(Required) + ///@param refresh_token Refresh token from Spotify(Required) if not authenticated use other constructor + ///@param debug_on Debug mode on or off(default off) + ///@param max_num_retry Max number of retries for a request(default 3) + Spotify(const char* client_id,const char* client_secret,const char* refresh_token, bool debug_on = false, int max_num_retry = 3); + /// @brief start the server and begin authentication + void begin(); + /// @brief handle client requests necessary for authentication + void handle_client(); + ///@brief Check if user is authenticated + ///@return true if user is authenticated + bool is_auth(); + #ifdef ENABLE_PLAYER + ///@brief Get information about the user's current playback state, including track, track progress, and active device. + ///@return response object containing http status code and reply + response currently_playing(); + ///@brief Start or resume playback. If no device_id is provided, the user's currently active device is the target. + ///@param context_uri Spotify URI of the context to play (Required) + ///@param offset Indicates from where in the context playback should start, Only works with albums or Playlists. NEEDS TO BE SET (0) IF ONLY URI IS PROVIDED + ///@param position_ms Indicates from what position in the context playback should start in milliseconds(Optional) + ///@param device_id Id of the device this command is targeting (Optional) + ///@return response object containing http status code and reply + response start_resume_playback(char* context_uri, int offset, int position_ms = 0, char* device_id = nullptr); + ///@brief Start or resume playback. If no device_id is provided, the user's currently active device is the target. + ///@param size Number of uris in uris array + ///@param uris Array of Spotify URIs of the tracks to play + ///@param device_id Id of the device this command is targeting (Optional) + ///@return response object containing http status code and reply + response start_resume_playback(int size, char ** uris ,char* device_id = nullptr); + ///@brief Start or resume playback. If no device_id is provided, the user's currently active device is the target. Targets the currently playing context. + ///@return response object containing http status code and reply + response start_resume_playback(char* device_id = nullptr); + ///@brief Pause playback on Spotify + ///@return response object containing http status code and reply + response pause_playback(); + ///@brief Skip to next track + ///@return response object containing http status code and reply + response skip(); + ///@brief Skip to previous track + ///@return response object containing http status code and reply + response previous(); + ///@brief get information about the user's available devices + ///@return response object containing http status code and reply + response available_devices(); + ///@brief get information about the user's current playback state, including track, track progress, and active device, shuffle etc. + ///@return response object containing http status code and reply + response current_playback_state(); + ///@brief Get recently played tracks + ///@param limit The maximum number of items to return. Default: 10. Minimum: 1. Maximum: 50 + response recently_played_tracks(int limit = 10); + ///@brief Seek to position of current context + ///@param time_ms Position in milliseconds to seek to, if the value is greater than the length of the track the player will skip to the next track + ///@return response object containing http status code and reply + response seek_to_position(int time_ms); + ///@brief get users queue, response can be empty or containing episode or track objects + ///@return response object containing http status code and reply + response get_queue(); + ///@Brief Set repeat mode, allowed values are REPEAT_OFF, REPEAT_TRACK, REPEAT_CONTEXT + ///@param mode Repeat mode + ///@return response object containing http status code and reply + response repeat_mode(char* mode); + ///@Brief Set shuffle mode, allowed values are SHUFFLE_ON, SHUFFLE_OFF + ///@param mode Shuffle mode + ///@return response object containing http status code and reply + response shuffle(bool mode); + ///@Brief Transfer playback to another device + ///@param device_id Id of the device this command is targeting + ///@return response object containing http status code and reply + response transfer_playback(char* device_id); + ///@Brief Set volume, does not work with all devices(eg. does not work on Phones) + ///@param value Volume value between 0 and 100 + ///@return response object containing http status code and reply + response set_volume(int value); + ///@Brief Add context to queue + ///@param context_uri Spotify URI of the context to add to queue + response add_to_queue(char* context_uri); + #endif + #ifdef ENABLE_ALBUM + ///@brief Get Spotify information for a single album. + ///@param album_id Spotify ID of the album + ///@return response object containing http status code and reply + response get_album(char* album_id); + ///@brief Get Spotify information for multiple albums identified by their Spotify IDs. + ///@param size Number of album ids in album_ids array + ///@param album_ids Array of Spotify IDs of the albums + ///@return response object containing http status code and reply + response get_albums(int size, char** album_ids); + ///@brief Get Spotify information about an album's tracks. + ///@param album_id Spotify ID of the album + ///@param limit The maximum number of tracks to return. Default: 10. Minimum: 1. Maximum: 50 + ///@param offset The index of the first track to return. Default: 0 (the first object). Use with limit to get the next set of tracks. + ///@return response object containing http status code and reply + response get_album_tracks(char* album_id, int limit = 10, int offset = 0); + ///@brief Get Albums saved to the current user's music library. + ///@param limit The maximum number of albums to return. Default: 10. Minimum: 1. Maximum: 50 + ///@param offset The index of the first album to return. Default: 0 (the first object). Use with limit to get the next set of albums. + ///@return response object containing http status code and reply + response get_users_saved_albums(int limit = 10, int offset = 0); + ///@brief Save one or more albums to the current user's music library. + ///@param size Number of album ids in album_ids array + ///@param album_ids Array of Spotify IDs of the albums + ///@return response object containing http status code and reply + response save_albums_for_current_user(int size, char** album_ids); + ///@brief Remove one or more albums from the current user's music library. + ///@param size Number of album ids in album_ids array + ///@param album_ids Array of Spotify IDs of the albums + ///@return response object containing http status code and reply + response remove_users_saved_albums(int size, char** album_ids); + ///@brief Check if one or more albums is already saved in the current Spotify user's music library. + ///@param size Number of album ids in album_ids array + ///@param album_ids Array of Spotify IDs of the albums + ///@return response object containing http status code and reply + response check_useres_saved_albums(int size, char** album_ids); + ///@brief Get a list of new album releases featured in Spotify + ///@param limit The maximum number of items to return. Default: 10. Minimum: 1. Maximum: 50 + ///@param offset The index of the first item to return. Default: 0 (the first object). Use with limit to get the next set of items. + ///@param country A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want the list of returned items to be relevant to a particular country. + ///@return response object containing http status code and reply + response get_new_releases(int limit = 10, int offset = 0, char* country = nullptr); + #endif + #ifdef ENABLE_ARTIST + ///@brief Get Spotify information for a single artist + ///@param artist_id Spotify ID of the artist + ///@return response object containing http status code and reply + response get_artist(char* artist_id); + ///@brief Get Spotify information for multiple artists + ///@param size Number of artist ids in artist_ids array + ///@param artist_ids Array of Spotify IDs of the artists + ///@return response object containing http status code and reply + response get_several_artists(int size, char** artist_ids); + ///@brief Get Spotify information about an artist's albums + ///@param artist_id Spotify ID of the artist + ///@param size_groups Number of groups in include_groups array + ///@param include_groups Array of groups to include in the response. Valid values are GROUP_ALBUM, GROUP_SINGLE, GROUP_APPEARS_ON, GROUP_COMPILATION or any combination of these. + ///@param limit The maximum number of items to return. Default: 10. Minimum: 1. Maximum: 50 + ///@param offset The index of the first album to return. Default: 0 (the first object). Use with limit to get the next set of albums. + ///@return response object containing http status code and reply + response get_artist_albums(char* artist_id,int size_groups, char** include_groups, int limit = 10, int offset = 0); + ///@brief Get Spotify information about an artist's top tracks + ///@param artist_id Spotify ID of the artist + ///@param country An ISO 3166-1 alpha-2 country code or the string from_token. Provide this parameter if you want the list of returned items to be relevant to a particular country. + ///@return response object containing http status code and reply + response get_artist_top_tracks(char* artist_id, char* country = nullptr); + ///@brief Get Spotify information about artists related to a single artist + ///@param artist_id Spotify ID of the artist + ///@return response object containing http status code and reply + response get_artist_related_artist(char* artist_id); + #endif + #ifdef ENABLE_AUDIOBOOKS + ///@brief Get Spotify information for a single audiobook(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + ///@param audiobook_id Spotify ID of the audiobook + ///@return response object containing http status code and reply + response get_audiobook(char* audiobook_id); + ///@brief Get Spotify information for multiple audiobooks(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + ///@param size Number of audiobook ids in audiobook_ids array + ///@param audiobook_ids Array of Spotify IDs of the audiobooks + ///@return response object containing http status code and reply + response get_several_audiobooks(int size, char** audiobook_ids); + ///@brief Get Spotify information about an audiobook's chapters(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + ///@param audiobook_id Spotify ID of the audiobook + ///@param limit The maximum number of items to return. Default: 10. Minimum: 1. Maximum: 50 + ///@param offset The index of the first chapter to return. Default: 0 (the first object). Use with limit to get the next set of chapters. + ///@return response object containing http status code and reply + response get_audiobook_chapters(char* audiobook_id, int limit = 10, int offset = 0); + /// @brief Get users saved audiobooks(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_users_audiobooks(int limit = 10, int offset = 0); + /// @brief Save one or more audiobooks to the current user's music library(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + /// @param size Number of audiobook ids in audiobook_ids array + /// @param audiobook_ids Array of Spotify IDs of the audiobooks + /// @return response object containing http status code and reply + response save_audiobooks_for_current_user(int size, char** audiobook_ids); + /// @brief Remove one or more audiobooks from the current user's music library(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + /// @param size Number of audiobook ids in audiobook_ids array + /// @param audiobook_ids Array of Spotify IDs of the audiobooks + /// @return response object containing http status code and reply + response remove_audiobooks_for_current_user(int size, char** audiobook_ids); + /// @brief Check if one or more audiobooks is already saved in the current Spotify user's music library(Only Available in US, UK, Canada, Ireland, New Zealand and Australia) + /// @param size Number of audiobook ids in audiobook_ids array + /// @param audiobook_ids Array of Spotify IDs of the audiobooks + /// @return response object containing http status code and reply + response check_users_saved_audiobooks(int size, char** audiobook_ids); + #endif + #ifdef ENABLE_CATEGORIES + ///@brief Get a list of categories used to tag items in Spotify + ///@param limit The maximum number of items to return + ///@param offset The index of the first item to return + ///@param country An ISO 3166-1 alpha-2 country code, if ommited the returned items will not be country-specific + ///@param locale The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore, if ommited the response defaults to American English + ///@return response object containing http status code and reply + response get_several_browse_categories(int limit = 10, int offset = 0, char* country = nullptr, char* locale = nullptr); + ///@brief Get a single category used to tag items in Spotify + ///@param category_id Spotify category ID of the category + ///@param country An ISO 3166-1 alpha-2 country code, if ommited the returned items will not be country-specific + ///@param locale The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore, if ommited the response defaults to American English + ///@return response object containing http status code and reply + response get_single_browse_category(char* category_id, char* country = nullptr, char* locale = nullptr); + #endif + #ifdef ENABLE_CHAPTERS + ///@brief Get Spotify information for a single chapter, Only Available in US, UK, Canada, Ireland, New Zealand and Australia + ///@param chapter_id Spotify ID of the chapter + ///@return response object containing http status code and reply + response get_chapter(char* chapter_id); + ///@brief Get Spotify information for multiple chapters, Only Available in US, UK, Canada, Ireland, New Zealand and Australia + ///@param size Number of chapter ids in chapter_ids array + ///@param chapter_ids Array of Spotify IDs of the chapters + ///@return response object containing http status code and reply + response get_several_chapters(int size, char** chapter_ids); + #endif + #ifdef ENABLE_EPISODES + ///@brief Get Spotify information for a single episode + ///@param episode_id Spotify ID of the episode + ///@return response object containing http status code and reply + response get_episode(char* episode_id); + ///@brief Get Spotify information for multiple episodes + ///@param size Number of episode ids in episode_ids array + ///@param episode_ids Array of Spotify IDs of the episodes + ///@return response object containing http status code and reply + response get_several_episodes(int size, char** episode_ids); + ///@brief Get users saved episodes + ///@param limit The maximum number of items to return, + ///@param offset The index of the first item to return + ///@return response object containing http status code and reply + response get_users_saved_episodes(int limit = 10, int offset = 0); + ///@brief Save one or more episodes to the current user's music library + ///@param size Number of episode ids in episode_ids array + ///@param episode_ids Array of Spotify IDs of the episodes + ///@return response object containing http status code and reply + response save_episodes_for_current_user(int size, char** episode_ids); + ///@brief Remove one or more episodes from the current user's music library + ///@param size Number of episode ids in episode_ids array + ///@param episode_ids Array of Spotify IDs of the episodes + ///@return response object containing http status code and reply + response remove_episodes_for_current_user(int size, char** episode_ids); + ///@brief Check if one or more episodes is already saved in the current Spotify user's music library + ///@param size Number of episode ids in episode_ids array + ///@param episode_ids Array of Spotify IDs of the episodes + ///@return response object containing http status code and reply + response check_users_saved_episodes(int size, char** episode_ids); + #endif + #ifdef ENABLE_GENRES + ///@brief Get a list of available genre seeds for recommendations + ///@return response object containing http status code and reply + response get_available_genre_seeds(); + #endif + #ifdef ENABLE_MARKETS + ///@brief Get a list of available markets for recommendations + ///@return response object containing http status code and reply + response get_available_markets(); + #endif + #ifdef ENABLE_PLAYLISTS + ///@brief Get Spotify information for a single playlist + ///@param playlist_id Spotify ID of the playlist + ///@param size Number of fields in fields array + ///@param fields Array of fields to return, leave empty to return all fields + ///@param additional_types A comma-separated list of item types that your client supports besides the default track type. Valid types are: track and episode. + ///@return response object containing http status code and reply + response get_playlist(char* playlist_id,int size = 0, char** fields = nullptr,int size_of_additional_types = 0, char ** additional_types = nullptr); + /// @brief Change details of a playlist + /// @param playlist_id ID of the playlist + /// @param name Set the name of the playlist + /// @param is_public Set the playlist to public or not + /// @param is_collaborative Set the playlist to collaborative or not + /// @param description Set the description of the playlist + /// @return response object containing http status code and reply + response change_playlist_details(char* playlist_id, char* name, bool is_public, bool is_collaborative, char* description); + /// @brief Get Items of a playlist + /// @param playlist_id Id of the playlist + /// @param fields Filters for the query: a comma-separated list of the fields to return. If omitted, all fields are returned. For example, to get just the total number of items and the request limit: + /// @param limit Number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_playlist_items(char* playlist_id, char* fields, int limit = 10, int offset = 0); + /// @brief Either reorder or replace items in a playlist depending on the request's parameters + /// @param playlist_id Id of the playlist + /// @param size Size of uris array + /// @param uris Array of Spotify URIs this will overwrite all existing items in the playlist, If items should only be reordered pass nullptr + /// @param range_length The position of the first item to be reordered. + /// @param range_start The position where the items should be inserted. + /// @param insert_before The amount of items to be reordered. + /// @return response object containing http status code and reply + response update_playlist_items(char* playlist_id, int size, char** uris = nullptr, int range_length = 1, int range_start = 0, int insert_before = 1 ); + /// @brief Add items to a playlist + /// @param playlist_id Id of the playlist + /// @param size Size of uris array + /// @param uris Array of Spotify URIs of the items to add + /// @param position The position to insert the items, a zero-based index + /// @return response object containing http status code and reply + response add_items_to_playlist(char* playlist_id, int size, char** uris, int position = 0); + /// @brief Remove items from a playlist + /// @param playlist_id Id of the playlist + /// @param size Size of uris array + /// @param uris Array of Spotify URIs of the items to remove + /// @return response object containing http status code and reply + response remove_playlist_items(char* playlist_id, int size, char** uris); + /// @brief Get a list of users playlists + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_current_users_playlists(int limit = 10, int offset = 0); + /// @brief get a users playlist + /// @param user_id Id of the user + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_user_playlists(char* user_id,int limit = 10, int offset = 0); + /// @brief Create a playlist + /// @param user_id Id of the user + /// @param name Name of the playlist + /// @param is_public Set the playlist to public or not + /// @param is_collaborative Set the playlist to collaborative or not + /// @param description Description of the playlist + /// @return response object containing http status code and reply + response create_playlist(char* user_id, char* name, bool is_public, bool is_collaborative, char* description); + /// @brief Get a list of Spotify featured playlists + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @param timestamp A timestamp in ISO 8601 format: yyyy-MM-ddTHH:mm:ss if ommited current utc time is used + /// @param country An ISO 3166-1 alpha-2 country code, Provide this to ensure that the category exists for a particular country. + /// @param locale The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore, if ommited the response defaults to American English + /// @return response object containing http status code and reply + response get_featured_playlists( int limit = 10, int offset = 0, char* timestamp = nullptr, char* country = nullptr, char* locale = nullptr); + /// @brief Get a list of Spotify playlists tagged with a particular category. + /// @param category_id Category ID can be got from get_several_browse_categories + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @param country The country: an ISO 3166-1 alpha-2 country code, Provide this to ensure that the category exists for a particular country. + /// @return response object containing http status code and reply + response get_category_playlists(char* category_id, int limit = 10, int offset = 0, char* country = nullptr); + /// @brief Get a cover image of a playlist + /// @param playlist_id Id of the playlist + /// @return response object containing http status code and reply + response get_playlist_cover_image(char* playlist_id); + /// @brief Upload a custom cover image of a playlist + /// @param playlist_id Id of the playlist + /// @param data Image data + /// @param market An ISO 3166-1 alpha-2 country code, Provide this parameter if you want to apply Track Relinking + /// @return response object containing http status code and reply + response add_custom_playlist_cover_image(char* playlist_id, char* data); + #endif + #ifdef ENABLE_SEARCH + /// @brief Search for an item + /// @param q Search query keywords and optional field filters and operators + /// @param type_size Number of item types in type array,needs to e set to 0 if limit, offset or market is used and type is not used + /// @param type A comma-separated list of item types to search across, needs to be set to nullptr if limit, offset or market is used and type is not used + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @param market An ISO 3166-1 alpha-2 country code or the string from_token, Provide this parameter if you want to apply Track Relinking + /// @return response object containing http status code and reply + response search(char* q,int type_size = 0, char** type = nullptr, int limit = 10, int offset = 0, char* market = nullptr); + #endif + #ifdef ENABLE_SHOWS + /// @brief Get Spotify information for a single show + /// @param show_id Spotify ID of the show + /// @return response object containing http status code and reply + response get_show(char* show_id); + /// @brief Get Spotify information for multiple shows + /// @param size Number of show ids in show_ids array + /// @param show_ids Array of Spotify IDs of the shows + /// @return response object containing http status code and reply + response get_several_shows(int size, char** show_ids); + /// @brief Get Spotify information about a show's episodes + /// @param show_id Spotify ID of the show + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_show_episodes(char* show_id, int limit = 10, int offset = 0); + /// @brief Get users saved shows + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_users_saved_shows(int limit = 10, int offset = 0); + /// @brief Save one or more shows to the current user's music library + /// @param size Number of show ids in show_ids array + /// @param show_ids Array of Spotify IDs of the shows + /// @return response object containing http status code and reply + response save_shows_for_current_user(int size, char** show_ids); + /// @brief Remove one or more shows from the current user's music library + /// @param size Number of show ids in show_ids array + /// @param show_ids Array of Spotify IDs of the shows + /// @return response object containing http status code and reply + response remove_shows_for_current_user(int size, char** show_ids); + /// @brief Check if one or more shows is already saved in the current Spotify user's music library + /// @param size Number of show ids in show_ids array + /// @param show_ids Array of Spotify IDs of the shows + /// @return response object containing http status code and reply + response check_users_saved_shows(int size, char** show_ids); + #endif + #ifdef ENABLE_TRACKS + /// @brief Get Spotify information for a single track + /// @param track_id Spotify ID of the track + /// @return response object containing http status code and reply + response get_track(char* track_id); + /// @brief Get Spotify information for multiple tracks + /// @param size Number of track ids in track_ids array + /// @param track_ids Array of Spotify IDs of the tracks + /// @return response object containing http status code and reply + response get_several_tracks(int size, char** track_ids); + /// @brief Get users saved tracks + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_user_saved_tracks(int limit = 10, int offset = 0); + /// @brief Save one or more tracks to the current user's music library + /// @param size Number of track ids in track_ids array + /// @param track_ids Array of Spotify IDs of the tracks + /// @return response object containing http status code and reply + response save_tracks_for_current_user(int size, char** track_ids); + /// @brief Remove one or more tracks from the current user's music library + /// @param size Number of track ids in track_ids array + /// @param track_ids Array of Spotify IDs of the tracks + /// @return response object containing http status code and reply + response remove_user_saved_tracks(int size, char** track_ids); + /// @brief Check if one or more tracks is already saved in the current Spotify user's music library + /// @param size Number of track ids in track_ids array + /// @param track_ids Array of Spotify IDs of the tracks + /// @return response object containing http status code and reply + response check_user_saved_tracks(int size, char** track_ids); + /// @brief Get audio features for multiple tracks + /// @param size Number of track ids in track_ids array + /// @param track_ids Array of Spotify IDs of the tracks + /// @return response object containing http status code and reply + response get_tracks_audio_features(int size, char** track_ids); + /// @brief Get audio features for a single track + /// @param track_id Spotify ID of the track + /// @return response object containing http status code and reply + response get_track_audio_features(char* track_id); + /// @brief Get audio analysis for a single track + /// @param track_id Spotify ID of the track + /// @return response object containing http status code and reply + response get_track_audio_analysis(char* track_id); + /// @brief Get a list of new album releases featured in Spotify + /// @param recom Recommendation object containing atleast one seed + /// @param limit The maximum number of items to return + /// @return response object containing http status code and reply + response get_recommendations(recommendations& recom, int limit = 10); + #endif + #ifdef ENABLE_USER + /// @brief Get detailed profile information about the current user (including the current user's username) + /// @return response object containing http status code and reply + response get_current_user_profile(); + /// @brief Get users top items + /// @param type The type of item to get, Valid values are: artists or tracks + /// @param time_range Over what time frame the affinities are computed, Valid values are: long_term, medium_term, short_term + /// @param limit The maximum number of items to return + /// @param offset The index of the first item to return + /// @return response object containing http status code and reply + response get_user_top_items(char* type, char* time_range = "medium_term", int limit = 10, int offset = 0); + /// @brief Get a users profile + /// @param user_id Id of the user + /// @return response object containing http status code and reply + response get_user_profile(char* user_id); + /// @brief Follow a playlist + /// @param playlist_id The Id of the playlist + /// @param is_public If true the playlist will be included in user's public playlists, if false it will remain private. + /// @return response object containing http status code and reply + response follow_playlist(char* playlist_id, bool is_public); + /// @brief Unfollow a playlist + /// @param playlist_id The Id of the playlist + /// @return response object containing http status code and reply + response unfollow_playlist(char* playlist_id); + /// @brief get users followed artists + /// @param after The last artist ID retrieved from the previous request + /// @param type The ID type, currently only artist is supported + /// @param limit The maximum number of items to return + /// @return response object containing http status code and reply + response get_followed_artists(char* after, char* type = "artist", int limit = 10); + /// @brief Follow artists or users + /// @param type The ID type, artist or user + /// @param size Number of artist or user ids in artist_user_ids array + /// @param artist_user_ids Array of Spotify IDs of the artists or users + /// @return response object containing http status code and reply + response follow_artists_or_users(char* type, int size, char** artist_user_ids); + /// @brief Unfollow artists or users + /// @param type The ID type, artist or user + /// @param size Number of artist or user ids in artist_user_ids array + /// @param artist_user_ids Array of Spotify IDs of the artists or users + /// @return response object containing http status code and reply + response unfollow_artists_or_users(char* type, int size, char** artist_user_ids); + /// @brief Check if users follow artists or users + /// @param type The ID type, artist or user + /// @param size Number of artist or user ids in artist_user_ids array + /// @param artist_user_ids Array of Spotify IDs of the artists or users + /// @return response object containing http status code and reply + response check_if_user_follows_artists_or_users(char* type, int size, char** artist_user_ids); + /// @brief Check if users follow a playlist + /// @param playlist_id The ID of the playlist + /// @param size Number of user ids in user_ids array + /// @param user_ids Array of Spotify IDs of the users + /// @return response object containing http status code and reply + response check_if_users_follow_playlist(char* playlist_id, int size, char** user_ids); + #endif + #ifdef ENABLE_SIMPIFIED + /// @brief Get Current track name + /// @return Current track name as String + String current_track_name(); + /// @brief Get Current track id + /// @return Current track id as String + String current_track_id(); + /// @brief Get Current device id + /// @return Current device id as String + String current_device_id(); + /// @brief Get Current artist names + /// @return Current artist names as String + String current_artist_names(); + /// @brief Get Current device id + /// @param char array to store device id + /// @return Current device id as pointer to char array + char* current_device_id(char* device_id); + /// @brief Get Current track id + /// @param char array to store track id + /// @return Current track id as pointer to char array + char* current_track_id(char* track_id); + /// @brief Get Current track name + /// @param char array to store track name + /// @return Current track name as pointer to char array + char* current_track_name(char* track_name); + /// @brief Get Current artist names + /// @param char array to store artist names + /// @return Current artist names as pointer to char array + char* current_artist_names(char* artist_names); + /// @brief Get if device is playing + /// @return true if device is playing + bool is_playing(); + /// @brief Get if it is possible to modify volume on current device + /// @return true if it is possible to modify volume on current device + bool volume_modifyable(); + #endif + /// @brief Convert ID to URI + /// @param id ID to convert + /// @param type Type of ID + /// @return URI as char + char convert_id_to_uri(char* id, char* type); + /// @brief Convert ID to URI + /// @param id ID to convert + /// @param type Type of ID + /// @param uri char array to store URI + /// @return URI as pointer to char array + char* convert_id_to_uri(char* id, char* type, char* uri); + + private: + WebServer _server; + /// @brief Maximum number of items in one request + static const int _max_num_items = 20; + /// @brief Maximum size of char array(35 been the size of a uri + comma + 150 as buffer for url etc.) + static const int _max_char_size = 35*_max_num_items + 150; + /// @brief Size of a uri + static const int _size_of_uri = 45; + /// @brief Size of an id + static const int _size_of_id = 25; + /// @brief Maximum number of items in one request + int _max_num_retry = 3; + /// @brief Users set redirect uri + char _redirect_uri[100] = ""; + /// @brief Users refresh token + char _refresh_token[300] = ""; + /// @brief user auth code + char _auth_code[800] = ""; + /// @brief Users set client id + const char* _client_id; + /// @brief Users set client secret + const char* _client_secret; + /// @brief Current number of retries + int _retry; + /// @brief Debug mode + bool _debug_on; + /// @brief port + int _port; + /// @brief Access token + String _access_token; + /// @brief Root login Page + void server_on_root(); + /// @brief Get refresh token from auth code + bool get_refresh_token(); + /// @brief Currying function for callback_login_page + friend std::function callback_fn(Spotify *spotify); + /// @brief Get Access Token with refresh token + /// @return Bool if token was successfully retrieved + bool get_token(); + /// @brief Initialize response object + /// @param response_obj Response object to initialize + void init_response(response* response_obj); + /// @brief Make PUT request to Spotify API + /// @param rest_url URL to make request to + /// @param payload_size Size of payload + /// @param payload Payload to send + /// @return Response object containing http status code and reply + response RestApiPut(char* rest_url, int payload_size = 0, char* payload = nullptr); + /// @brief Make POST request to Spotify API + /// @param rest_url URL to make request to + /// @param payload_size Size of payload + /// @param payload Payload to send + /// @return Response object containing http status code and reply + response RestApiPost(char* rest_url, int payload_size = 0, char* payload = nullptr); + /// @brief Make DELETE request to Spotify API + /// @param rest_url URL to make request to + /// @param payload Payload to send + /// @return Response object containing http status code and reply + response RestApiDelete(char* rest_url, char* payload = nullptr); + /// @brief Make GET request to Spotify API + /// @param rest_url URL to make request to + /// @return Response object containing http status code and reply + response RestApiGet(char* rest_url); + /// @brief Convert array of chars to one char array, seperated by comma + /// @param size Size of array + /// @param array Array to convert + /// @param result Array to store result + /// @return Pointer to result array + char* array_to_char(int size, char** array, char* result); + /// @brief Convert array of chars to one json array + /// @param size Size of array + /// @param array Array to convert + /// @param data Array to store result + /// @param data_size Size of data array + /// @return Pointer to data array + void array_to_json_array(int size, char** array, char* data, int data_size = _max_char_size);//Convert array of chars to one json array + #ifdef ENABLE_TRACKS + /// @brief Check if recommendation value is valid + /// @param param Float value to check + /// @return Bool if value is valid + bool is_valid_value(float param); + /// @brief Check if recommendation value is valid + /// @param param Int value to check + /// @return Bool if value is valid + bool is_valid_value(int param); + /// @brief Populate recommendation char values + /// @param map Map to populate + /// @param recom recommendation object + /// @return Void + void populate_char_values(std::map& map, recommendations& recom); + /// @brief Populate recommendation float values + /// @param map Map to populate + /// @param recom recommendation object + /// @return Void + void populate_float_values(std::map& map, recommendations& recom); + #endif + /// @brief Extract endpoint from url with regex + const char * extract_endpoint(const char* rest_url); + /// @brief Root CA for Spotify API + const char* _spotify_root_ca PROGMEM= \ + "-----BEGIN CERTIFICATE-----\n"\ + "MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh\n"\ + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"\ + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"\ + "MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\n"\ + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh\n"\ + "bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD\n"\ + "ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV\n"\ + "cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy\n"\ + "FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc\n"\ + "3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8\n"\ + "osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT\n"\ + "zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud\n"\ + "EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G\n"\ + "A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd\n"\ + "BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG\n"\ + "CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG\n"\ + "NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH\n"\ + "Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t\n"\ + "L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC\n"\ + "ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG\n"\ + "9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t\n"\ + "wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS\n"\ + "slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R\n"\ + "bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4\n"\ + "chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN\n"\ + "JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==\n"\ + "-----END CERTIFICATE-----\n"; + + const char* _login_page PROGMEM = R"=====( + + + ESP Spotify Login + + +
+

Spotify Login

+ Log in to spotify +
+ + + )====="; +}; +#endif \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..7f8e62e --- /dev/null +++ b/src/config.h @@ -0,0 +1,6 @@ +const char* ssid = "YOUR WIFI SSID"; +const char* password = "YOUR WIFI PASSWORD"; +const char* client_id = "YOUR CLIENT ID FROM THE SPOTIFY DASHBOARD"; +const char* client_secret = "YOUR CLIENT SECRET FROM THE SPOTIFY DASHBOARD"; +const char* refresh_token = "YOUR REFRESH TOKEN IF YOU ALREADY HAVE ONE"; +