#include "ehs/io/socket/rest/Spotify.h" #include "ehs/io/socket/DNS_LNX.h" #include "ehs/system/System.h" #include "ehs/URI.h" namespace ehs { const Str_8 Spotify::trackUriPrefix = "https://open.spotify.com/track/"; Spotify::~Spotify() { client.Release(); } Spotify::Spotify() : forceVerify(false) { } Spotify::Spotify(Str_8 clientId, Str_8 secret, Str_8 redURI, Array scopes, const bool forceVerify) : client(AddrType::IPV4), clientId((Str_8 &&)clientId), secret((Str_8 &&)secret), redURI((Str_8 &&)redURI), scopes((Array &&)scopes), forceVerify(forceVerify) { } bool Spotify::Authorize() { Str_8 scopesFinal; for (UInt_64 i = 0; i < scopes.Size(); ++i) { scopesFinal += scopes[i]; if (i < scopes.Size() - 1) scopesFinal += "%20"; } Str_8 rURI = URI::Encode(redURI); Str_8 uri = "https://accounts.spotify.com/authorize?client_id=" + clientId + "&redirect_uri=" + rURI + "&response_type=code&show_dialog=" + (forceVerify ? "true" : "false") + "&scope=" + scopesFinal; TCP server(AddrType::IPV4); server.Initialize(); server.Bind("", 65534); server.Listen(); System::OpenURI(uri); TCP* cbClient = server.Accept(); Request cbReq = cbClient->RecvReq(); if (cbReq.GetResource() != "/callback") { Response resp(423, "Event Horizon"); resp.SetContentType(ContentType::TEXT_HTML); resp.SetBody("LWE ResponseHostile Information Received"); cbClient->SendRes(resp); cbClient->Release(); return false; } Response resp(200, "Event Horizon"); resp.SetContentType(ContentType::TEXT_HTML); resp.SetBody("LWE ResponseAuthentication Successful"); cbClient->SendRes(resp); cbClient->Release(); server.Release(); SSL accounts(AddrType::IPV4); accounts.Initialize(); accounts.Connect("accounts.spotify.com", SSL::HTTPS_Port); Request authReq(Verb::POST, "/api/token"); authReq.SetContentType(ContentType::APP_FORMURLENCODED); authReq.BasicAuth(clientId, secret); authReq.AddToBody("grant_type", "authorization_code"); authReq.AddToBody("code", cbReq.GetQuery("code")); authReq.AddToBody("redirect_uri", rURI); accounts.SendReq(authReq); Response authRes = accounts.RecvRes(); accounts.Release(); if (authRes.GetCode() == 400) { EHS_LOG_INT(LogType::ERR, 0, "Could not authorize with Spotify because the client id was invalid."); return false; } else if (authRes.GetCode() == 403) { EHS_LOG_INT(LogType::ERR, 1, "Could not authorize with Spotify because the secret was invalid."); return false; } else if (authRes.GetCode() != 200) { EHS_LOG_INT(LogType::ERR, 2, "Could not authorize with Spotify with code " + Str_8::FromNum(authRes.GetCode()) + "."); return false; } Json authResJson = authRes.GetJson(); JsonObj* value = (JsonObj*)authResJson.GetValue(); if (!value) return false; JsonVar* tokenVar = value->GetVar("access_token"); if (!tokenVar) return false; JsonVar* rTokenVar = value->GetVar("refresh_token"); if (!rTokenVar) return false; token = ((JsonStr*)tokenVar->GetValue())->value; rToken = ((JsonStr*)rTokenVar->GetValue())->value; return true; } UInt_32 Spotify::SetVolume(const UInt_8 level) { StartConnection(); Request req(Verb::PUT, "/v1/me/player/volume"); req.AddQuery("volume_percent", Str_8::FromNum(level)); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::Play() { StartConnection(); Request req(Verb::PUT, "/v1/me/player/play"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::Pause() { StartConnection(); Request req(Verb::PUT, "/v1/me/player/pause"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::SetRepeat(const SpotifyState state) { StartConnection(); Str_8 result; switch (state) { case SpotifyState::TRACK: result = "track"; break; case SpotifyState::CONTEXT: result = "context"; break; case SpotifyState::OFF: result = "off"; break; } Request req(Verb::PUT, "/v1/me/player/repeat"); req.AddQuery("state", result); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::SetShuffle(const bool state) { StartConnection(); Request req(Verb::PUT, "/v1/me/player/repeat"); req.AddQuery("state", state ? "true" : "false"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::SearchTrack(Vector& artists, Str_8& id, Str_8& name) { StartConnection(); Request req(Verb::GET, "/v1/search"); Str_8 q = "artist%3A"; for (UInt_64 i = 0; i < artists.Size(); ++i) { q += artists[i].ReplaceAll(" ", "+"); if (i != artists.Size() - 1) q += "%2C+"; } q += "+track%3A" + name.ReplaceAll(" ", "+"); req.AddQuery("q", q); req.AddQuery("type", "track"); req.AddQuery("limit", "1"); req.AddQuery("offset", "0"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return SearchTrack(artists, name, id); } Json body = res.GetJson(); JsonNum* total = (JsonNum*)body.RetrieveValue("tracks.total"); if (!total || total->value == 0.0f) return 0; JsonObj* item = (JsonObj*)body.RetrieveValue("tracks.items[0]"); if (!item) return 0; JsonVar* artistsVar = item->GetVar("artists"); if (!artistsVar) return 0; JsonArray* artistsArray = (JsonArray*)artistsVar->GetValue(); if (!artistsArray) return 0; JsonVar* trackIdVar = item->GetVar("id"); if (!trackIdVar) return 0; JsonStr* trackIdValue = (JsonStr*)trackIdVar->GetValue(); if (!trackIdValue) return 0; JsonVar* trackNameVar = item->GetVar("name"); if (!trackNameVar) return 0; JsonStr* trackNameValue = (JsonStr*)trackNameVar->GetValue(); if (!trackNameValue) return 0; artists.Resize(artistsArray->Size()); for (UInt_64 i = 0; i < artistsArray->Size(); ++i) { JsonObj* artistObj = (JsonObj*)(*artistsArray)[i]; JsonVar* artistNameVar = artistObj->GetVar("name"); JsonStr* artistNameValue = (JsonStr*)artistNameVar->GetValue(); artists[i] = artistNameValue->value; } id = trackIdValue->value; name = trackNameValue->value; return res.GetCode(); } UInt_32 Spotify::GetPlayingTrack(Vector& artists, Str_8& id, Str_8& name) { StartConnection(); Request req(Verb::GET, "/v1/me/player/currently-playing"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return GetPlayingTrack(artists, id, name); } Json result = res.GetJson(); JsonObj* itemObj = (JsonObj*)result.RetrieveValue("item"); if (!itemObj) return {}; JsonVar* artistsVar = itemObj->GetVar("artists"); if (!artistsVar) return 0; JsonArray* artistsArray = (JsonArray*)artistsVar->GetValue(); if (!artistsArray) return 0; JsonVar* trackIdVar = itemObj->GetVar("id"); if (!trackIdVar) return 0; JsonStr* trackIdValue = (JsonStr*)trackIdVar->GetValue(); if (!trackIdValue) return 0; JsonVar* trackNameVar = itemObj->GetVar("name"); if (!trackNameVar) return 0; JsonStr* trackNameValue = (JsonStr*)trackNameVar->GetValue(); if (!trackNameValue) return 0; artists.Resize(artistsArray->Size()); for (UInt_64 i = 0; i < artists.Size(); ++i) { JsonObj* artistObj = (JsonObj*)(*artistsArray)[i]; JsonVar* artistNameVar = artistObj->GetVar("name"); JsonStr* artistNameValue = (JsonStr*)artistNameVar->GetValue(); artists[i] = artistNameValue->value; } id = trackIdValue->value; name = trackNameValue->value; return res.GetCode(); } UInt_32 Spotify::GetQueue(Array& tracks) { StartConnection(); Request req(Verb::GET, "/v1/me/player/queue"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return GetQueue(tracks); } Json json = res.GetJson(); JsonObj* root = (JsonObj*)json.GetValue(); JsonVar* currentVar = root->GetVar("currently_playing"); if (!currentVar->GetValue()) return res.GetCode(); JsonObj* currentObj = (JsonObj*)currentVar->GetValue(); JsonArray* cArtists = (JsonArray*)currentObj->GetVar("artists")->GetValue(); JsonArray* queue = (JsonArray*)root->GetVar("queue")->GetValue(); tracks.Resize(queue->Size() + 1); tracks[0].artists.Resize(cArtists->Size()); for (UInt_64 i = 0; i < cArtists->Size(); ++i) tracks[0].artists[i] = ((JsonStr*)((JsonObj*)(*cArtists)[i])->GetVar("name")->GetValue())->value; tracks[0].id = ((JsonStr*)currentObj->GetVar("id")->GetValue())->value; tracks[0].name = ((JsonStr*)currentObj->GetVar("name")->GetValue())->value; for (UInt_64 i = 1; i < queue->Size(); ++i) { JsonObj* trackObj = (JsonObj*)(*queue)[i - 1]; JsonArray* artists = (JsonArray*)trackObj->GetVar("artists")->GetValue(); tracks[i].artists.Resize(artists->Size()); for (UInt_64 a = 0; a < artists->Size(); ++a) tracks[i].artists[a] = ((JsonStr*)((JsonObj*)(*artists)[a])->GetVar("name")->GetValue())->value; tracks[i].id = ((JsonStr*)trackObj->GetVar("id")->GetValue())->value; tracks[i].name = ((JsonStr*)trackObj->GetVar("name")->GetValue())->value; } return res.GetCode(); } UInt_32 Spotify::QueueTrack(const Str_8& id) { StartConnection(); Request req(Verb::POST, "/v1/me/player/queue"); req.AddQuery("uri", "spotify:track:" + id); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return QueueTrack(id); } return res.GetCode(); } UInt_32 Spotify::AddTracks(const Str_8& playlistId, const Array& trackIds, const UInt_32 pos) { StartConnection(); JsonObj obj(0); JsonArray tracks(trackIds.Size(), 0); for (UInt_64 i = 0; i < trackIds.Size(); ++i) tracks[i] = (JsonBase*)new JsonStr("spotify:track:" + trackIds[i]); obj.AddVar({"uris", tracks}); obj.AddVar({"position", (float)pos}); Json json(obj); Request req(Verb::POST, "/v1/playlists/" + playlistId + "/tracks"); req.BearerAuth(token); req.SetContentType(ContentType::APP_JSON); req.SetBody(json.ToStr(true)); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return AddTracks(playlistId, trackIds, pos); } return res.GetCode(); } UInt_32 Spotify::AddTrack(const Str_8& playlistId, const Str_8& trackId, const UInt_32 pos) { StartConnection(); Request req(Verb::POST, "/v1/playlists/" + playlistId + "/tracks"); req.AddQuery("position", Str_8::FromNum(pos)); req.AddQuery("uris", "spotify:track:" + trackId); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return AddTrack(playlistId, trackId, pos); } return res.GetCode(); } UInt_32 Spotify::Skip() { StartConnection(); Request req(Verb::POST, "/v1/me/player/next"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Skip(); } return res.GetCode(); } UInt_32 Spotify::Previous() { StartConnection(); Request req(Verb::POST, "/v1/me/player/previous"); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Previous(); } return res.GetCode(); } UInt_32 Spotify::Seek(const UInt_32 pos) { StartConnection(); Request req(Verb::PUT, "/v1/me/player/seek"); req.AddQuery("position_ms", Str_8::FromNum(pos)); req.BearerAuth(token); client.SendReq(req); Response res = client.RecvRes(); if (res.GetCode() == 401) { ReAuthorize(); return Seek(pos); } return res.GetCode(); } void Spotify::StartConnection() { client.Release(); client.Initialize(); client.Connect("api.spotify.com", SSL::HTTPS_Port); } bool Spotify::ReAuthorize() { SSL accounts(AddrType::IPV4); accounts.Initialize(); accounts.Connect("accounts.spotify.com", SSL::HTTPS_Port); Request reAuth(Verb::POST, "/api/token"); reAuth.SetContentType(ContentType::APP_FORMURLENCODED); reAuth.BasicAuth(clientId, secret); reAuth.AddToBody("grant_type", "refresh_token"); reAuth.AddToBody("refresh_token", rToken); accounts.SendReq(reAuth); Response res = accounts.RecvRes(); accounts.Release(); if (res.GetCode() != 200) { EHS_LOG_INT(LogType::ERR, 0, "Failed to reauthorize with Spotify with code #" + Str_8::FromNum(res.GetCode()) + "."); client.Release(); return false; } Json json = res.GetJson(); JsonObj* obj = (JsonObj*)json.GetValue(); JsonVar* tokenVar = obj->GetVar("access_token"); if (!tokenVar) return false; token = ((JsonStr*)tokenVar->GetValue())->value; return true; } bool Spotify::IsActive() const { return client.IsConnected(); } Str_8 Spotify::GetClientId() const { return clientId; } Str_8 Spotify::GetSecret() const { return secret; } Str_8 Spotify::GetRedURI() const { return redURI; } bool Spotify::IsVerificationForced() const { return forceVerify; } }