1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "lastfmengine.h"
25 #include "network/networkaccessmanager.h"
26 #include "gui/covers.h"
27 #include "gui/apikeys.h"
28 #include "config.h"
29 #include <QUrlQuery>
30 #include <QXmlStreamReader>
31 #include <QRegExp>
32
33 #include <QDebug>
34 static bool debugEnabled=false;
35 #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__
enableDebug()36 void LastFmEngine::enableDebug()
37 {
38 debugEnabled=true;
39 }
40
41 const QLatin1String LastFmEngine::constLang("lastfm");
42 const QLatin1String LastFmEngine::constLinkPlaceholder("XXX_CONTEXT_READ_MORE_ON_LASTFM_XXX");
43
44 static const char * constModeProperty="mode";
45 static const char * constQuery="query";
46
LastFmEngine(QObject * p)47 LastFmEngine::LastFmEngine(QObject *p)
48 : ContextEngine(p)
49 {
50 }
51
getLangs() const52 QStringList LastFmEngine::getLangs() const
53 {
54 QStringList langs;
55 langs.append(constLang);
56 return langs;
57 }
58
translateLinks(QString text) const59 QString LastFmEngine::translateLinks(QString text) const
60 {
61 text=text.replace(constLinkPlaceholder, tr("Read more on last.fm"));
62 return text;
63 }
64
search(const QStringList & query,Mode mode)65 void LastFmEngine::search(const QStringList &query, Mode mode)
66 {
67 QStringList fixedQuery=fixQuery(query);
68 QUrl url("https://ws.audioscrobbler.com/2.0/");
69 QUrlQuery urlQuery;
70
71 switch (mode) {
72 case Artist:
73 urlQuery.addQueryItem("method", "artist.getInfo");
74 break;
75 case Album:
76 urlQuery.addQueryItem("method", "album.getInfo");
77 urlQuery.addQueryItem("album", fixedQuery.at(1));
78 break;
79 case Track:
80 urlQuery.addQueryItem("method", "track.getInfo");
81 urlQuery.addQueryItem("track", fixedQuery.at(1));
82 break;
83 }
84
85 ApiKeys::self()->addKey(urlQuery, ApiKeys::LastFm);
86 urlQuery.addQueryItem("autocorrect", "1");
87 urlQuery.addQueryItem("artist", Covers::fixArtist(fixedQuery.at(0)));
88
89 url.setQuery(urlQuery);
90
91 job=NetworkAccessManager::self()->get(url);
92 job->setProperty(constModeProperty, (int)mode);
93
94 QStringList queryString;
95 for (QString q: fixedQuery) {
96 q=q.replace("/", "%2F");
97 q=q.replace(" ", "+");
98 queryString.append(q);
99 }
100 job->setProperty(constQuery, queryString.join("/"));
101 DBUG << url.toString();
102 connect(job, SIGNAL(finished()), this, SLOT(parseResponse()));
103 }
104
parseResponse()105 void LastFmEngine::parseResponse()
106 {
107 DBUG << __FUNCTION__;
108 NetworkJob *reply = getReply(sender());
109 if (!reply) {
110 return;
111 }
112
113 QByteArray data=reply->readAll();
114 if (!reply->ok() || data.isEmpty()) {
115 DBUG << "Empty/error";
116 emit searchResult(QString(), QString());
117 return;
118 }
119
120 Mode mode=(Mode)reply->property(constModeProperty).toInt();
121 QString text;
122
123 switch (mode) {
124 case Artist:
125 text=parseResponse(data, QLatin1String("artist"), QLatin1String("bio"));
126 break;
127 case Album:
128 text=parseResponse(data, QLatin1String("album"), QLatin1String("wiki"));
129 break;
130 case Track:
131 text=parseResponse(data, QLatin1String("track"), QLatin1String("wiki"));
132 break;
133 }
134
135 if (!text.isEmpty()) {
136 static const QRegExp constLicense("User-contributed text is available.*");
137 text.remove(constLicense);
138 text.replace("\n", "<br>");
139 text=text.simplified();
140 text.replace(" <br>", "<br>");
141
142 // Remove last.fm read more link (as we add our own!!)
143 int end=text.lastIndexOf(QLatin1String("on Last.fm</a>"));
144 if (-1!=end) {
145 int start=text.lastIndexOf(QLatin1String("<a href=\"https://www.last.fm/music/"), end);
146 if (-1==start) {
147 start=text.lastIndexOf(QLatin1String("<a href=\"http://www.last.fm/music/"), end);
148 }
149 if (-1!=start) {
150 if (text.indexOf(QLatin1String("Read more about"), start)<end) {
151 text=text.left(start);
152 }
153 }
154 }
155 text += QLatin1String("<br><br><a href='http://www.last.fm/music/")+reply->property(constQuery).toString()+QLatin1String("/+wiki'>")+constLinkPlaceholder+QLatin1String("</a>");
156 text.replace("<br><br><br><br><br>", "<br><br>");
157 text.replace("<br><br><br><br>", "<br><br>");
158 text.replace("<br><br><br>", "<br><br>");
159 }
160 emit searchResult(text, text.isEmpty() ? QString() : constLang);
161 }
162
parseResponse(const QByteArray & data,const QString & firstTag,const QString & secondTag)163 QString LastFmEngine::parseResponse(const QByteArray &data, const QString &firstTag, const QString &secondTag)
164 {
165 DBUG << __FUNCTION__ << data;
166 QXmlStreamReader xml(data);
167 xml.setNamespaceProcessing(false);
168 while (xml.readNextStartElement()) {
169 if (firstTag==xml.name()) {
170 while (xml.readNextStartElement()) {
171 if (secondTag==xml.name()) {
172 while (xml.readNextStartElement()) {
173 if (QLatin1String("content")==xml.name()) {
174 return xml.readElementText().trimmed();
175 } else {
176 xml.skipCurrentElement();
177 }
178 }
179 } else {
180 xml.skipCurrentElement();
181 }
182 }
183 }
184 }
185
186 return QString();
187 }
188
189 #include "moc_lastfmengine.cpp"
190