#include "monstercat_dl.h" #include "curl_dl.h" #include #include #include using std::endl; using std::map; using std::ofstream; using std::sort; using std::string; using std::stringstream; using std::to_string; using nlohmann::json; static bool json_extract(const json& j, const string& key, string& value) { bool result = false; if (j.contains(key) && j[key].is_string()) { value = j[key]; result = true; } return result; } static string trim_whitespace(const string& s) { const string whitespace = " \t\r\n\f\v"; string result(s); size_t pos; pos = result.find_first_not_of(whitespace); result.erase(0, pos); pos = result.find_last_not_of(whitespace); result.erase(pos + 1); return result; } string Monstercat_DL::calc_proper_artist(const string& artist_raw) { size_t pos; string result; pos = artist_raw.find("feat."); if (string::npos == pos) { return artist_raw; } result = artist_raw.substr(0, pos); result = trim_whitespace(result); return result; } string Monstercat_DL::calc_proper_title(const string& artist_raw, const string& title_raw, const string& version_raw) { size_t pos; string result; result = trim_whitespace(title_raw); pos = artist_raw.find("feat."); if (string::npos != pos) { result += " ("; result += trim_whitespace(artist_raw.substr(pos)); result += ")"; } if (!version_raw.empty()) { result += " ("; result += trim_whitespace(version_raw); result += ")"; } return result; } Monstercat_DL::Monstercat_DL() : m_log(&std::cout), m_base_url("https://player.monstercat.app/api/"), m_is_logged_in(false) {} Monstercat_DL::~Monstercat_DL() { } bool Monstercat_DL::login(const string& user, const string& pass) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json data; string url; stringstream out; data["Email"] = user; data["Password"] = pass; url = m_base_url; url += "sign-in"; ok = curl.post_json(url, data, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not post json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } data.clear(); out >> data; if (data.contains("Needs2FA") && data["Needs2FA"].is_boolean() && data["Needs2FA"] == false) { m_is_logged_in = true; return true; } return false; } bool Monstercat_DL::logout() { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json data; string url; if (!m_is_logged_in) { return false; } url = m_base_url; url += "sign-out"; ok = curl.post_json(url, data, nullptr); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not post json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } return ok; } json Monstercat_DL::get_release_json(const string& catalog_id) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json result; string url; stringstream out; url = m_base_url; url += "catalog/release/"; url += catalog_id; ok = curl.download(url, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return result; } out >> result; return result; } json Monstercat_DL::get_browse_json(const string& release_id) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; json result; string url; stringstream out; url = m_base_url; url += "catalog/browse?offset=0&limit=0&search=&sort=&nogold=false&releaseId="; url += release_id; ok = curl.download(url, &out); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download json" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return result; } out >> result; return result; } Release Monstercat_DL::parse_release_json(const json& release_json) { Release result; json release_object; json tracks_object; string artist_raw; string title_raw; string version_raw; Track temp_track; if (release_json.contains("Release")) { release_object = release_json["Release"]; } else { return result; } // Parse Release json_extract(release_object, "CatalogId", result.catalog_id); json_extract(release_object, "Id", result.id); json_extract(release_object, "Type", result.type); json_extract(release_object, "ReleaseDate", result.release_date); { size_t pos = result.release_date.find('T'); if (pos != string::npos) { result.release_date.erase(pos); } } json_extract(release_object, "ArtistsTitle", artist_raw); json_extract(release_object, "Title", title_raw); json_extract(release_object, "Version", version_raw); result.artist = calc_proper_artist(artist_raw); result.title = calc_proper_title(artist_raw, title_raw, version_raw); if (release_json.contains("Tracks") && release_json["Tracks"].is_array()) { tracks_object = release_json["Tracks"]; } else { return result; } // Parse Tracks for (json& curr_track : tracks_object) { if (curr_track.contains("TrackNumber") && curr_track["TrackNumber"].is_number_integer()) { temp_track.number = curr_track["TrackNumber"]; } json_extract(curr_track, "Id", temp_track.id); json_extract(curr_track, "ArtistsTitle", artist_raw); json_extract(curr_track, "Title", title_raw); json_extract(curr_track, "Version", version_raw); temp_track.artist = calc_proper_artist(artist_raw); temp_track.title = calc_proper_title(artist_raw, title_raw, version_raw); result.tracks.push_back(temp_track); } // Sort tracks by number sort(result.tracks.begin(), result.tracks.end(), [](const Track& t1, const Track& t2) { return t1.number < t2.number; }); return result; } void Monstercat_DL::add_extended_mixes(Release& release, const json& browse_json) { int track_num; json data_obj; json file_obj; Track *track; string mime_type; if (browse_json.contains("Data") && browse_json["Data"].is_array()) { data_obj = browse_json["Data"]; } for (json& track_json : data_obj) { // Get the track number track_num = 0; if (track_json.contains("TrackNumber") && track_json["TrackNumber"].is_number_integer()) { track_num = track_json["TrackNumber"]; } // File must exist and be an object if (!track_json.contains("File") || !track_json["File"].is_object()) { continue; } // Find track track = nullptr; for (int i = 0; i < release.tracks.size(); ++i) { if (release.tracks[i].number == track_num) { track = &(release.tracks[i]); break; } } if (nullptr == track) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not find track number " << track_num << " for catalog id " << release.catalog_id << endl; } return; } file_obj = track_json["File"]; json_extract(file_obj, "Id", track->extended_mix_file_id); json_extract(file_obj, "MimeType", mime_type); if ("audio/wav" == mime_type) { track->extended_mix_extension = ".wav"; } else { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Unknown MIME type for catalog id " << release.catalog_id << " - " << mime_type << endl; } } return; } } string Monstercat_DL::calc_release_folder(const Release& release, const string& main_folder) { string result; // Ensure main path ends with / result = main_folder; if (result.empty()) { result = "./"; } if (*(result.rbegin()) != '/') { result += '/'; } result += release.type; result += '/'; result += release.release_date; result += " - "; result += release.catalog_id; result += " - "; result += release.artist; result += " - "; result += release.title; return result; } string Monstercat_DL::calc_track_filename(const Release& release, int track_num) { string result; bool use_number; bool use_artist; const Track *track = nullptr; // Find the track for (int i = 0; i < release.tracks.size(); ++i) { if (release.tracks[i].number == track_num) { track = &(release.tracks[i]); break; } } if (nullptr == track) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Cannot find track number " << track_num << " in catalog id " << release.catalog_id << endl; } return ""; } // Determine what we need if (1 == release.tracks.size()) { use_number = false; use_artist = true; } else { use_number = true; if (track->artist == release.artist) { use_artist = false; } else { use_artist = true; } } // Build filename result = ""; if (use_number) { result += to_string(track->number); // 2 digits always if (result.size() < 2) { result.insert(result.begin(), '0'); } result += " - "; } if (use_artist) { result += track->artist; result += " - "; } result += track->title; return result; } bool Monstercat_DL::download_cover(const string& catalog_id, const string& path) { CURL_DL& curl = CURL_DL::get_handle(); bool ok; string url; stringstream out; ofstream out_file; map out_headers; string filename; url = "https://www.monstercat.com/release/"; url += catalog_id; url += "/cover"; ok = curl.download(url, &out, nullptr, &out_headers); if (!ok) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not download image" << endl; *m_log << "CURL:" << curl.get_error() << endl; } return false; } filename = path; if (0 == out_headers.count("content-type")) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "No content-type" << endl; } } else { if (out_headers["content-type"] == "image/jpeg") { filename += ".jpg"; } else if (out_headers["content-type"] == "image/png") { filename += ".png"; } else { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Unknown Content-Type for cover - " << out_headers["content-type"] << endl; } } } out_file.open(filename, std::ios::binary); if (!out_file.is_open()) { if (nullptr != m_log) { *m_log << __PRETTY_FUNCTION__ << endl; *m_log << "Could not open file for write" << endl; *m_log << filename << endl; } return false; } out_file << out.rdbuf(); out_file.close(); return true; } bool Monstercat_DL::download_track(const string& release_id, const string& track_id, const string& path) { string url; url = m_base_url; url += "release/"; url += release_id; url += "/track-download/"; url += track_id; return false; } bool Monstercat_DL::download_file(const string& file_id, const string& path) { string url; url = m_base_url; url += "file/"; url += file_id; url += "/open?download=true"; return false; }