Print song information to the console

This commit is contained in:
kdeng00
2020-11-28 18:26:17 -05:00
parent e576842f2e
commit 906fb04c82
10 changed files with 468 additions and 23 deletions
+3
View File
@@ -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
+31 -3
View File
@@ -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})
-12
View File
@@ -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;
}
-8
View File
@@ -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.
+4
View File
@@ -0,0 +1,4 @@
{
"client_id": "id",
"client_secret": "secret"
}
+76
View File
@@ -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
+175
View File
@@ -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
+113
View File
@@ -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
+65
View File
@@ -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;
}