Print song information to the console
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
[submodule "SpotifyDemo/3rdparty/vcpkg"]
|
||||
path = SpotifyDemo/3rdparty/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg
|
||||
[submodule "SpotifyDemo/3rdparty/cpp-base64"]
|
||||
path = SpotifyDemo/3rdparty/cpp-base64
|
||||
url = https://github.com/ReneNyffenegger/cpp-base64
|
||||
|
||||
+1
Submodule SpotifyDemo/3rdparty/cpp-base64 added at 80525f93cb
@@ -3,7 +3,35 @@
|
||||
#
|
||||
cmake_minimum_required (VERSION 3.8)
|
||||
|
||||
# Add source to this project's executable.
|
||||
add_executable (SpotifyDemo "SpotifyDemo.cpp" "SpotifyDemo.h")
|
||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
# TODO: Add tests and install targets if needed.
|
||||
|
||||
find_package (nlohmann_json CONFIG REQUIRED)
|
||||
find_package (cpr CONFIG REQUIRED)
|
||||
|
||||
find_path (CPPCODEC_INCLUDE_DIRS "cppcodec/base32_crockford.hpp")
|
||||
|
||||
|
||||
set (SOURCES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/SpotifyDemo.cpp"
|
||||
)
|
||||
|
||||
set (SD_INCLUDE_DIR
|
||||
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
set (HEADERS
|
||||
"${SD_INCLUDE_DIR}/Models.h"
|
||||
)
|
||||
|
||||
include_directories ("${SD_INCLUDE_DIR}")
|
||||
|
||||
configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/client_information.json" "${CMAKE_BINARY_DIR}/SpotifyDemo/client_information.json" COPYONLY)
|
||||
|
||||
# Add source to this project's executable.
|
||||
add_executable (SpotifyDemo ${SOURCES})
|
||||
|
||||
target_link_libraries (SpotifyDemo PRIVATE nlohmann_json nlohmann_json::nlohmann_json cpr)
|
||||
target_include_directories (SpotifyDemo PRIVATE ${CPPCODEC_INCLUDE_DIRS})
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// SpotifyDemo.cpp : Defines the entry point for the application.
|
||||
//
|
||||
|
||||
#include "SpotifyDemo.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << "Hello CMake." << endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// SpotifyDemo.h : Include file for standard system include files,
|
||||
// or project specific include files.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// TODO: Reference additional headers your program requires here.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"client_id": "id",
|
||||
"client_secret": "secret"
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef _MODELS_H_
|
||||
#define _MODELS_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Models
|
||||
{
|
||||
template<typename Str = std::string,
|
||||
typename Num = int,
|
||||
typename Dur = long>
|
||||
class Song
|
||||
{
|
||||
public:
|
||||
Song() = default;
|
||||
|
||||
Str title;
|
||||
Str artist;
|
||||
Str album;
|
||||
Str released;
|
||||
Dur duration;
|
||||
};
|
||||
|
||||
template<typename Str = std::string,
|
||||
typename Num = int>
|
||||
class Album
|
||||
{
|
||||
public:
|
||||
Album() = default;
|
||||
|
||||
Str title;
|
||||
Str released;
|
||||
std::vector<Song<>> songs;
|
||||
};
|
||||
|
||||
template<typename Str = std::string>
|
||||
class ClientInformation
|
||||
{
|
||||
public:
|
||||
ClientInformation() = default;
|
||||
ClientInformation(Str& id, Str& secret) :
|
||||
client_id(std::move(id)), client_secret(std::move(secret))
|
||||
{
|
||||
}
|
||||
ClientInformation(Str&& id, Str&& secret) :
|
||||
client_id(id), client_secret(secret)
|
||||
{
|
||||
}
|
||||
|
||||
auto is_empty()
|
||||
{
|
||||
return (client_id.empty() && client_secret.empty()) ? true : false;
|
||||
}
|
||||
|
||||
Str client_id;
|
||||
Str client_secret;
|
||||
};
|
||||
|
||||
template<typename Str = std::string,
|
||||
typename Num = int>
|
||||
class TokenResp
|
||||
{
|
||||
public:
|
||||
auto is_empty()
|
||||
{
|
||||
return this->access_token.empty();
|
||||
}
|
||||
|
||||
Str access_token;
|
||||
Str token_type;
|
||||
Num expires_in;
|
||||
Str scope;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,175 @@
|
||||
#ifndef _SPOTIFYCALL_H_
|
||||
#define _SPOTIFYCALL_H_
|
||||
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cpr/cpr.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "Models.h"
|
||||
|
||||
namespace Spotify
|
||||
{
|
||||
template<typename Tok = Models::TokenResp<std::string>>
|
||||
class SpotifyCall
|
||||
{
|
||||
public:
|
||||
SpotifyCall() = default;
|
||||
SpotifyCall(Tok& tok) : token(tok)
|
||||
{
|
||||
}
|
||||
SpotifyCall(Tok&& tok) : token(std::move(tok))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
template<typename Str = std::string>
|
||||
auto search_songs(Str&& title)
|
||||
{
|
||||
constexpr auto limit = 49;
|
||||
|
||||
std::vector<Models::Album<>> albums;
|
||||
albums.reserve(limit);
|
||||
|
||||
std::string search_query("name:");
|
||||
search_query.append(delimit_search_query(title));
|
||||
|
||||
|
||||
const auto uri = search_uri();
|
||||
const auto authorization = bearer_authorization();
|
||||
|
||||
Str limit_str(std::to_string(limit));
|
||||
|
||||
auto resp = cpr::Get(cpr::Url{ uri },
|
||||
cpr::Parameters{ {"type", "track"},
|
||||
{"limit", limit_str},
|
||||
{"q", search_query} },
|
||||
cpr::Header{ {"Authorization", authorization.c_str()} });
|
||||
|
||||
if (resp.status_code == 200) {
|
||||
parse_albums(albums, std::move(resp.text));
|
||||
}
|
||||
|
||||
|
||||
return albums;
|
||||
}
|
||||
|
||||
template<typename Cont = std::vector<Models::Album<std::string>>>
|
||||
void print_songs_to_console(Cont &albums)
|
||||
{
|
||||
std::cout << "Printing songs to the console\n";
|
||||
|
||||
std::for_each(albums.begin(), albums.end(), [](auto album) {
|
||||
auto song = album.songs.at(0);
|
||||
|
||||
std::cout << "Title: " << song.title << "\n";
|
||||
std::cout << "Artist: " << song.artist << "\n";
|
||||
std::cout << "Album: " << album.title << "\n";
|
||||
std::cout << "Released Date: " << song.released << "\n";
|
||||
std::cout << "Duration (ms): " << song.duration << "\n";
|
||||
std::cout << "\n";
|
||||
});
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Str = std::string>
|
||||
auto delimit_search_query(Str &&title)
|
||||
{
|
||||
return std::regex_replace(title, std::regex(" "), "%20");
|
||||
}
|
||||
|
||||
auto bearer_authorization()
|
||||
{
|
||||
std::string authorization("Bearer ");
|
||||
authorization.append(this->token.access_token);
|
||||
|
||||
return authorization;
|
||||
}
|
||||
|
||||
template<typename Alb = Models::Album<>, typename Vec = std::vector<Alb>,
|
||||
typename Str = std::string>
|
||||
void parse_albums(Vec &alb, Str &&resp)
|
||||
{
|
||||
auto obj = nlohmann::json::parse(resp);
|
||||
|
||||
for (auto& item : obj["tracks"]["items"]) {
|
||||
auto album_json = item["album"];
|
||||
auto artists_json = item["artists"];
|
||||
|
||||
Alb album;
|
||||
album.title = album_json["name"].get<std::string>();
|
||||
album.released = album_json["release_date"].get<std::string>();
|
||||
|
||||
Models::Song<> song;
|
||||
song.title = item["name"].get<std::string>();
|
||||
song.duration = item["duration_ms"].get<long>();
|
||||
song.artist = artists_json[0]["name"].get<std::string>();
|
||||
song.released = album.released;
|
||||
song.album = album.title;
|
||||
|
||||
std::vector<Models::Song<>> songs;
|
||||
songs.push_back(song);
|
||||
|
||||
album.songs = songs;
|
||||
|
||||
alb.push_back(album);
|
||||
}
|
||||
}
|
||||
|
||||
auto saved_albums_response_body()
|
||||
{
|
||||
nlohmann::json obj;
|
||||
|
||||
std::string uri(base_uri());
|
||||
uri.append("/");
|
||||
uri.append(album_saved());
|
||||
|
||||
std::string auth_header("Bearer ");
|
||||
auth_header.append(this->token.access_token);
|
||||
|
||||
auto resp = cpr::Get(cpr::Url(uri),
|
||||
cpr::Header{{"Authorization", auth_header.c_str()}}
|
||||
);
|
||||
|
||||
if (resp.status_code == 200) {
|
||||
obj = nlohmann::json::parse(resp.text);
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<typename Str = std::string>
|
||||
auto search_uri()
|
||||
{
|
||||
Str uri(base_uri());
|
||||
uri.append("/");
|
||||
uri.append(search_item_endpoint());
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
constexpr auto base_uri() noexcept
|
||||
{
|
||||
return "https://api.spotify.com/v1";
|
||||
}
|
||||
|
||||
constexpr auto album_saved() noexcept
|
||||
{
|
||||
return "me/albums";
|
||||
}
|
||||
constexpr auto search_item_endpoint() noexcept
|
||||
{
|
||||
return "search";
|
||||
}
|
||||
|
||||
Tok token;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
#ifndef _TOKENRETRIEVAL_H_
|
||||
#define _TOKENRETRIEVAL_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "cppcodec/base64_rfc4648.hpp"
|
||||
#include "cpr/cpr.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "Models.h"
|
||||
|
||||
namespace Manager
|
||||
{
|
||||
template<typename Str = std::string>
|
||||
class TokenRetrieval
|
||||
{
|
||||
public:
|
||||
TokenRetrieval() = default;
|
||||
TokenRetrieval(Models::ClientInformation<Str> &info) :
|
||||
client_information(std::move(info))
|
||||
{
|
||||
}
|
||||
TokenRetrieval(Models::ClientInformation<Str> &&info) :
|
||||
client_information(info)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename TokResp>
|
||||
auto retrieve_token()
|
||||
{
|
||||
if (this->client_information.is_empty()) {
|
||||
return TokResp();
|
||||
}
|
||||
|
||||
auto token = make_call<TokResp>();
|
||||
|
||||
|
||||
return (token.is_empty()) ? TokResp() : token;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
template<typename TokResp>
|
||||
auto make_call()
|
||||
{
|
||||
TokResp resp;
|
||||
|
||||
Str authorization("Basic ");
|
||||
authorization.append(encoded_authorization());
|
||||
|
||||
|
||||
const auto uri = construct_uri(spotify_uri(), spotify_token_endpoint());
|
||||
auto r = cpr::Post(cpr::Url{ uri },
|
||||
cpr::Parameters{ {"grant_type", "client_credentials" }},
|
||||
cpr::Header{ {"Authorization", authorization.c_str()} }
|
||||
);
|
||||
|
||||
if (r.status_code == 200) {
|
||||
auto token = nlohmann::json::parse(r.text);
|
||||
resp.access_token = token["access_token"].get<Str>();
|
||||
resp.token_type = token["token_type"].get<Str>();
|
||||
resp.expires_in = token["expires_in"].get<int>();
|
||||
resp.scope = token["scope"].get<Str>();
|
||||
}
|
||||
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
auto encoded_authorization()
|
||||
{
|
||||
Str unencoded(client_information.client_id);
|
||||
unencoded.append(":");
|
||||
unencoded.append(client_information.client_secret);
|
||||
|
||||
std::vector<char> v(unencoded.begin(), unencoded.end());
|
||||
|
||||
return cppcodec::base64_rfc4648::encode(v);
|
||||
}
|
||||
|
||||
template<typename Ch = char>
|
||||
auto construct_uri(Ch *base, Ch *endpoint)
|
||||
{
|
||||
Str uri(base);
|
||||
|
||||
if (uri.at(uri.size() - 1) != '/') {
|
||||
uri.append("/");
|
||||
}
|
||||
|
||||
uri.append(endpoint);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
auto constexpr spotify_uri() noexcept
|
||||
{
|
||||
return "https://accounts.spotify.com";
|
||||
}
|
||||
|
||||
auto constexpr spotify_token_endpoint() noexcept
|
||||
{
|
||||
return "api/token";
|
||||
}
|
||||
|
||||
Models::ClientInformation<Str> client_information;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
// SpotifyDemo.cpp : Defines the entry point for the application.
|
||||
//
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
#include "cpr/cpr.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "Models.h"
|
||||
#include "SpotifyCall.hpp"
|
||||
#include "TokenRetrieval.hpp"
|
||||
|
||||
using namespace Models;
|
||||
|
||||
|
||||
template<typename Conf, typename Str>
|
||||
auto retrieve_client_information(Str &&path)
|
||||
{
|
||||
std::fstream config_file(path, std::ios::in);
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << config_file.rdbuf();
|
||||
|
||||
config_file.close();
|
||||
|
||||
auto client_json = nlohmann::json::parse(buffer.str());
|
||||
std::string id(client_json["client_id"].get<std::string>());
|
||||
std::string secret(client_json["client_secret"].get<std::string>());
|
||||
|
||||
Conf client_information(std::move(id), std::move(secret));
|
||||
|
||||
return client_information;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
std::cout << "Starting SpotfyDemo\n";
|
||||
|
||||
if (argc < 2) {
|
||||
std::cout << "Provide path to the client information json file\n";
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string path(std::move(argv[1]));
|
||||
|
||||
auto client_info = retrieve_client_information<ClientInformation<std::string>,
|
||||
std::string>(std::move(path));
|
||||
|
||||
Manager::TokenRetrieval<> tok_mgr(std::move(client_info));
|
||||
auto resp = tok_mgr.retrieve_token<Models::TokenResp<std::string>>();
|
||||
|
||||
Spotify::SpotifyCall<> spotify(std::move(resp));
|
||||
std::string song_title("Heave");
|
||||
|
||||
auto albums = spotify.search_songs(song_title);
|
||||
spotify.print_songs_to_console(albums);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user