diff --git a/Spotify_v3/.gitignore b/Spotify_v3/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/Spotify_v3/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/Spotify_v3/.vscode/extensions.json b/Spotify_v3/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/Spotify_v3/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/Spotify_v3/include/README b/Spotify_v3/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/Spotify_v3/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Spotify_v3/lib/README b/Spotify_v3/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/Spotify_v3/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Spotify_v3/platformio.ini b/Spotify_v3/platformio.ini new file mode 100644 index 0000000..31474ca --- /dev/null +++ b/Spotify_v3/platformio.ini @@ -0,0 +1,21 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32doit-devkit-v1] +platform = espressif32 +board = esp32doit-devkit-v1 +framework = arduino +lib_deps = + plageoj/UrlEncode@^1.0.1 + bblanchon/ArduinoJson@^7.0.3 + esphome/AsyncTCP-esphome@^2.1.2 +monitor_filters = esp32_exception_decoder +monitor_speed = 115200 +build_flags = -w \ No newline at end of file diff --git a/Spotify_v3/src/SpotifyESP32.cpp b/Spotify_v3/src/SpotifyESP32.cpp new file mode 100644 index 0000000..1b1f8d1 --- /dev/null +++ b/Spotify_v3/src/SpotifyESP32.cpp @@ -0,0 +1,1416 @@ +#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; + strncpy(_refresh_token, refresh_token,sizeof(_refresh_token)); + _debug_on = debug_on; + if(max_num_retry>0){ + _max_num_retry = max_num_retry; + } + else{ + _max_num_retry = 1; + } +} + +//Login Webserver Functions +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(); + JsonDocument 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]; + snprintf(message,sizeof(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){ + snprintf(_redirect_uri,sizeof(_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{ + snprintf(_redirect_uri,sizeof(_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::RestApi(char* rest_url, char* type, 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); + if(payload_size>0){ + http.addHeader("content-Length", String(payload_size)); + } + int http_code; + if(strcmp(type, "GET") == 0){ + http_code = http.GET(); + } + else if(strcmp(type, "PUT") == 0){ + http_code = http.PUT(payload); + } + else if(strcmp(type, "POST") == 0){ + http_code = http.POST(payload); + } + else if(strcmp(type, "DELETE") == 0){ + http_code = http.sendRequest("DELETE", payload); + } + response_obj.status_code = http_code; + String reply = ""; + JsonDocument doc; + + if(http.getSize()>0){ + reply = http.getString(); + deserializeJson(doc, reply); + } + + if(_debug_on){ + const char* endpoint = extract_endpoint(rest_url); + Serial.printf("%s \"%s\" Status: %i\n", type, endpoint, http_code); + Serial.printf("Reply: %s\n", reply.c_str()); + } + if ((http_code >= 200)&&(http_code <= 299)){ + response_obj.reply = reply; + } + 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 = RestApi(rest_url, type, payload_size, payload); + } + } + else{ + response_obj.reply = message; + } + } + http.end(); + _retry = 0; + + return response_obj; +} +response Spotify::RestApiPut(char* rest_url,int payload_size, char* payload){ + return RestApi(rest_url, "PUT", payload_size, payload); +} +response Spotify::RestApiGet(char* rest_url){ + return RestApi(rest_url, "GET"); +} +response Spotify::RestApiPost(char* rest_url,int payload_size, char* payload){ + return RestApi(rest_url, "POST", payload_size, payload); +} +response Spotify::RestApiDelete(char* rest_url, char* payload){ + return RestApi(rest_url, "DELETE", 0, payload); +} +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(); + JsonDocument 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){ + JsonDocument doc; + 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]; + snprintf(value, sizeof(value), "&%s%s",param.first, param.second); + strcat(url, value); + } + for(const auto& param : float_params){ + char value[100]; + snprintf(value, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(url), "https://api.spotify.com/v1/playlists/%s/followers", playlist_id); + char payload[100]; + int payload_size = 0; + snprintf(payload,sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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]; + snprintf(url, sizeof(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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + deserializeJson(doc,data.reply); + JsonArray devices = doc["devices"].as(); + for (JsonVariant device : devices) { + JsonObject deviceObj = device.as(); + if (deviceObj["is_active"].as()) { + snprintf(device_id,sizeof(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)){ + JsonDocument doc; + deserializeJson(doc,data.reply); + snprintf(track_name,sizeof(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)){ + JsonDocument doc; + deserializeJson(doc,data.reply); + snprintf(track_id,sizeof(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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + 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)){ + JsonDocument doc; + 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[_size_of_uri]; + snprintf(uri,_size_of_uri, "spotify:%s:%s", type, id); + return *uri; +} +char* Spotify::convert_id_to_uri(char* id, char* type,char * uri){ + snprintf(uri,sizeof(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.c_str()); +} \ No newline at end of file diff --git a/Spotify_v3/src/SpotifyESP32.h b/Spotify_v3/src/SpotifyESP32.h new file mode 100644 index 0000000..6064f4d --- /dev/null +++ b/Spotify_v3/src/SpotifyESP32.h @@ -0,0 +1,824 @@ +#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[200] = ""; + /// @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 request to Spotify API + /// @param rest_url URL to make request to + /// @param type Type of request + /// @param payload_size Size of payload + /// @param payload Payload to send + response RestApi(char* rest_url, char* type, int payload_size = 0, char* payload = nullptr); + /// @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/Spotify_v3/src/config.h b/Spotify_v3/src/config.h new file mode 100644 index 0000000..416a09d --- /dev/null +++ b/Spotify_v3/src/config.h @@ -0,0 +1,6 @@ +const char* SSID = "Rebweg10D"; +const char* PASSWORD = "Bitte_eintreten"; +const char* CLIENT_ID = "4a60dc25e5eb43e29161a0b00d296ff7"; +const char* CLIENT_SECRET = "d65a695928b34a4ea3a0e66d9120b911"; +const char* REFRESH_TOKEN = "AQC_3lM_jUt1Zgag5UO4qtOZcqCHSprkjsq6tvamaH2nXjn5xSyU0yuFXpi1M1YTNPyHEaBHpe1nWTOA_PkalojPGY8XfxS0kxFjFaZxbE2B4sQvVvSfgi6JIZrwYIwWYuo"; +int PORT = 8000; \ No newline at end of file diff --git a/Spotify_v3/src/main.cpp b/Spotify_v3/src/main.cpp new file mode 100644 index 0000000..2567778 --- /dev/null +++ b/Spotify_v3/src/main.cpp @@ -0,0 +1,34 @@ +#include +#include +#include "SpotifyESP32.h" +#include "config.h" +using namespace Spotify_types; + +void connect_to_wifi(); + + +Spotify sp(CLIENT_ID, CLIENT_SECRET,REFRESH_TOKEN); + +void setup() { + Serial.begin(115200); + connect_to_wifi(); + sp.begin(); + while(!sp.is_auth()){ + sp.handle_client(); + } + Serial.println("Authenticated"); + Serial.println(sp.current_artist_names()); +} + +void loop() { + // put your main code here, to run repeatedly: +} +void connect_to_wifi(){ + WiFi.begin(SSID, PASSWORD); + Serial.print("Connecting to WiFi..."); + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + Serial.print("."); + } + Serial.printf("\nConnected to WiFi\n"); +} diff --git a/Spotify_v3/test/README b/Spotify_v3/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/Spotify_v3/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html