Fix InternetLyricsFetcher
[ncmpcpp.git] / src / lastfm_service.cpp
blob3d6b173d2d99ea1e1cdac53362c6ac303eb44b3c
1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "lastfm_service.h"
23 #include <boost/algorithm/string/replace.hpp>
24 #include <boost/algorithm/string/trim.hpp>
25 #include <boost/locale/conversion.hpp>
26 #include <fstream>
27 #include "charset.h"
28 #include "curl_handle.h"
29 #include "settings.h"
30 #include "utility/html.h"
31 #include "utility/string.h"
33 namespace {
35 const char *apiUrl = "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method=";
36 const char *msgInvalidResponse = "Invalid response";
40 namespace LastFm {
42 Service::Result Service::fetch()
44 Result result;
45 result.first = false;
47 std::string url = apiUrl;
48 url += methodName();
49 for (auto &arg : m_arguments)
51 url += "&";
52 url += arg.first;
53 url += "=";
54 url += Curl::escape(arg.second);
57 std::string data;
58 CURLcode code = Curl::perform(data, url);
60 if (code != CURLE_OK)
61 result.second = curl_easy_strerror(code);
62 else if (actionFailed(data))
63 result.second = msgInvalidResponse;
64 else
66 result = processData(data);
68 // if relevant part of data was not found and one of arguments
69 // was language, try to fetch it again without that parameter.
70 // otherwise just report failure.
71 if (!result.first && !m_arguments["lang"].empty())
73 m_arguments.erase("lang");
74 result = fetch();
78 return result;
81 bool Service::actionFailed(const std::string &data)
83 return data.find("status=\"failed\"") != std::string::npos;
86 /***********************************************************************/
88 bool ArtistInfo::argumentsOk()
90 return !m_arguments["artist"].empty();
93 void ArtistInfo::beautifyOutput(NC::Scrollpad &w)
95 w.setProperties(NC::Format::Bold, "\n\nSimilar artists:\n", NC::Format::NoBold, 0);
96 w.setProperties(NC::Format::Bold, "\n\nSimilar tags:\n", NC::Format::NoBold, 0);
97 w.setProperties(Config.color2.color(), "\n * ", NC::Color::End, 0,
98 boost::regex::literal);
101 Service::Result ArtistInfo::processData(const std::string &data)
103 size_t a, b;
104 Service::Result result;
105 result.first = false;
107 boost::regex rx("<content>(.*?)</content>");
108 boost::smatch what;
109 if (boost::regex_search(data, what, rx))
111 std::string desc = what[1];
112 // if there is a description...
113 if (desc.length() > 0)
115 // ...locate the link to wiki on last.fm...
116 rx.assign("<link rel=\"original\" href=\"(.*?)\"");
117 if (boost::regex_search(data, what, rx))
119 std::string url = what[1], wiki;
120 // unescape &amp;s
121 unescapeHtmlEntities(url);
122 // fill in language info since url points to english version.
123 const auto &lang = m_arguments["lang"];
124 if (!lang.empty())
125 boost::replace_first(url, "last.fm/music/", "last.fm/" + lang + "/music/");
126 // ...try to get the content of it...
127 CURLcode code = Curl::perform(wiki, url, "", true);
129 if (code != CURLE_OK)
131 result.second = curl_easy_strerror(code);
132 return result;
134 else
136 // ...and filter it to get the whole description.
137 rx.assign("<div class=\"wiki\">(.*?)</div>");
138 if (boost::regex_search(wiki, what, rx))
139 desc = unescapeHtmlUtf8(what[1]);
142 else
144 // otherwise, get rid of CDATA wrapper.
145 rx.assign("<!\\[CDATA\\[(.*)\\]\\]>");
146 desc = boost::regex_replace(desc, rx, "\\1");
148 stripHtmlTags(desc);
149 boost::trim(desc);
150 result.second += desc;
152 else
154 result.second += "No description available for this artist.";
155 return result;
158 else
160 result.second = msgInvalidResponse;
161 return result;
164 auto add_similars = [&result](boost::sregex_iterator &it, const boost::sregex_iterator &last) {
165 for (; it != last; ++it)
167 std::string value = it->str(1);
168 std::string url = it->str(2);
169 stripHtmlTags(value);
170 stripHtmlTags(url);
171 result.second += "\n * ";
172 result.second += value;
173 result.second += " (";
174 result.second += url;
175 result.second += ")";
179 a = data.find("<similar>");
180 b = data.find("</similar>");
181 if (a != std::string::npos && b != std::string::npos)
183 rx.assign("<artist>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</artist>");
184 auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx);
185 auto last = boost::sregex_iterator();
186 if (it != last)
187 result.second += "\n\nSimilar artists:\n";
188 add_similars(it, last);
191 a = data.find("<tags>");
192 b = data.find("</tags>");
193 if (a != std::string::npos && b != std::string::npos)
195 rx.assign("<tag>.*?<name>(.*?)</name>.*?<url>(.*?)</url>.*?</tag>");
196 auto it = boost::sregex_iterator(data.begin()+a, data.begin()+b, rx);
197 auto last = boost::sregex_iterator();
198 if (it != last)
199 result.second += "\n\nSimilar tags:\n";
200 add_similars(it, last);
203 // get artist we look for, it's the one before similar artists
204 rx.assign("<name>.*?</name>.*?<url>(.*?)</url>.*?<similar>");
206 if (boost::regex_search(data, what, rx))
208 std::string url = what[1];
209 stripHtmlTags(url);
210 result.second += "\n\n";
211 // add only url
212 result.second += url;
215 result.first = true;
216 return result;