|
|
|
|
@ -23,152 +23,190 @@ |
|
|
|
|
#ifdef HAVE_CURL_CURL_H |
|
|
|
|
|
|
|
|
|
#include <boost/algorithm/string/trim.hpp> |
|
|
|
|
#include <boost/locale/conversion.hpp> |
|
|
|
|
#include <fstream> |
|
|
|
|
#include "charset.h" |
|
|
|
|
#include "curl_handle.h" |
|
|
|
|
#include "settings.h" |
|
|
|
|
#include "utility/html.h" |
|
|
|
|
#include "utility/string.h" |
|
|
|
|
|
|
|
|
|
const char *LastfmService::baseURL = "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method="; |
|
|
|
|
namespace { |
|
|
|
|
|
|
|
|
|
const char *LastfmService::msgParseFailed = "Fetched data could not be parsed"; |
|
|
|
|
const char *apiUrl = "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method="; |
|
|
|
|
const char *msgInvalidResponse = "Invalid response"; |
|
|
|
|
|
|
|
|
|
LastfmService::Result LastfmService::fetch(Args &args) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
namespace LastFm { |
|
|
|
|
|
|
|
|
|
Service::Result Service::fetch() |
|
|
|
|
{ |
|
|
|
|
Result result; |
|
|
|
|
result.first = false; |
|
|
|
|
|
|
|
|
|
std::string url = baseURL; |
|
|
|
|
std::string url = apiUrl; |
|
|
|
|
url += methodName(); |
|
|
|
|
for (Args::const_iterator it = args.begin(); it != args.end(); ++it) |
|
|
|
|
for (auto &arg : m_arguments) |
|
|
|
|
{ |
|
|
|
|
url += "&"; |
|
|
|
|
url += it->first; |
|
|
|
|
url += arg.first; |
|
|
|
|
url += "="; |
|
|
|
|
url += Curl::escape(it->second); |
|
|
|
|
url += Curl::escape(arg.second); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::string data; |
|
|
|
|
CURLcode code = Curl::perform(data, url); |
|
|
|
|
|
|
|
|
|
if (code != CURLE_OK) |
|
|
|
|
{ |
|
|
|
|
result.second = curl_easy_strerror(code); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (actionFailed(data)) |
|
|
|
|
else if (actionFailed(data)) |
|
|
|
|
result.second = msgInvalidResponse; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
stripHtmlTags(data); |
|
|
|
|
result.second = data; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
result = processData(data); |
|
|
|
|
|
|
|
|
|
if (!parse(data)) |
|
|
|
|
{ |
|
|
|
|
// if relevant part of data was not found and one of arguments
|
|
|
|
|
// was language, try to fetch it again without that parameter.
|
|
|
|
|
// otherwise just report failure.
|
|
|
|
|
Args::iterator lang = args.find("lang"); |
|
|
|
|
if (lang != args.end()) |
|
|
|
|
if (!result.first && !m_arguments["lang"].empty()) |
|
|
|
|
{ |
|
|
|
|
args.erase(lang); |
|
|
|
|
return fetch(args); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
// parse should change data to error msg, if it fails
|
|
|
|
|
result.second = data; |
|
|
|
|
return result; |
|
|
|
|
m_arguments.erase("lang"); |
|
|
|
|
result = fetch(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
result.first = true; |
|
|
|
|
result.second = data; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool LastfmService::actionFailed(const std::string &data) |
|
|
|
|
bool Service::actionFailed(const std::string &data) |
|
|
|
|
{ |
|
|
|
|
return data.find("status=\"failed\"") != std::string::npos; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LastfmService::postProcess(std::string &data) |
|
|
|
|
{ |
|
|
|
|
stripHtmlTags(data); |
|
|
|
|
boost::trim(data); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/***********************************************************************/ |
|
|
|
|
|
|
|
|
|
bool ArtistInfo::checkArgs(const Args &args) |
|
|
|
|
bool ArtistInfo::argumentsOk() |
|
|
|
|
{ |
|
|
|
|
return args.find("artist") != args.end(); |
|
|
|
|
return !m_arguments["artist"].empty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ArtistInfo::colorizeOutput(NC::Scrollpad &w) |
|
|
|
|
void ArtistInfo::beautifyOutput(NC::Scrollpad &w) |
|
|
|
|
{ |
|
|
|
|
w.setProperties(NC::Format::Bold, "\n\nSimilar artists:\n", NC::Format::NoBold, 0, boost::regex::literal); |
|
|
|
|
w.setProperties(NC::Format::Bold, "\n\nSimilar artists:\n", NC::Format::NoBold, 0); |
|
|
|
|
w.setProperties(NC::Format::Bold, "\n\nSimilar tags:\n", NC::Format::NoBold, 0); |
|
|
|
|
w.setProperties(Config.color2, "\n * ", NC::Color::End, 0, boost::regex::literal); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool ArtistInfo::parse(std::string &data) |
|
|
|
|
Service::Result ArtistInfo::processData(const std::string &data) |
|
|
|
|
{ |
|
|
|
|
size_t a, b; |
|
|
|
|
bool parse_failed = false; |
|
|
|
|
Service::Result result; |
|
|
|
|
result.first = false; |
|
|
|
|
|
|
|
|
|
boost::regex rx("<content>(.*?)</content>"); |
|
|
|
|
boost::smatch what; |
|
|
|
|
if (boost::regex_search(data, what, rx)) |
|
|
|
|
{ |
|
|
|
|
std::string desc = what[1]; |
|
|
|
|
// if there is a description...
|
|
|
|
|
if (desc.length() > 0) |
|
|
|
|
{ |
|
|
|
|
// ...locate the link to wiki on last.fm...
|
|
|
|
|
rx.assign("<link rel=\"original\" href=\"(.*?)\""); |
|
|
|
|
if (boost::regex_search(data, what, rx)) |
|
|
|
|
{ |
|
|
|
|
// ...try to get the content of it...
|
|
|
|
|
std::string wiki; |
|
|
|
|
CURLcode code = Curl::perform(wiki, what[1]); |
|
|
|
|
|
|
|
|
|
if ((a = data.find("<content>")) != std::string::npos) |
|
|
|
|
if (code != CURLE_OK) |
|
|
|
|
{ |
|
|
|
|
a += const_strlen("<content>"); |
|
|
|
|
if ((b = data.find("</content>")) == std::string::npos) |
|
|
|
|
parse_failed = true; |
|
|
|
|
result.second = curl_easy_strerror(code); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
parse_failed = true; |
|
|
|
|
|
|
|
|
|
if (parse_failed) |
|
|
|
|
{ |
|
|
|
|
data = msgParseFailed; |
|
|
|
|
return false; |
|
|
|
|
// ...and filter it to get the whole description.
|
|
|
|
|
rx.assign("<div id=\"wiki\">(.*?)</div>"); |
|
|
|
|
if (boost::regex_search(wiki, what, rx)) |
|
|
|
|
desc = unescapeHtmlUtf8(what[1]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (a == b) |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
data = "No description available for this artist."; |
|
|
|
|
return false; |
|
|
|
|
// otherwise, get rid of CDATA wrapper.
|
|
|
|
|
rx.assign("<!\\[CDATA\\[(.*)\\]\\]>"); |
|
|
|
|
desc = boost::regex_replace(desc, rx, "\\1"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector< std::pair<std::string, std::string> > similars; |
|
|
|
|
for (size_t i = data.find("<name>"), j, k = data.find("<url>"), l; |
|
|
|
|
i != std::string::npos; i = data.find("<name>", i), k = data.find("<url>", k)) |
|
|
|
|
stripHtmlTags(desc); |
|
|
|
|
boost::trim(desc); |
|
|
|
|
result.second += desc; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
result.second += "No description available for this artist."; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
j = data.find("</name>", i); |
|
|
|
|
i += const_strlen("<name>"); |
|
|
|
|
result.second = msgInvalidResponse; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
l = data.find("</url>", k); |
|
|
|
|
k += const_strlen("<url>"); |
|
|
|
|
auto add_similars = [&result](boost::sregex_iterator &it, const boost::sregex_iterator &last) { |
|
|
|
|
for (; it != last; ++it) |
|
|
|
|
{ |
|
|
|
|
std::string name = it->str(1); |
|
|
|
|
std::string url = it->str(2); |
|
|
|
|
stripHtmlTags(name); |
|
|
|
|
stripHtmlTags(url); |
|
|
|
|
result.second += "\n * "; |
|
|
|
|
result.second += name; |
|
|
|
|
result.second += " ("; |
|
|
|
|
result.second += url; |
|
|
|
|
result.second += ")"; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
similars.push_back(std::make_pair(data.substr(i, j-i), data.substr(k, l-k))); |
|
|
|
|
stripHtmlTags(similars.back().first); |
|
|
|
|
a = data.find("<similar>"); |
|
|
|
|
b = data.find("</similar>"); |
|
|
|
|
if (a != std::string::npos && b != std::string::npos) |
|
|
|
|
{ |
|
|
|
|
rx.assign("<artist>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</artist>"); |
|
|
|
|
auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx); |
|
|
|
|
auto last = boost::sregex_iterator(); |
|
|
|
|
if (it != last) |
|
|
|
|
result.second += "\n\nSimilar artists:\n"; |
|
|
|
|
add_similars(it, last); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
a += const_strlen("<![CDATA["); |
|
|
|
|
b -= const_strlen("]]>"); |
|
|
|
|
data = data.substr(a, b-a); |
|
|
|
|
a = data.find("<tags>"); |
|
|
|
|
b = data.find("</tags>"); |
|
|
|
|
if (a != std::string::npos && b != std::string::npos) |
|
|
|
|
{ |
|
|
|
|
rx.assign("<tag>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</tag>"); |
|
|
|
|
auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx); |
|
|
|
|
auto last = boost::sregex_iterator(); |
|
|
|
|
if (it != last) |
|
|
|
|
result.second += "\n\nSimilar tags:\n"; |
|
|
|
|
add_similars(it, last); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
postProcess(data); |
|
|
|
|
// get artist we look for, it's the one before similar artists
|
|
|
|
|
rx.assign("<name>.*?</name>.*?<url>(.*?)</url>.*?<similar>"); |
|
|
|
|
|
|
|
|
|
data += "\n\nSimilar artists:\n"; |
|
|
|
|
for (size_t i = 1; i < similars.size(); ++i) |
|
|
|
|
if (boost::regex_search(data, what, rx)) |
|
|
|
|
{ |
|
|
|
|
data += "\n * "; |
|
|
|
|
data += similars[i].first; |
|
|
|
|
data += " ("; |
|
|
|
|
data += similars[i].second; |
|
|
|
|
data += ")"; |
|
|
|
|
std::string url = what[1]; |
|
|
|
|
stripHtmlTags(url); |
|
|
|
|
result.second += "\n\n"; |
|
|
|
|
// add only url
|
|
|
|
|
result.second += url; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
result.first = true; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
data += "\n\n"; |
|
|
|
|
data += similars.front().second; |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endif |
|
|
|
|
|