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