1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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>
28 #include "curl_handle.h"
30 #include "utility/html.h"
31 #include "utility/string.h"
35 const char *apiUrl
= "http://ws.audioscrobbler.com/2.0/?api_key=d94e5b6e26469a2d1ffae8ef20131b79&method=";
36 const char *msgInvalidResponse
= "Invalid response";
42 Service::Result
Service::fetch()
47 std::string url
= apiUrl
;
49 for (auto &arg
: m_arguments
)
54 url
+= Curl::escape(arg
.second
);
58 CURLcode code
= Curl::perform(data
, url
);
61 result
.second
= curl_easy_strerror(code
);
62 else if (actionFailed(data
))
63 result
.second
= msgInvalidResponse
;
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");
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
)
104 Service::Result result
;
105 result
.first
= false;
107 boost::regex
rx("<content>(.*?)</content>");
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
;
121 unescapeHtmlEntities(url
);
122 // fill in language info since url points to english version.
123 const auto &lang
= m_arguments
["lang"];
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
);
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]);
144 // otherwise, get rid of CDATA wrapper.
145 rx
.assign("<!\\[CDATA\\[(.*)\\]\\]>");
146 desc
= boost::regex_replace(desc
, rx
, "\\1");
150 result
.second
+= desc
;
154 result
.second
+= "No description available for this artist.";
160 result
.second
= msgInvalidResponse
;
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
);
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();
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();
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];
210 result
.second
+= "\n\n";
212 result
.second
+= url
;