2020-05-08 18:35:36 +02:00
/*
* Strawberry Music Player
2021-03-20 21:14:47 +01:00
* Copyright 2020 - 2021 , Jonas Kvinge < jonas @ jkvinge . net >
2020-05-08 18:35:36 +02:00
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
*
*/
# include "config.h"
2020-05-08 18:47:55 +02:00
# include <memory>
2020-05-08 18:35:36 +02:00
# include <QObject>
# include <QPair>
# include <QList>
# include <QByteArray>
# include <QVariant>
# include <QString>
# include <QUrl>
# include <QUrlQuery>
# include <QNetworkRequest>
# include <QNetworkReply>
# include <QSslError>
# include <QDesktopServices>
# include <QCryptographicHash>
2020-07-18 04:05:07 +02:00
# include <QRegularExpression>
2020-05-08 18:35:36 +02:00
# include <QSettings>
# include <QJsonDocument>
# include <QJsonObject>
# include <QJsonArray>
# include <QJsonValue>
# include <QJsonParseError>
# include <QMessageBox>
# include <QtDebug>
# include "core/logging.h"
# include "core/utilities.h"
2021-01-11 16:48:46 +01:00
# include "core/networkaccessmanager.h"
2020-05-08 18:35:36 +02:00
# include "internet/localredirectserver.h"
# include "jsonlyricsprovider.h"
# include "lyricsfetcher.h"
# include "lyricsprovider.h"
# include "geniuslyricsprovider.h"
const char * GeniusLyricsProvider : : kSettingsGroup = " GeniusLyrics " ;
const char * GeniusLyricsProvider : : kOAuthAuthorizeUrl = " https://api.genius.com/oauth/authorize " ;
const char * GeniusLyricsProvider : : kOAuthAccessTokenUrl = " https://api.genius.com/oauth/token " ;
2021-07-11 09:49:38 +02:00
const char * GeniusLyricsProvider : : kOAuthRedirectUrl = " http://localhost:63111/ " ; // Genius does not accept a random port number. This port must match the the URL of the ClientID.
2020-05-08 18:35:36 +02:00
const char * GeniusLyricsProvider : : kUrlSearch = " https://api.genius.com/search/ " ;
const char * GeniusLyricsProvider : : kClientIDB64 = " RUNTNXU4U1VyMU1KUU5hdTZySEZteUxXY2hkanFiY3lfc2JjdXBpNG5WMU9SNUg4dTBZelEtZTZCdFg2dl91SQ== " ;
const char * GeniusLyricsProvider : : kClientSecretB64 = " VE9pMU9vUjNtTXZ3eFR3YVN0QVRyUjVoUlhVWDI1Ylp5X240eEt1M0ZkYlNwRG5JUnd0LXFFbHdGZkZkRWY2VzJ1S011UnQzM3c2Y3hqY0tVZ3NGN2c= " ;
2021-08-12 23:00:10 +02:00
GeniusLyricsProvider : : GeniusLyricsProvider ( NetworkAccessManager * network , QObject * parent ) : JsonLyricsProvider ( " Genius " , true , true , network , parent ) , server_ ( nullptr ) {
2020-05-08 18:35:36 +02:00
QSettings s ;
s . beginGroup ( kSettingsGroup ) ;
if ( s . contains ( " access_token " ) ) {
access_token_ = s . value ( " access_token " ) . toString ( ) ;
}
s . endGroup ( ) ;
}
2020-05-12 21:28:42 +02:00
GeniusLyricsProvider : : ~ GeniusLyricsProvider ( ) {
while ( ! replies_ . isEmpty ( ) ) {
QNetworkReply * reply = replies_ . takeFirst ( ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2020-05-12 21:28:42 +02:00
reply - > abort ( ) ;
reply - > deleteLater ( ) ;
}
}
2020-05-08 18:35:36 +02:00
void GeniusLyricsProvider : : Authenticate ( ) {
QUrl redirect_url ( kOAuthRedirectUrl ) ;
if ( ! server_ ) {
server_ = new LocalRedirectServer ( this ) ;
server_ - > set_https ( false ) ;
server_ - > set_port ( redirect_url . port ( ) ) ;
if ( ! server_ - > Listen ( ) ) {
AuthError ( server_ - > error ( ) ) ;
server_ - > deleteLater ( ) ;
server_ = nullptr ;
return ;
}
2021-01-26 16:48:04 +01:00
QObject : : connect ( server_ , & LocalRedirectServer : : Finished , this , & GeniusLyricsProvider : : RedirectArrived ) ;
2020-05-08 18:35:36 +02:00
}
code_verifier_ = Utilities : : CryptographicRandomString ( 44 ) ;
code_challenge_ = QString ( QCryptographicHash : : hash ( code_verifier_ . toUtf8 ( ) , QCryptographicHash : : Sha256 ) . toBase64 ( QByteArray : : Base64UrlEncoding ) ) ;
if ( code_challenge_ . lastIndexOf ( QChar ( ' = ' ) ) = = code_challenge_ . length ( ) - 1 ) {
code_challenge_ . chop ( 1 ) ;
}
const ParamList params = ParamList ( ) < < Param ( " client_id " , QByteArray : : fromBase64 ( kClientIDB64 ) )
< < Param ( " redirect_uri " , redirect_url . toString ( ) )
< < Param ( " scope " , " me " )
< < Param ( " state " , code_challenge_ )
< < Param ( " response_type " , " code " ) ;
QUrlQuery url_query ;
for ( const Param & param : params ) {
url_query . addQueryItem ( QUrl : : toPercentEncoding ( param . first ) , QUrl : : toPercentEncoding ( param . second ) ) ;
}
QUrl url ( kOAuthAuthorizeUrl ) ;
url . setQuery ( url_query ) ;
const bool result = QDesktopServices : : openUrl ( url ) ;
if ( ! result ) {
QMessageBox messagebox ( QMessageBox : : Information , tr ( " Genius Authentication " ) , tr ( " Please open this URL in your browser " ) + QString ( " :<br /><a href= \" %1 \" >%1</a> " ) . arg ( url . toString ( ) ) , QMessageBox : : Ok ) ;
messagebox . setTextFormat ( Qt : : RichText ) ;
messagebox . exec ( ) ;
}
}
void GeniusLyricsProvider : : RedirectArrived ( ) {
if ( ! server_ ) return ;
if ( server_ - > error ( ) . isEmpty ( ) ) {
QUrl url = server_ - > request_url ( ) ;
if ( url . isValid ( ) ) {
QUrlQuery url_query ( url ) ;
if ( url_query . hasQueryItem ( " error " ) ) {
AuthError ( QUrlQuery ( url ) . queryItemValue ( " error " ) ) ;
}
else if ( url_query . hasQueryItem ( " code " ) ) {
QUrl redirect_url ( kOAuthRedirectUrl ) ;
redirect_url . setPort ( server_ - > url ( ) . port ( ) ) ;
RequestAccessToken ( url , redirect_url ) ;
}
else {
AuthError ( tr ( " Redirect missing token code! " ) ) ;
}
}
else {
AuthError ( tr ( " Received invalid reply from web browser. " ) ) ;
}
}
else {
AuthError ( server_ - > error ( ) ) ;
}
server_ - > close ( ) ;
server_ - > deleteLater ( ) ;
server_ = nullptr ;
}
void GeniusLyricsProvider : : RequestAccessToken ( const QUrl & url , const QUrl & redirect_url ) {
qLog ( Debug ) < < " GeniusLyrics: Authorization URL Received " < < url ;
QUrlQuery url_query ( url ) ;
if ( url . hasQuery ( ) & & url_query . hasQueryItem ( " code " ) & & url_query . hasQueryItem ( " state " ) ) {
QString code = url_query . queryItemValue ( " code " ) ;
const ParamList params = ParamList ( ) < < Param ( " code " , code )
< < Param ( " client_id " , QByteArray : : fromBase64 ( kClientIDB64 ) )
< < Param ( " client_secret " , QByteArray : : fromBase64 ( kClientSecretB64 ) )
< < Param ( " redirect_uri " , redirect_url . toString ( ) )
< < Param ( " grant_type " , " authorization_code " )
< < Param ( " response_type " , " code " ) ;
QUrlQuery new_url_query ;
for ( const Param & param : params ) {
new_url_query . addQueryItem ( QUrl : : toPercentEncoding ( param . first ) , QUrl : : toPercentEncoding ( param . second ) ) ;
}
QUrl new_url ( kOAuthAccessTokenUrl ) ;
2020-08-14 20:31:04 +02:00
QNetworkRequest req ( new_url ) ;
2020-08-14 20:20:41 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
# else
2020-05-08 18:35:36 +02:00
req . setAttribute ( QNetworkRequest : : FollowRedirectsAttribute , true ) ;
2020-08-14 20:20:41 +02:00
# endif
2020-05-08 18:35:36 +02:00
QByteArray query = new_url_query . toString ( QUrl : : FullyEncoded ) . toUtf8 ( ) ;
QNetworkReply * reply = network_ - > post ( req , query ) ;
2020-05-12 21:28:42 +02:00
replies_ < < reply ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( reply , & QNetworkReply : : sslErrors , this , & GeniusLyricsProvider : : HandleLoginSSLErrors ) ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply ] ( ) { AccessTokenRequestFinished ( reply ) ; } ) ;
2020-05-08 18:35:36 +02:00
}
else {
AuthError ( tr ( " Redirect from Genius is missing query items code or state. " ) ) ;
return ;
}
}
2021-06-20 19:04:08 +02:00
void GeniusLyricsProvider : : HandleLoginSSLErrors ( const QList < QSslError > & ssl_errors ) {
2020-05-08 18:35:36 +02:00
for ( const QSslError & ssl_error : ssl_errors ) {
login_errors_ + = ssl_error . errorString ( ) ;
}
}
void GeniusLyricsProvider : : AccessTokenRequestFinished ( QNetworkReply * reply ) {
2020-05-12 21:28:42 +02:00
if ( ! replies_ . contains ( reply ) ) return ;
replies_ . removeAll ( reply ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2020-05-08 18:35:36 +02:00
reply - > deleteLater ( ) ;
if ( reply - > error ( ) ! = QNetworkReply : : NoError | | reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ! = 200 ) {
if ( reply - > error ( ) ! = QNetworkReply : : NoError & & reply - > error ( ) < 200 ) {
// This is a network error, there is nothing more to do.
AuthError ( QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ) ;
return ;
}
else {
// See if there is Json data containing "status" and "userMessage" then use that instead.
2021-12-19 20:59:38 +01:00
QByteArray data = reply - > readAll ( ) ;
2020-05-08 18:35:36 +02:00
QJsonParseError json_error ;
QJsonDocument json_doc = QJsonDocument : : fromJson ( data , & json_error ) ;
2020-10-02 19:27:47 +02:00
if ( json_error . error = = QJsonParseError : : NoError & & ! json_doc . isEmpty ( ) & & json_doc . isObject ( ) ) {
2020-05-08 18:35:36 +02:00
QJsonObject json_obj = json_doc . object ( ) ;
if ( ! json_obj . isEmpty ( ) & & json_obj . contains ( " error " ) & & json_obj . contains ( " error_description " ) ) {
QString error = json_obj [ " error " ] . toString ( ) ;
QString error_description = json_obj [ " error_description " ] . toString ( ) ;
2021-03-21 04:43:55 +01:00
login_errors_ < < QString ( " Authentication failure: %1 (%2) " ) . arg ( error , error_description ) ;
2020-05-08 18:35:36 +02:00
}
}
if ( login_errors_ . isEmpty ( ) ) {
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
login_errors_ < < QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
}
else {
login_errors_ < < QString ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ;
}
}
AuthError ( ) ;
return ;
}
}
2021-12-19 20:59:38 +01:00
QByteArray data = reply - > readAll ( ) ;
2020-05-08 18:35:36 +02:00
QJsonParseError json_error ;
QJsonDocument json_doc = QJsonDocument : : fromJson ( data , & json_error ) ;
if ( json_error . error ! = QJsonParseError : : NoError ) {
2020-05-10 12:48:48 +02:00
Error ( QString ( " Failed to parse Json data in authentication reply: %1 " ) . arg ( json_error . errorString ( ) ) ) ;
2020-05-08 18:35:36 +02:00
return ;
}
if ( json_doc . isEmpty ( ) ) {
AuthError ( " Authentication reply from server has empty Json document. " ) ;
return ;
}
if ( ! json_doc . isObject ( ) ) {
AuthError ( " Authentication reply from server has Json document that is not an object. " , json_doc ) ;
return ;
}
QJsonObject json_obj = json_doc . object ( ) ;
if ( json_obj . isEmpty ( ) ) {
AuthError ( " Authentication reply from server has empty Json object. " , json_doc ) ;
return ;
}
if ( ! json_obj . contains ( " access_token " ) ) {
AuthError ( " Authentication reply from server is missing access token. " , json_obj ) ;
return ;
}
access_token_ = json_obj [ " access_token " ] . toString ( ) ;
QSettings s ;
s . beginGroup ( kSettingsGroup ) ;
s . setValue ( " access_token " , access_token_ ) ;
s . endGroup ( ) ;
2021-06-04 00:15:35 +02:00
qLog ( Debug ) < < " Genius: Authentication was successful. " ;
2020-05-08 18:35:36 +02:00
emit AuthenticationComplete ( true ) ;
emit AuthenticationSuccess ( ) ;
}
2021-10-30 02:21:29 +02:00
bool GeniusLyricsProvider : : StartSearch ( const QString & artist , const QString & album , const QString & title , const int id ) {
2020-05-08 18:35:36 +02:00
Q_UNUSED ( album ) ;
if ( access_token_ . isEmpty ( ) ) return false ;
std : : shared_ptr < GeniusLyricsSearchContext > search = std : : make_shared < GeniusLyricsSearchContext > ( ) ;
search - > id = id ;
search - > artist = artist ;
search - > title = title ;
requests_search_ . insert ( id , search ) ;
const ParamList params = ParamList ( ) < < Param ( " q " , QString ( artist + " " + title ) ) ;
QUrlQuery url_query ;
for ( const Param & param : params ) {
url_query . addQueryItem ( QUrl : : toPercentEncoding ( param . first ) , QUrl : : toPercentEncoding ( param . second ) ) ;
}
QUrl url ( kUrlSearch ) ;
url . setQuery ( url_query ) ;
QNetworkRequest req ( url ) ;
2020-08-14 20:20:41 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
# else
2020-05-08 18:35:36 +02:00
req . setAttribute ( QNetworkRequest : : FollowRedirectsAttribute , true ) ;
2020-08-14 20:20:41 +02:00
# endif
2020-05-08 18:35:36 +02:00
req . setRawHeader ( " Authorization " , " Bearer " + access_token_ . toUtf8 ( ) ) ;
QNetworkReply * reply = network_ - > get ( req ) ;
2020-05-12 21:28:42 +02:00
replies_ < < reply ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , id ] ( ) { HandleSearchReply ( reply , id ) ; } ) ;
2020-05-08 18:35:36 +02:00
return true ;
}
2021-10-30 02:21:29 +02:00
void GeniusLyricsProvider : : CancelSearch ( const int id ) { Q_UNUSED ( id ) ; }
2020-05-08 18:35:36 +02:00
2021-10-30 02:21:29 +02:00
void GeniusLyricsProvider : : HandleSearchReply ( QNetworkReply * reply , const int id ) {
2020-05-08 18:35:36 +02:00
2020-05-12 21:28:42 +02:00
if ( ! replies_ . contains ( reply ) ) return ;
replies_ . removeAll ( reply ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2020-05-08 18:35:36 +02:00
reply - > deleteLater ( ) ;
if ( ! requests_search_ . contains ( id ) ) return ;
std : : shared_ptr < GeniusLyricsSearchContext > search = requests_search_ . value ( id ) ;
QJsonObject json_obj = ExtractJsonObj ( reply ) ;
if ( json_obj . isEmpty ( ) ) {
EndSearch ( search ) ;
return ;
}
if ( ! json_obj . contains ( " meta " ) ) {
2020-05-10 12:48:48 +02:00
Error ( " Json reply is missing meta object. " , json_obj ) ;
2020-05-08 18:35:36 +02:00
EndSearch ( search ) ;
return ;
}
if ( ! json_obj [ " meta " ] . isObject ( ) ) {
Error ( " Json reply meta is not an object. " , json_obj ) ;
EndSearch ( search ) ;
return ;
}
QJsonObject obj_meta = json_obj [ " meta " ] . toObject ( ) ;
if ( ! obj_meta . contains ( " status " ) ) {
Error ( " Json reply meta object is missing status. " , obj_meta ) ;
EndSearch ( search ) ;
return ;
}
int status = obj_meta [ " status " ] . toInt ( ) ;
if ( status ! = 200 ) {
if ( obj_meta . contains ( " message " ) ) {
Error ( QString ( " Received error %1: %2. " ) . arg ( status ) . arg ( obj_meta [ " message " ] . toString ( ) ) ) ;
}
else {
Error ( QString ( " Received error %1. " ) . arg ( status ) ) ;
}
EndSearch ( search ) ;
return ;
}
if ( ! json_obj . contains ( " response " ) ) {
Error ( " Json reply is missing response. " , json_obj ) ;
EndSearch ( search ) ;
return ;
}
if ( ! json_obj [ " response " ] . isObject ( ) ) {
Error ( " Json response is not an object. " , json_obj ) ;
EndSearch ( search ) ;
return ;
}
QJsonObject obj_response = json_obj [ " response " ] . toObject ( ) ;
if ( ! obj_response . contains ( " hits " ) ) {
Error ( " Json response is missing hits. " , obj_response ) ;
EndSearch ( search ) ;
return ;
}
if ( ! obj_response [ " hits " ] . isArray ( ) ) {
Error ( " Json hits is not an array. " , obj_response ) ;
EndSearch ( search ) ;
return ;
}
QJsonArray array_hits = obj_response [ " hits " ] . toArray ( ) ;
2021-03-26 23:55:55 +01:00
for ( const QJsonValueRef value_hit : array_hits ) {
2020-05-08 18:35:36 +02:00
if ( ! value_hit . isObject ( ) ) {
continue ;
}
QJsonObject obj_hit = value_hit . toObject ( ) ;
if ( ! obj_hit . contains ( " result " ) ) {
continue ;
}
if ( ! obj_hit [ " result " ] . isObject ( ) ) {
continue ;
}
QJsonObject obj_result = obj_hit [ " result " ] . toObject ( ) ;
if ( ! obj_result . contains ( " title " ) | | ! obj_result . contains ( " primary_artist " ) | | ! obj_result . contains ( " url " ) | | ! obj_result [ " primary_artist " ] . isObject ( ) ) {
Error ( " Missing one or more values in result object " , obj_result ) ;
continue ;
}
QJsonObject primary_artist = obj_result [ " primary_artist " ] . toObject ( ) ;
if ( ! primary_artist . contains ( " name " ) ) continue ;
QString artist = primary_artist [ " name " ] . toString ( ) ;
QString title = obj_result [ " title " ] . toString ( ) ;
// Ignore results where both the artist and title don't match.
2021-07-13 23:18:12 +02:00
if ( artist . compare ( search - > artist , Qt : : CaseInsensitive ) ! = 0 & & title . compare ( search - > title , Qt : : CaseInsensitive ) ! = 0 ) continue ;
2020-05-08 18:35:36 +02:00
QUrl url ( obj_result [ " url " ] . toString ( ) ) ;
if ( ! url . isValid ( ) ) continue ;
if ( search - > requests_lyric_ . contains ( url ) ) continue ;
GeniusLyricsLyricContext lyric ;
lyric . artist = artist ;
lyric . title = title ;
lyric . url = url ;
search - > requests_lyric_ . insert ( url , lyric ) ;
QNetworkRequest req ( url ) ;
2020-08-14 20:20:41 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
# else
2020-05-08 18:35:36 +02:00
req . setAttribute ( QNetworkRequest : : FollowRedirectsAttribute , true ) ;
2020-08-14 20:20:41 +02:00
# endif
2020-05-08 18:35:36 +02:00
QNetworkReply * new_reply = network_ - > get ( req ) ;
2020-05-12 21:28:42 +02:00
replies_ < < new_reply ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( new_reply , & QNetworkReply : : finished , this , [ this , new_reply , search , url ] ( ) { HandleLyricReply ( new_reply , search - > id , url ) ; } ) ;
2020-05-08 18:35:36 +02:00
}
EndSearch ( search ) ;
}
void GeniusLyricsProvider : : HandleLyricReply ( QNetworkReply * reply , const int search_id , const QUrl & url ) {
2020-05-12 21:28:42 +02:00
if ( ! replies_ . contains ( reply ) ) return ;
replies_ . removeAll ( reply ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2020-05-08 18:35:36 +02:00
reply - > deleteLater ( ) ;
if ( ! requests_search_ . contains ( search_id ) ) return ;
std : : shared_ptr < GeniusLyricsSearchContext > search = requests_search_ . value ( search_id ) ;
if ( ! search - > requests_lyric_ . contains ( url ) ) {
EndSearch ( search ) ;
return ;
}
const GeniusLyricsLyricContext lyric = search - > requests_lyric_ . value ( url ) ;
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
Error ( QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ) ;
EndSearch ( search , lyric ) ;
return ;
}
else if ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ! = 200 ) {
Error ( QString ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ) ;
EndSearch ( search , lyric ) ;
return ;
}
QByteArray data = reply - > readAll ( ) ;
if ( data . isEmpty ( ) ) {
Error ( " Empty reply received from server. " ) ;
EndSearch ( search , lyric ) ;
return ;
}
2020-11-04 18:16:23 +01:00
QString content = data ;
2020-05-08 18:35:36 +02:00
// Extract the lyrics from HTML.
QString tag_begin = " <div class= \" lyrics \" > " ;
QString tag_end = " </div> " ;
2021-10-30 02:21:29 +02:00
qint64 begin_idx = content . indexOf ( tag_begin ) ;
2020-05-08 18:35:36 +02:00
QString lyrics ;
if ( begin_idx > 0 ) {
begin_idx + = tag_begin . length ( ) ;
2021-10-30 02:21:29 +02:00
qint64 end_idx = content . indexOf ( tag_end , begin_idx ) ;
2021-12-19 20:59:38 +01:00
if ( end_idx > 0 ) {
QString text = content . mid ( begin_idx , end_idx - begin_idx ) ;
text = text . replace ( QRegularExpression ( " <br[^>]+> " ) , " \n " ) ;
text = text . remove ( QRegularExpression ( " <[^>]*> " ) ) ;
text = text . trimmed ( ) ;
if ( text . length ( ) < 6000 ) {
lyrics = text ;
}
}
}
else {
QRegularExpressionMatch rematch = QRegularExpression ( " <div data-lyrics-container=[^>]+> " ) . match ( content ) ;
if ( rematch . hasMatch ( ) ) {
begin_idx = content . indexOf ( rematch . captured ( ) ) ;
if ( begin_idx > 0 ) {
qint64 end_idx = content . indexOf ( " </div> " , begin_idx + rematch . captured ( ) . length ( ) ) ;
if ( end_idx > 0 ) {
QString text = content . mid ( begin_idx , end_idx - begin_idx ) ;
text = text . replace ( QRegularExpression ( " <br[^>]+> " ) , " \n " ) ;
text = text . remove ( QRegularExpression ( " <[^>]*> " ) ) ;
text = text . trimmed ( ) ;
if ( text . length ( ) < 6000 & & ! text . contains ( " there are no lyrics to " , Qt : : CaseInsensitive ) ) {
lyrics = text ;
}
}
}
}
2020-05-08 18:35:36 +02:00
}
if ( ! lyrics . isEmpty ( ) ) {
LyricsSearchResult result ;
result . artist = lyric . artist ;
result . title = lyric . title ;
2021-01-11 16:05:39 +01:00
result . lyrics = Utilities : : DecodeHtmlEntities ( lyrics ) ;
2020-05-08 18:35:36 +02:00
search - > results . append ( result ) ;
}
EndSearch ( search , lyric ) ;
}
void GeniusLyricsProvider : : AuthError ( const QString & error , const QVariant & debug ) {
if ( ! error . isEmpty ( ) ) login_errors_ < < error ;
2020-05-09 02:07:51 +02:00
for ( const QString & e : login_errors_ ) Error ( e ) ;
2020-05-08 18:35:36 +02:00
if ( debug . isValid ( ) ) qLog ( Debug ) < < debug ;
emit AuthenticationFailure ( login_errors_ ) ;
emit AuthenticationComplete ( false , login_errors_ ) ;
login_errors_ . clear ( ) ;
}
void GeniusLyricsProvider : : Error ( const QString & error , const QVariant & debug ) {
qLog ( Error ) < < " GeniusLyrics: " < < error ;
if ( debug . isValid ( ) ) qLog ( Debug ) < < debug ;
}
2021-06-20 19:04:08 +02:00
void GeniusLyricsProvider : : EndSearch ( std : : shared_ptr < GeniusLyricsSearchContext > search , const GeniusLyricsLyricContext & lyric ) {
2020-05-08 18:35:36 +02:00
if ( search - > requests_lyric_ . contains ( lyric . url ) ) {
search - > requests_lyric_ . remove ( lyric . url ) ;
}
if ( search - > requests_lyric_ . count ( ) = = 0 ) {
requests_search_ . remove ( search - > id ) ;
if ( search - > results . isEmpty ( ) ) {
qLog ( Debug ) < < " GeniusLyrics: No lyrics for " < < search - > artist < < search - > title ;
}
else {
qLog ( Debug ) < < " GeniusLyrics: Got lyrics for " < < search - > artist < < search - > title ;
}
emit SearchFinished ( search - > id , search - > results ) ;
}
}