2018-12-23 18:54:27 +01:00
/*
* Strawberry Music Player
2021-03-20 21:14:47 +01:00
* Copyright 2018 - 2021 , Jonas Kvinge < jonas @ jkvinge . net >
2018-12-23 18:54:27 +01: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-02-09 02:29:35 +01:00
# include <algorithm>
2018-12-23 18:54:27 +01:00
# include <QtGlobal>
2018-12-24 00:15:53 +01:00
# include <QApplication>
2018-12-23 18:54:27 +01:00
# include <QDesktopServices>
2020-02-09 02:29:35 +01:00
# include <QLocale>
2018-12-24 00:15:53 +01:00
# include <QClipboard>
2020-02-09 02:29:35 +01:00
# include <QPair>
2018-12-23 18:54:27 +01:00
# include <QVariant>
# include <QByteArray>
# include <QString>
# include <QUrl>
# include <QUrlQuery>
# include <QDateTime>
2021-01-30 21:50:28 +01:00
# include <QTimer>
2018-12-23 18:54:27 +01:00
# include <QCryptographicHash>
# include <QMessageBox>
# include <QSettings>
# include <QNetworkRequest>
# include <QNetworkReply>
# include <QJsonDocument>
# include <QJsonObject>
# include <QJsonArray>
# include <QJsonValue>
2020-02-09 02:29:35 +01:00
# include <QFlags>
# include <QtDebug>
2018-12-23 18:54:27 +01:00
# include "core/application.h"
2021-01-11 16:48:46 +01:00
# include "core/networkaccessmanager.h"
2018-12-23 18:54:27 +01:00
# include "core/song.h"
# include "core/timeconstants.h"
# include "core/logging.h"
# include "internet/localredirectserver.h"
# include "settings/scrobblersettingspage.h"
# include "audioscrobbler.h"
2020-02-09 02:29:35 +01:00
# include "scrobblerservice.h"
2018-12-23 18:54:27 +01:00
# include "scrobblingapi20.h"
# include "scrobblercache.h"
# include "scrobblercacheitem.h"
const char * ScrobblingAPI20 : : kApiKey = " 211990b4c96782c05d1536e7219eb56e " ;
const char * ScrobblingAPI20 : : kSecret = " 80fd738f49596e9709b1bf9319c444a8 " ;
const int ScrobblingAPI20 : : kScrobblesPerRequest = 50 ;
2021-07-11 07:40:57 +02:00
ScrobblingAPI20 : : ScrobblingAPI20 ( const QString & name , const QString & settings_group , const QString & auth_url , const QString & api_url , const bool batch , Application * app , QObject * parent )
: ScrobblerService ( name , app , parent ) ,
name_ ( name ) ,
settings_group_ ( settings_group ) ,
auth_url_ ( auth_url ) ,
api_url_ ( api_url ) ,
batch_ ( batch ) ,
app_ ( app ) ,
server_ ( nullptr ) ,
enabled_ ( false ) ,
https_ ( false ) ,
prefer_albumartist_ ( false ) ,
subscriber_ ( false ) ,
submitted_ ( false ) ,
scrobbled_ ( false ) ,
timestamp_ ( 0 ) {
2021-01-30 21:50:28 +01:00
timer_submit_ . setSingleShot ( true ) ;
QObject : : connect ( & timer_submit_ , & QTimer : : timeout , this , & ScrobblingAPI20 : : Submit ) ;
}
2018-12-23 18:54:27 +01:00
2020-05-12 21:28:42 +02:00
ScrobblingAPI20 : : ~ ScrobblingAPI20 ( ) {
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 ( ) ;
}
if ( server_ ) {
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( server_ , nullptr , this , nullptr ) ;
2020-05-12 21:28:42 +02:00
if ( server_ - > isListening ( ) ) server_ - > close ( ) ;
server_ - > deleteLater ( ) ;
}
}
2018-12-23 18:54:27 +01:00
void ScrobblingAPI20 : : ReloadSettings ( ) {
QSettings s ;
2019-10-19 02:56:23 +02:00
2018-12-23 18:54:27 +01:00
s . beginGroup ( settings_group_ ) ;
enabled_ = s . value ( " enabled " , false ) . toBool ( ) ;
2019-04-15 22:17:40 +02:00
https_ = s . value ( " https " , false ) . toBool ( ) ;
2018-12-23 18:54:27 +01:00
s . endGroup ( ) ;
2019-10-19 02:56:23 +02:00
s . beginGroup ( ScrobblerSettingsPage : : kSettingsGroup ) ;
prefer_albumartist_ = s . value ( " albumartist " , false ) . toBool ( ) ;
s . endGroup ( ) ;
2018-12-23 18:54:27 +01:00
}
void ScrobblingAPI20 : : LoadSession ( ) {
QSettings s ;
s . beginGroup ( settings_group_ ) ;
subscriber_ = s . value ( " subscriber " , false ) . toBool ( ) ;
username_ = s . value ( " username " ) . toString ( ) ;
session_key_ = s . value ( " session_key " ) . toString ( ) ;
s . endGroup ( ) ;
}
void ScrobblingAPI20 : : Logout ( ) {
subscriber_ = false ;
username_ . clear ( ) ;
session_key_ . clear ( ) ;
QSettings settings ;
settings . beginGroup ( settings_group_ ) ;
settings . remove ( " subscriber " ) ;
settings . remove ( " username " ) ;
settings . remove ( " session_key " ) ;
settings . endGroup ( ) ;
}
2019-04-15 22:17:40 +02:00
void ScrobblingAPI20 : : Authenticate ( const bool https ) {
2018-12-23 18:54:27 +01:00
2019-11-14 21:07:30 +01:00
if ( ! server_ ) {
2020-05-08 18:35:36 +02:00
server_ = new LocalRedirectServer ( this ) ;
server_ - > set_https ( https ) ;
2019-11-14 21:07:30 +01:00
if ( ! server_ - > Listen ( ) ) {
AuthError ( server_ - > error ( ) ) ;
delete server_ ;
server_ = nullptr ;
return ;
}
2021-01-26 16:48:04 +01:00
QObject : : connect ( server_ , & LocalRedirectServer : : Finished , this , & ScrobblingAPI20 : : RedirectArrived ) ;
2019-04-15 22:17:40 +02:00
}
2018-12-23 18:54:27 +01:00
QUrlQuery url_query ;
url_query . addQueryItem ( " api_key " , kApiKey ) ;
2021-06-25 16:04:19 +02:00
url_query . addQueryItem ( " cb " , server_ - > url ( ) . toString ( ) ) ;
2019-04-16 17:48:11 +02:00
QUrl url ( auth_url_ ) ;
2018-12-23 18:54:27 +01:00
url . setQuery ( url_query ) ;
2019-12-29 23:53:54 +01:00
QMessageBox messagebox ( QMessageBox : : Information , tr ( " %1 Scrobbler Authentication " ) . arg ( name_ ) , tr ( " Open URL in web browser? " ) + QString ( " <br /><a href= \" %1 \" >%1</a><br /> " ) . arg ( url . toString ( ) ) + tr ( " Press \" Save \" to copy the URL to clipboard and manually open it in a web browser. " ) , QMessageBox : : Open | QMessageBox : : Save | QMessageBox : : Cancel ) ;
2018-12-24 00:15:53 +01:00
messagebox . setTextFormat ( Qt : : RichText ) ;
int result = messagebox . exec ( ) ;
switch ( result ) {
case QMessageBox : : Open : {
bool openurl_result = QDesktopServices : : openUrl ( url ) ;
if ( openurl_result ) {
break ;
}
2019-12-29 23:37:48 +01:00
QMessageBox messagebox_error ( QMessageBox : : Warning , tr ( " %1 Scrobbler Authentication " ) . arg ( name_ ) , tr ( " Could not open URL. Please open this URL in your browser " ) + QString ( " :<br /><a href= \" %1 \" >%1</a> " ) . arg ( url . toString ( ) ) , QMessageBox : : Ok ) ;
2018-12-24 00:15:53 +01:00
messagebox_error . setTextFormat ( Qt : : RichText ) ;
messagebox_error . exec ( ) ;
}
2019-09-16 21:20:12 +02:00
// fallthrough
2018-12-24 00:15:53 +01:00
case QMessageBox : : Save :
QApplication : : clipboard ( ) - > setText ( url . toString ( ) ) ;
break ;
case QMessageBox : : Cancel :
2019-11-14 21:07:30 +01:00
if ( server_ ) {
server_ - > close ( ) ;
server_ - > deleteLater ( ) ;
server_ = nullptr ;
}
2019-04-15 22:17:40 +02:00
emit AuthenticationComplete ( false ) ;
2018-12-24 00:15:53 +01:00
break ;
default :
break ;
2018-12-23 18:54:27 +01:00
}
}
2019-11-14 21:07:30 +01:00
void ScrobblingAPI20 : : RedirectArrived ( ) {
2018-12-23 18:54:27 +01:00
2019-11-14 21:07:30 +01:00
if ( ! server_ ) return ;
2018-12-23 18:54:27 +01:00
2019-11-14 21:07:30 +01:00
if ( server_ - > error ( ) . isEmpty ( ) ) {
QUrl url = server_ - > request_url ( ) ;
if ( url . isValid ( ) ) {
QUrlQuery url_query ( url ) ;
if ( url_query . hasQueryItem ( " token " ) ) {
QString token = url_query . queryItemValue ( " token " ) . toUtf8 ( ) ;
RequestSession ( token ) ;
}
else {
AuthError ( tr ( " Invalid reply from web browser. Missing token. " ) ) ;
}
}
else {
AuthError ( tr ( " Received invalid reply from web browser. Try the HTTPS option, or use another browser like Chromium or Chrome. " ) ) ;
}
2019-04-15 22:17:40 +02:00
}
2019-11-14 21:07:30 +01:00
else {
AuthError ( server_ - > error ( ) ) ;
2019-04-15 22:17:40 +02:00
}
2019-11-14 21:07:30 +01:00
server_ - > close ( ) ;
server_ - > deleteLater ( ) ;
server_ = nullptr ;
2018-12-23 18:54:27 +01:00
}
2019-11-14 21:07:30 +01:00
void ScrobblingAPI20 : : RequestSession ( const QString & token ) {
2018-12-23 18:54:27 +01:00
QUrl session_url ( api_url_ ) ;
QUrlQuery session_url_query ;
session_url_query . addQueryItem ( " api_key " , kApiKey ) ;
session_url_query . addQueryItem ( " method " , " auth.getSession " ) ;
session_url_query . addQueryItem ( " token " , token ) ;
QString data_to_sign ;
2021-03-21 04:43:55 +01:00
for ( const QPair < QString , QString > & param : session_url_query . queryItems ( ) ) {
2018-12-23 18:54:27 +01:00
data_to_sign + = param . first + param . second ;
}
data_to_sign + = kSecret ;
QByteArray const digest = QCryptographicHash : : hash ( data_to_sign . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
QString signature = QString : : fromLatin1 ( digest . toHex ( ) ) . rightJustified ( 32 , ' 0 ' ) . toLower ( ) ;
session_url_query . addQueryItem ( " api_sig " , signature ) ;
session_url_query . addQueryItem ( QUrl : : toPercentEncoding ( " format " ) , QUrl : : toPercentEncoding ( " json " ) ) ;
session_url . setQuery ( session_url_query ) ;
QNetworkRequest req ( session_url ) ;
2020-08-14 20:20:41 +02:00
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
2018-12-23 18:54:27 +01:00
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 ] ( ) { AuthenticateReplyFinished ( reply ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
void ScrobblingAPI20 : : AuthenticateReplyFinished ( 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 ) ;
2018-12-23 18:54:27 +01:00
reply - > deleteLater ( ) ;
QByteArray data ;
if ( reply - > error ( ) = = QNetworkReply : : NoError & & reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) = = 200 ) {
data = reply - > readAll ( ) ;
}
else {
if ( reply - > error ( ) < 200 ) {
// This is a network error, there is nothing more to do.
2019-07-07 21:14:24 +02:00
AuthError ( QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ) ;
2018-12-23 18:54:27 +01:00
}
else {
// See if there is Json data containing "error" and "message" - then use that instead.
data = reply - > readAll ( ) ;
2019-07-07 21:14:24 +02:00
QString error ;
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 ( ) ) {
2018-12-23 18:54:27 +01:00
QJsonObject json_obj = json_doc . object ( ) ;
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
2019-07-07 21:14:24 +02:00
int code = json_obj [ " error " ] . toInt ( ) ;
2018-12-23 18:54:27 +01:00
QString message = json_obj [ " message " ] . toString ( ) ;
2019-07-07 21:14:24 +02:00
error = " Error: " + QString : : number ( code ) + " : " + message ;
2018-12-23 18:54:27 +01:00
}
else {
2019-07-07 21:14:24 +02:00
error = QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
2018-12-23 18:54:27 +01:00
}
}
2019-07-07 21:14:24 +02:00
if ( error . isEmpty ( ) ) {
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
error = QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
}
else {
error = QString ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ;
}
2018-12-23 18:54:27 +01:00
}
2019-07-07 21:14:24 +02:00
AuthError ( error ) ;
2018-12-23 18:54:27 +01:00
}
return ;
}
2021-07-11 07:40:57 +02:00
2018-12-23 18:54:27 +01:00
QJsonObject json_obj = ExtractJsonObj ( data ) ;
if ( json_obj . isEmpty ( ) ) {
AuthError ( " Json document from server was empty. " ) ;
return ;
}
2021-07-11 07:40:57 +02:00
2018-12-23 18:54:27 +01:00
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
int error = json_obj [ " error " ] . toInt ( ) ;
QString message = json_obj [ " message " ] . toString ( ) ;
QString failure_reason = " Error: " + QString : : number ( error ) + " : " + message ;
AuthError ( failure_reason ) ;
return ;
}
if ( ! json_obj . contains ( " session " ) ) {
AuthError ( " Json reply from server is missing session. " ) ;
return ;
}
QJsonValue json_session = json_obj [ " session " ] ;
if ( ! json_session . isObject ( ) ) {
AuthError ( " Json session is not an object. " ) ;
return ;
}
json_obj = json_session . toObject ( ) ;
if ( json_obj . isEmpty ( ) ) {
AuthError ( " Json session object is empty. " ) ;
return ;
}
if ( ! json_obj . contains ( " subscriber " ) | | ! json_obj . contains ( " name " ) | | ! json_obj . contains ( " key " ) ) {
AuthError ( " Json session object is missing values. " ) ;
return ;
}
subscriber_ = json_obj [ " subscriber " ] . toBool ( ) ;
username_ = json_obj [ " name " ] . toString ( ) ;
session_key_ = json_obj [ " key " ] . toString ( ) ;
2021-07-11 07:40:57 +02:00
2018-12-23 18:54:27 +01:00
QSettings s ;
s . beginGroup ( settings_group_ ) ;
s . setValue ( " subscriber " , subscriber_ ) ;
s . setValue ( " username " , username_ ) ;
s . setValue ( " session_key " , session_key_ ) ;
s . endGroup ( ) ;
emit AuthenticationComplete ( true ) ;
2018-12-26 01:45:28 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
}
QNetworkReply * ScrobblingAPI20 : : CreateRequest ( const ParamList & request_params ) {
ParamList params = ParamList ( )
< < Param ( " api_key " , kApiKey )
< < Param ( " sk " , session_key_ )
< < Param ( " lang " , QLocale ( ) . name ( ) . left ( 2 ) . toLower ( ) )
< < request_params ;
std : : sort ( params . begin ( ) , params . end ( ) ) ;
QUrlQuery url_query ;
QString data_to_sign ;
for ( const Param & param : params ) {
EncodedParam encoded_param ( QUrl : : toPercentEncoding ( param . first ) , QUrl : : toPercentEncoding ( param . second ) ) ;
url_query . addQueryItem ( encoded_param . first , encoded_param . second ) ;
data_to_sign + = param . first + param . second ;
}
data_to_sign + = kSecret ;
QByteArray const digest = QCryptographicHash : : hash ( data_to_sign . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
QString signature = QString : : fromLatin1 ( digest . toHex ( ) ) . rightJustified ( 32 , ' 0 ' ) . toLower ( ) ;
url_query . addQueryItem ( " api_sig " , QUrl : : toPercentEncoding ( signature ) ) ;
url_query . addQueryItem ( " format " , QUrl : : toPercentEncoding ( " json " ) ) ;
QUrl url ( api_url_ ) ;
QNetworkRequest req ( url ) ;
2020-08-14 20:20:41 +02:00
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
2018-12-23 18:54:27 +01:00
req . setHeader ( QNetworkRequest : : ContentTypeHeader , " application/x-www-form-urlencoded " ) ;
QByteArray query = url_query . toString ( QUrl : : FullyEncoded ) . toUtf8 ( ) ;
QNetworkReply * reply = network ( ) - > post ( req , query ) ;
2020-05-12 21:28:42 +02:00
replies_ < < reply ;
2018-12-23 18:54:27 +01:00
2020-07-21 03:14:02 +02:00
//qLog(Debug) << name_ << "Sending request" << url_query.toString(QUrl::FullyDecoded);
2018-12-23 18:54:27 +01:00
return reply ;
}
QByteArray ScrobblingAPI20 : : GetReplyData ( QNetworkReply * reply ) {
QByteArray data ;
if ( reply - > error ( ) = = QNetworkReply : : NoError & & reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) = = 200 ) {
data = reply - > readAll ( ) ;
}
else {
2019-07-07 21:14:24 +02:00
if ( reply - > error ( ) ! = QNetworkReply : : NoError & & reply - > error ( ) < 200 ) {
2018-12-23 18:54:27 +01:00
// This is a network error, there is nothing more to do.
2019-07-07 21:14:24 +02:00
Error ( QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ) ;
2018-12-23 18:54:27 +01:00
}
else {
2019-07-07 21:14:24 +02:00
QString error ;
2018-12-23 18:54:27 +01:00
// See if there is Json data containing "error" and "message" - then use that instead.
data = reply - > readAll ( ) ;
2019-07-07 21:14:24 +02:00
QJsonParseError json_error ;
QJsonDocument json_doc = QJsonDocument : : fromJson ( data , & json_error ) ;
2018-12-23 18:54:27 +01:00
int error_code = - 1 ;
2020-10-02 19:27:47 +02:00
if ( json_error . error = = QJsonParseError : : NoError & & ! json_doc . isEmpty ( ) & & json_doc . isObject ( ) ) {
2018-12-23 18:54:27 +01:00
QJsonObject json_obj = json_doc . object ( ) ;
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
error_code = json_obj [ " error " ] . toInt ( ) ;
2018-12-26 01:17:17 +01:00
QString error_message = json_obj [ " message " ] . toString ( ) ;
2019-07-07 21:14:24 +02:00
error = QString ( " %1 (%2) " ) . arg ( error_message ) . arg ( error_code ) ;
}
}
if ( error . isEmpty ( ) ) {
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
error = QString ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
2018-12-23 18:54:27 +01:00
}
else {
2019-07-07 21:14:24 +02:00
error = QString ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ;
2018-12-23 18:54:27 +01:00
}
}
if ( reply - > error ( ) = = QNetworkReply : : ContentAccessDenied | |
reply - > error ( ) = = QNetworkReply : : ContentOperationNotPermittedError | |
reply - > error ( ) = = QNetworkReply : : AuthenticationRequiredError | |
error_code = = ScrobbleErrorCode : : InvalidSessionKey | |
2020-07-19 03:47:21 +02:00
error_code = = ScrobbleErrorCode : : UnauthorizedToken | |
error_code = = ScrobbleErrorCode : : LoginRequired | |
error_code = = ScrobbleErrorCode : : AuthenticationFailed | |
error_code = = ScrobbleErrorCode : : APIKeySuspended
2018-12-23 18:54:27 +01:00
) {
// Session is probably expired
Logout ( ) ;
}
2019-07-07 21:14:24 +02:00
Error ( error ) ;
2018-12-23 18:54:27 +01:00
}
return QByteArray ( ) ;
}
return data ;
2019-04-15 22:17:40 +02:00
2018-12-23 18:54:27 +01:00
}
void ScrobblingAPI20 : : UpdateNowPlaying ( const Song & song ) {
2020-04-25 00:07:42 +02:00
CheckScrobblePrevSong ( ) ;
2018-12-23 18:54:27 +01:00
song_playing_ = song ;
2020-07-18 03:53:30 +02:00
timestamp_ = QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) ;
2020-04-25 00:07:42 +02:00
scrobbled_ = false ;
2018-12-23 18:54:27 +01:00
2019-04-04 20:16:26 +02:00
if ( ! IsAuthenticated ( ) | | ! song . is_metadata_good ( ) | | app_ - > scrobbler ( ) - > IsOffline ( ) ) return ;
2018-12-23 18:54:27 +01:00
2018-12-26 13:33:56 +01:00
QString album = song . album ( ) ;
2021-05-01 18:19:08 +02:00
QString title = song . title ( ) ;
2019-01-22 22:49:48 +01:00
album = album . remove ( Song : : kAlbumRemoveDisc ) ;
album = album . remove ( Song : : kAlbumRemoveMisc ) ;
2021-05-01 18:19:08 +02:00
title = title . remove ( Song : : kTitleRemoveMisc ) ;
2018-12-26 13:33:56 +01:00
2018-12-23 18:54:27 +01:00
ParamList params = ParamList ( )
< < Param ( " method " , " track.updateNowPlaying " )
2019-11-20 21:30:41 +01:00
< < Param ( " artist " , prefer_albumartist_ & & song . effective_albumartist ( ) ! = Song : : kVariousArtists ? song . effective_albumartist ( ) : song . artist ( ) )
2021-05-01 18:19:08 +02:00
< < Param ( " track " , title ) ;
2019-10-19 02:56:23 +02:00
2021-07-13 23:18:12 +02:00
if ( ! album . isEmpty ( ) ) {
2019-11-20 21:30:41 +01:00
params < < Param ( " album " , album ) ;
2021-07-13 23:18:12 +02:00
}
2019-11-20 21:30:41 +01:00
2021-07-13 23:18:12 +02:00
if ( ! prefer_albumartist_ & & ! song . albumartist ( ) . isEmpty ( ) & & song . albumartist ( ) . compare ( Song : : kVariousArtists , Qt : : CaseInsensitive ) ! = 0 ) {
2019-11-20 21:30:41 +01:00
params < < Param ( " albumArtist " , song . albumartist ( ) ) ;
2021-07-13 23:18:12 +02:00
}
2018-12-23 18:54:27 +01:00
QNetworkReply * reply = CreateRequest ( params ) ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply ] ( ) { UpdateNowPlayingRequestFinished ( reply ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
void ScrobblingAPI20 : : UpdateNowPlayingRequestFinished ( 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 ) ;
2018-12-23 18:54:27 +01:00
reply - > deleteLater ( ) ;
QByteArray data = GetReplyData ( reply ) ;
if ( data . isEmpty ( ) ) {
return ;
}
2018-12-26 02:31:32 +01:00
QJsonObject json_obj = ExtractJsonObj ( data ) ;
if ( json_obj . isEmpty ( ) ) {
return ;
}
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
int error_code = json_obj [ " error " ] . toInt ( ) ;
QString error_message = json_obj [ " message " ] . toString ( ) ;
QString error_reason = QString ( " %1 (%2) " ) . arg ( error_message ) . arg ( error_code ) ;
Error ( error_reason ) ;
return ;
}
if ( ! json_obj . contains ( " nowplaying " ) ) {
Error ( " Json reply from server is missing nowplaying. " , json_obj ) ;
return ;
}
2018-12-23 18:54:27 +01:00
}
2019-06-12 00:38:52 +02:00
void ScrobblingAPI20 : : ClearPlaying ( ) {
2020-04-25 00:07:42 +02:00
CheckScrobblePrevSong ( ) ;
2019-06-12 00:38:52 +02:00
song_playing_ = Song ( ) ;
2020-04-25 00:07:42 +02:00
scrobbled_ = false ;
timestamp_ = 0 ;
2019-06-12 00:38:52 +02:00
}
2018-12-23 18:54:27 +01:00
void ScrobblingAPI20 : : Scrobble ( const Song & song ) {
if ( song . id ( ) ! = song_playing_ . id ( ) | | song . url ( ) ! = song_playing_ . url ( ) | | ! song . is_metadata_good ( ) ) return ;
2020-04-25 00:07:42 +02:00
scrobbled_ = true ;
2018-12-23 18:54:27 +01:00
cache ( ) - > Add ( song , timestamp_ ) ;
if ( app_ - > scrobbler ( ) - > IsOffline ( ) ) return ;
2018-12-26 13:33:56 +01:00
2018-12-23 18:54:27 +01:00
if ( ! IsAuthenticated ( ) ) {
2020-05-26 17:51:23 +02:00
if ( app_ - > scrobbler ( ) - > ShowErrorDialog ( ) ) { emit ErrorMessage ( tr ( " Scrobbler %1 is not authenticated! " ) . arg ( name_ ) ) ; }
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! submitted_ ) {
submitted_ = true ;
2018-12-25 22:58:34 +01:00
if ( ! batch_ | | app_ - > scrobbler ( ) - > SubmitDelay ( ) < = 0 ) {
Submit ( ) ;
}
2021-01-30 21:50:28 +01:00
else if ( ! timer_submit_ . isActive ( ) ) {
2021-10-09 14:05:05 +02:00
timer_submit_ . setInterval ( static_cast < int > ( app_ - > scrobbler ( ) - > SubmitDelay ( ) * kMsecPerSec ) ) ;
2021-01-30 21:50:28 +01:00
timer_submit_ . start ( ) ;
2018-12-25 22:58:34 +01:00
}
2018-12-23 18:54:27 +01:00
}
}
2018-12-26 01:17:17 +01:00
void ScrobblingAPI20 : : DoSubmit ( ) {
if ( ! submitted_ & & cache ( ) - > Count ( ) > 0 ) {
submitted_ = true ;
2021-01-30 21:50:28 +01:00
if ( ! timer_submit_ . isActive ( ) ) {
2021-10-09 14:05:05 +02:00
timer_submit_ . setInterval ( static_cast < int > ( app_ - > scrobbler ( ) - > SubmitDelay ( ) * kMsecPerSec ) ) ;
2021-01-30 21:50:28 +01:00
timer_submit_ . start ( ) ;
}
2018-12-26 01:17:17 +01:00
}
}
2018-12-23 18:54:27 +01:00
void ScrobblingAPI20 : : Submit ( ) {
submitted_ = false ;
if ( ! IsEnabled ( ) | | ! IsAuthenticated ( ) | | app_ - > scrobbler ( ) - > IsOffline ( ) ) return ;
2019-06-12 00:38:52 +02:00
qLog ( Debug ) < < name_ < < " Submitting scrobbles. " ;
ParamList params = ParamList ( ) < < Param ( " method " , " track.scrobble " ) ;
2018-12-23 18:54:27 +01:00
2020-07-21 03:14:02 +02:00
int i = 0 ;
2018-12-23 18:54:27 +01:00
QList < quint64 > list ;
2021-06-20 19:04:08 +02:00
QList < ScrobblerCacheItemPtr > items = cache ( ) - > List ( ) ;
2021-08-25 03:45:12 +02:00
for ( ScrobblerCacheItemPtr item : items ) { // clazy:exclude=range-loop-reference
2018-12-23 18:54:27 +01:00
if ( item - > sent_ ) continue ;
item - > sent_ = true ;
2019-11-20 21:30:41 +01:00
if ( ! batch_ ) {
SendSingleScrobble ( item ) ;
continue ;
}
2018-12-23 18:54:27 +01:00
list < < item - > timestamp_ ;
2019-10-19 02:56:23 +02:00
params < < Param ( QString ( " %1[%2] " ) . arg ( " artist " ) . arg ( i ) , prefer_albumartist_ ? item - > effective_albumartist ( ) : item - > artist_ ) ;
2018-12-23 18:54:27 +01:00
params < < Param ( QString ( " %1[%2] " ) . arg ( " track " ) . arg ( i ) , item - > song_ ) ;
params < < Param ( QString ( " %1[%2] " ) . arg ( " timestamp " ) . arg ( i ) , QString : : number ( item - > timestamp_ ) ) ;
params < < Param ( QString ( " %1[%2] " ) . arg ( " duration " ) . arg ( i ) , QString : : number ( item - > duration_ / kNsecPerSec ) ) ;
2021-07-13 23:18:12 +02:00
if ( ! item - > album_ . isEmpty ( ) ) {
2019-11-20 21:30:41 +01:00
params < < Param ( QString ( " %1[%2] " ) . arg ( " album " ) . arg ( i ) , item - > album_ ) ;
2021-07-13 23:18:12 +02:00
}
if ( ! prefer_albumartist_ & & ! item - > albumartist_ . isEmpty ( ) & & item - > albumartist_ . compare ( Song : : kVariousArtists , Qt : : CaseInsensitive ) ! = 0 ) {
2019-11-20 21:30:41 +01:00
params < < Param ( QString ( " %1[%2] " ) . arg ( " albumArtist " ) . arg ( i ) , item - > albumartist_ ) ;
2021-07-13 23:18:12 +02:00
}
if ( item - > track_ > 0 ) {
2020-07-21 03:14:02 +02:00
params < < Param ( QString ( " %1[%2] " ) . arg ( " trackNumber " ) . arg ( i ) , QString : : number ( item - > track_ ) ) ;
2021-07-13 23:18:12 +02:00
}
2020-07-21 03:14:02 +02:00
+ + i ;
2018-12-23 18:54:27 +01:00
if ( i > = kScrobblesPerRequest ) break ;
}
if ( ! batch_ | | i < = 0 ) return ;
QNetworkReply * reply = CreateRequest ( params ) ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , list ] ( ) { ScrobbleRequestFinished ( reply , list ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
2021-06-20 19:04:08 +02:00
void ScrobblingAPI20 : : ScrobbleRequestFinished ( QNetworkReply * reply , const QList < quint64 > & list ) {
2018-12-23 18:54:27 +01: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 ) ;
2018-12-23 18:54:27 +01:00
reply - > deleteLater ( ) ;
QByteArray data = GetReplyData ( reply ) ;
if ( data . isEmpty ( ) ) {
cache ( ) - > ClearSent ( list ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
QJsonObject json_obj = ExtractJsonObj ( data ) ;
if ( json_obj . isEmpty ( ) ) {
cache ( ) - > ClearSent ( list ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
return ;
}
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
int error_code = json_obj [ " error " ] . toInt ( ) ;
QString error_message = json_obj [ " message " ] . toString ( ) ;
QString error_reason = QString ( " %1 (%2) " ) . arg ( error_message ) . arg ( error_code ) ;
Error ( error_reason ) ;
cache ( ) - > ClearSent ( list ) ;
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! json_obj . contains ( " scrobbles " ) ) {
2018-12-26 01:17:17 +01:00
Error ( " Json reply from server is missing scrobbles. " , json_obj ) ;
2018-12-23 18:54:27 +01:00
cache ( ) - > ClearSent ( list ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
cache ( ) - > Flush ( list ) ;
2020-04-25 00:07:42 +02:00
QJsonValue value_scrobbles = json_obj [ " scrobbles " ] ;
if ( ! value_scrobbles . isObject ( ) ) {
2018-12-23 18:54:27 +01:00
Error ( " Json scrobbles is not an object. " , json_obj ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-04-25 00:07:42 +02:00
json_obj = value_scrobbles . toObject ( ) ;
2018-12-23 18:54:27 +01:00
if ( json_obj . isEmpty ( ) ) {
2020-04-25 00:07:42 +02:00
Error ( " Json scrobbles object is empty. " , value_scrobbles ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! json_obj . contains ( " @attr " ) | | ! json_obj . contains ( " scrobble " ) ) {
Error ( " Json scrobbles object is missing values. " , json_obj ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
QJsonValue value_attr = json_obj [ " @attr " ] ;
if ( ! value_attr . isObject ( ) ) {
Error ( " Json scrobbles attr is not an object. " , value_attr ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
QJsonObject obj_attr = value_attr . toObject ( ) ;
if ( obj_attr . isEmpty ( ) ) {
Error ( " Json scrobbles attr is empty. " , value_attr ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
if ( ! obj_attr . contains ( " accepted " ) | | ! obj_attr . contains ( " ignored " ) ) {
Error ( " Json scrobbles attr is missing values. " , obj_attr ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
int accepted = obj_attr [ " accepted " ] . toInt ( ) ;
int ignored = obj_attr [ " ignored " ] . toInt ( ) ;
2018-12-23 18:54:27 +01:00
qLog ( Debug ) < < name_ < < " Scrobbles accepted: " < < accepted < < " ignored: " < < ignored ;
2020-07-21 03:14:02 +02:00
QJsonArray array_scrobble ;
QJsonValue value_scrobble = json_obj [ " scrobble " ] ;
if ( value_scrobble . isObject ( ) ) {
QJsonObject obj_scrobble = value_scrobble . toObject ( ) ;
if ( obj_scrobble . isEmpty ( ) ) {
Error ( " Json scrobbles scrobble object is empty. " , obj_scrobble ) ;
DoSubmit ( ) ;
return ;
}
array_scrobble . append ( obj_scrobble ) ;
2018-12-23 18:54:27 +01:00
}
2020-07-21 03:14:02 +02:00
else if ( value_scrobble . isArray ( ) ) {
array_scrobble = value_scrobble . toArray ( ) ;
if ( array_scrobble . isEmpty ( ) ) {
Error ( " Json scrobbles scrobble array is empty. " , value_scrobble ) ;
DoSubmit ( ) ;
return ;
}
}
else {
Error ( " Json scrobbles scrobble is not an object or array. " , value_scrobble ) ;
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
2021-10-30 02:02:37 +02:00
for ( const QJsonValueRef value : array_scrobble ) { // clazy:exclude=range-loop-detach
2018-12-23 18:54:27 +01:00
if ( ! value . isObject ( ) ) {
2021-03-26 22:10:43 +01:00
Error ( " Json scrobbles scrobble array value is not an object. " ) ;
2018-12-23 18:54:27 +01:00
continue ;
}
QJsonObject json_track = value . toObject ( ) ;
if ( json_track . isEmpty ( ) ) {
continue ;
}
if ( ! json_track . contains ( " artist " ) | |
! json_track . contains ( " album " ) | |
! json_track . contains ( " albumArtist " ) | |
! json_track . contains ( " track " ) | |
! json_track . contains ( " timestamp " ) | |
! json_track . contains ( " ignoredMessage " )
) {
Error ( " Json scrobbles scrobble is missing values. " , json_track ) ;
2018-12-26 01:17:17 +01:00
continue ;
2018-12-23 18:54:27 +01:00
}
2020-07-21 03:14:02 +02:00
QJsonValue value_artist = json_track [ " artist " ] ;
QJsonValue value_album = json_track [ " album " ] ;
QJsonValue value_song = json_track [ " track " ] ;
QJsonValue value_ignoredmessage = json_track [ " ignoredMessage " ] ;
2018-12-23 18:54:27 +01:00
//quint64 timestamp = json_track["timestamp"].toVariant().toULongLong();
2020-07-21 03:14:02 +02:00
if ( ! value_artist . isObject ( ) | | ! value_album . isObject ( ) | | ! value_song . isObject ( ) | | ! value_ignoredmessage . isObject ( ) ) {
2018-12-23 18:54:27 +01:00
Error ( " Json scrobbles scrobble values are not objects. " , json_track ) ;
continue ;
}
2020-07-21 03:14:02 +02:00
QJsonObject obj_artist = value_artist . toObject ( ) ;
QJsonObject obj_album = value_album . toObject ( ) ;
QJsonObject obj_song = value_song . toObject ( ) ;
QJsonObject obj_ignoredmessage = value_ignoredmessage . toObject ( ) ;
2018-12-23 18:54:27 +01:00
2020-07-21 03:14:02 +02:00
if ( obj_artist . isEmpty ( ) | | obj_album . isEmpty ( ) | | obj_song . isEmpty ( ) | | obj_ignoredmessage . isEmpty ( ) ) {
2018-12-23 18:54:27 +01:00
Error ( " Json scrobbles scrobble values objects are empty. " , json_track ) ;
continue ;
}
2020-07-21 03:14:02 +02:00
if ( ! obj_artist . contains ( " #text " ) | | ! obj_album . contains ( " #text " ) | | ! obj_song . contains ( " #text " ) ) {
2018-12-23 18:54:27 +01:00
continue ;
}
2021-03-21 04:43:55 +01:00
//QString artist = obj_artist["#text"].toString();
//QString album = obj_album["#text"].toString();
2020-07-21 03:14:02 +02:00
QString song = obj_song [ " #text " ] . toString ( ) ;
bool ignoredmessage = obj_ignoredmessage [ " code " ] . toVariant ( ) . toBool ( ) ;
QString ignoredmessage_text = obj_ignoredmessage [ " #text " ] . toString ( ) ;
2018-12-25 23:58:55 +01:00
if ( ignoredmessage ) {
2021-03-21 04:43:55 +01:00
Error ( QString ( " Scrobble for \" %1 \" ignored: %2 " ) . arg ( song , ignoredmessage_text ) ) ;
2018-12-25 23:58:55 +01:00
}
else {
qLog ( Debug ) < < name_ < < " Scrobble for " < < song < < " accepted " ;
}
2018-12-23 18:54:27 +01:00
}
2018-12-26 01:17:17 +01:00
DoSubmit ( ) ;
2018-12-23 18:54:27 +01:00
}
2020-05-10 14:59:04 +02:00
void ScrobblingAPI20 : : SendSingleScrobble ( ScrobblerCacheItemPtr item ) {
2018-12-23 18:54:27 +01:00
ParamList params = ParamList ( )
< < Param ( " method " , " track.scrobble " )
2019-10-19 02:56:23 +02:00
< < Param ( " artist " , prefer_albumartist_ ? item - > effective_albumartist ( ) : item - > artist_ )
2018-12-23 18:54:27 +01:00
< < Param ( " track " , item - > song_ )
< < Param ( " timestamp " , QString : : number ( item - > timestamp_ ) )
< < Param ( " duration " , QString : : number ( item - > duration_ / kNsecPerSec ) ) ;
2021-07-13 23:18:12 +02:00
if ( ! item - > album_ . isEmpty ( ) ) {
2019-11-20 21:30:41 +01:00
params < < Param ( " album " , item - > album_ ) ;
2021-07-13 23:18:12 +02:00
}
if ( ! prefer_albumartist_ & & ! item - > albumartist_ . isEmpty ( ) & & item - > albumartist_ . compare ( Song : : kVariousArtists , Qt : : CaseInsensitive ) ! = 0 ) {
2019-11-20 21:30:41 +01:00
params < < Param ( " albumArtist " , item - > albumartist_ ) ;
2021-07-13 23:18:12 +02:00
}
if ( item - > track_ > 0 ) {
2019-11-20 21:30:41 +01:00
params < < Param ( " trackNumber " , QString : : number ( item - > track_ ) ) ;
2021-07-13 23:18:12 +02:00
}
2018-12-23 18:54:27 +01:00
QNetworkReply * reply = CreateRequest ( params ) ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , item ] ( ) { SingleScrobbleRequestFinished ( reply , item - > timestamp_ ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
2021-06-20 19:04:08 +02:00
void ScrobblingAPI20 : : SingleScrobbleRequestFinished ( QNetworkReply * reply , const quint64 timestamp ) {
2018-12-23 18:54:27 +01: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 ) ;
2018-12-23 18:54:27 +01:00
reply - > deleteLater ( ) ;
2020-05-10 14:59:04 +02:00
ScrobblerCacheItemPtr item = cache ( ) - > Get ( timestamp ) ;
2018-12-23 18:54:27 +01:00
if ( ! item ) {
Error ( QString ( " Received reply for non-existing cache entry %1. " ) . arg ( timestamp ) ) ;
return ;
}
QByteArray data = GetReplyData ( reply ) ;
if ( data . isEmpty ( ) ) {
2018-12-26 01:17:17 +01:00
item - > sent_ = false ;
2018-12-23 18:54:27 +01:00
return ;
}
QJsonObject json_obj = ExtractJsonObj ( data ) ;
if ( json_obj . isEmpty ( ) ) {
2018-12-26 01:17:17 +01:00
item - > sent_ = false ;
return ;
}
if ( json_obj . contains ( " error " ) & & json_obj . contains ( " message " ) ) {
int error_code = json_obj [ " error " ] . toInt ( ) ;
QString error_message = json_obj [ " message " ] . toString ( ) ;
QString error_reason = QString ( " %1 (%2) " ) . arg ( error_message ) . arg ( error_code ) ;
Error ( error_reason ) ;
item - > sent_ = false ;
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! json_obj . contains ( " scrobbles " ) ) {
2018-12-26 01:17:17 +01:00
Error ( " Json reply from server is missing scrobbles. " , json_obj ) ;
item - > sent_ = false ;
2018-12-23 18:54:27 +01:00
return ;
}
2018-12-26 01:17:17 +01:00
cache ( ) - > Remove ( timestamp ) ;
item = nullptr ;
2020-04-25 00:07:42 +02:00
QJsonValue value_scrobbles = json_obj [ " scrobbles " ] ;
if ( ! value_scrobbles . isObject ( ) ) {
2018-12-23 18:54:27 +01:00
Error ( " Json scrobbles is not an object. " , json_obj ) ;
return ;
}
2020-04-25 00:07:42 +02:00
json_obj = value_scrobbles . toObject ( ) ;
2018-12-23 18:54:27 +01:00
if ( json_obj . isEmpty ( ) ) {
2020-04-25 00:07:42 +02:00
Error ( " Json scrobbles object is empty. " , value_scrobbles ) ;
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! json_obj . contains ( " @attr " ) | | ! json_obj . contains ( " scrobble " ) ) {
Error ( " Json scrobbles object is missing values. " , json_obj ) ;
return ;
}
2020-07-21 03:14:02 +02:00
QJsonValue value_attr = json_obj [ " @attr " ] ;
if ( ! value_attr . isObject ( ) ) {
Error ( " Json scrobbles attr is not an object. " , value_attr ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
QJsonObject obj_attr = value_attr . toObject ( ) ;
if ( obj_attr . isEmpty ( ) ) {
Error ( " Json scrobbles attr is empty. " , value_attr ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
QJsonValue value_scrobble = json_obj [ " scrobble " ] ;
if ( ! value_scrobble . isObject ( ) ) {
Error ( " Json scrobbles scrobble is not an object. " , value_scrobble ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
QJsonObject json_obj_scrobble = value_scrobble . toObject ( ) ;
2018-12-23 18:54:27 +01:00
if ( json_obj_scrobble . isEmpty ( ) ) {
2020-07-21 03:14:02 +02:00
Error ( " Json scrobbles scrobble is empty. " , value_scrobble ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
if ( ! obj_attr . contains ( " accepted " ) | | ! obj_attr . contains ( " ignored " ) ) {
Error ( " Json scrobbles attr is missing values. " , obj_attr ) ;
2018-12-23 18:54:27 +01:00
return ;
}
if ( ! json_obj_scrobble . contains ( " artist " ) | | ! json_obj_scrobble . contains ( " album " ) | | ! json_obj_scrobble . contains ( " albumArtist " ) | | ! json_obj_scrobble . contains ( " track " ) | | ! json_obj_scrobble . contains ( " timestamp " ) ) {
Error ( " Json scrobbles scrobble is missing values. " , json_obj_scrobble ) ;
return ;
}
2018-12-26 01:17:17 +01:00
QJsonValue json_value_artist = json_obj_scrobble [ " artist " ] ;
QJsonValue json_value_album = json_obj_scrobble [ " album " ] ;
QJsonValue json_value_song = json_obj_scrobble [ " track " ] ;
if ( ! json_value_artist . isObject ( ) | | ! json_value_album . isObject ( ) | | ! json_value_song . isObject ( ) ) {
Error ( " Json scrobbles scrobble values are not objects. " , json_obj_scrobble ) ;
return ;
}
QJsonObject json_obj_artist = json_value_artist . toObject ( ) ;
QJsonObject json_obj_album = json_value_album . toObject ( ) ;
QJsonObject json_obj_song = json_value_song . toObject ( ) ;
if ( json_obj_artist . isEmpty ( ) | | json_obj_album . isEmpty ( ) | | json_obj_song . isEmpty ( ) ) {
Error ( " Json scrobbles scrobble values objects are empty. " , json_obj_scrobble ) ;
return ;
}
if ( ! json_obj_artist . contains ( " #text " ) | | ! json_obj_album . contains ( " #text " ) | | ! json_obj_song . contains ( " #text " ) ) {
Error ( " Json scrobbles scrobble values objects are missing #text. " , json_obj_artist ) ;
return ;
}
2021-03-21 04:43:55 +01:00
//QString artist = json_obj_artist["#text"].toString();
//QString album = json_obj_album["#text"].toString();
2018-12-26 01:17:17 +01:00
QString song = json_obj_song [ " #text " ] . toString ( ) ;
2020-07-21 03:14:02 +02:00
int accepted = obj_attr [ " accepted " ] . toVariant ( ) . toInt ( ) ;
2018-12-23 18:54:27 +01:00
if ( accepted = = 1 ) {
2018-12-26 01:17:17 +01:00
qLog ( Debug ) < < name_ < < " Scrobble for " < < song < < " accepted " ;
2018-12-23 18:54:27 +01:00
}
else {
2018-12-26 01:17:17 +01:00
Error ( QString ( " Scrobble for \" %1 \" not accepted " ) . arg ( song ) ) ;
2018-12-23 18:54:27 +01:00
}
}
2019-06-12 00:38:52 +02:00
void ScrobblingAPI20 : : Love ( ) {
if ( ! song_playing_ . is_valid ( ) | | ! song_playing_ . is_metadata_good ( ) ) return ;
2018-12-23 18:54:27 +01:00
if ( ! IsAuthenticated ( ) ) app_ - > scrobbler ( ) - > ShowConfig ( ) ;
2019-06-12 00:38:52 +02:00
qLog ( Debug ) < < name_ < < " Sending love for song " < < song_playing_ . artist ( ) < < song_playing_ . album ( ) < < song_playing_ . title ( ) ;
2018-12-23 18:54:27 +01:00
ParamList params = ParamList ( )
< < Param ( " method " , " track.love " )
2019-11-20 21:30:41 +01:00
< < Param ( " artist " , prefer_albumartist_ & & song_playing_ . effective_albumartist ( ) ! = Song : : kVariousArtists ? song_playing_ . effective_albumartist ( ) : song_playing_ . artist ( ) )
< < Param ( " track " , song_playing_ . title ( ) ) ;
if ( ! song_playing_ . album ( ) . isEmpty ( ) )
params < < Param ( " album " , song_playing_ . album ( ) ) ;
2021-07-13 23:18:12 +02:00
if ( ! prefer_albumartist_ & & ! song_playing_ . albumartist ( ) . isEmpty ( ) & & song_playing_ . albumartist ( ) . compare ( Song : : kVariousArtists , Qt : : CaseInsensitive ) ! = 0 )
2019-11-20 21:30:41 +01:00
params < < Param ( " albumArtist " , song_playing_ . albumartist ( ) ) ;
2018-12-23 18:54:27 +01:00
QNetworkReply * reply = CreateRequest ( params ) ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply ] { LoveRequestFinished ( reply ) ; } ) ;
2019-06-12 00:38:52 +02:00
}
void ScrobblingAPI20 : : LoveRequestFinished ( 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 ) ;
2019-06-12 00:38:52 +02:00
reply - > deleteLater ( ) ;
QByteArray data = GetReplyData ( reply ) ;
if ( data . isEmpty ( ) ) {
return ;
}
QJsonObject json_obj = ExtractJsonObj ( data , true ) ;
if ( json_obj . isEmpty ( ) ) {
return ;
}
if ( json_obj . contains ( " error " ) ) {
QJsonValue json_value = json_obj [ " error " ] ;
if ( ! json_value . isObject ( ) ) {
Error ( " Error is not on object. " ) ;
return ;
}
QJsonObject json_obj_error = json_value . toObject ( ) ;
if ( json_obj_error . isEmpty ( ) ) {
Error ( " Received empty json error object. " , json_obj ) ;
return ;
}
if ( json_obj_error . contains ( " code " ) & & json_obj_error . contains ( " #text " ) ) {
int code = json_obj_error [ " code " ] . toInt ( ) ;
QString text = json_obj_error [ " #text " ] . toString ( ) ;
QString error_reason = QString ( " %1 (%2) " ) . arg ( text ) . arg ( code ) ;
Error ( error_reason ) ;
return ;
}
}
if ( json_obj . contains ( " lfm " ) ) {
QJsonValue json_value = json_obj [ " lfm " ] ;
if ( json_value . isObject ( ) ) {
QJsonObject json_obj_lfm = json_value . toObject ( ) ;
if ( json_obj_lfm . contains ( " status " ) ) {
QString status = json_obj_lfm [ " status " ] . toString ( ) ;
qLog ( Debug ) < < name_ < < " Received love status: " < < status ;
return ;
}
}
}
2018-12-23 18:54:27 +01:00
}
2019-11-14 21:07:30 +01:00
void ScrobblingAPI20 : : AuthError ( const QString & error ) {
2018-12-23 18:54:27 +01:00
emit AuthenticationComplete ( false , error ) ;
}
2019-11-14 21:07:30 +01:00
void ScrobblingAPI20 : : Error ( const QString & error , const QVariant & debug ) {
2018-12-23 18:54:27 +01:00
qLog ( Error ) < < name_ < < error ;
if ( debug . isValid ( ) ) qLog ( Debug ) < < debug ;
2021-03-21 04:43:55 +01:00
if ( app_ - > scrobbler ( ) - > ShowErrorDialog ( ) ) { emit ErrorMessage ( tr ( " Scrobbler %1 error: %2 " ) . arg ( name_ , error ) ) ; }
2020-05-26 17:51:23 +02:00
2018-12-23 18:54:27 +01:00
}
2020-08-30 18:09:13 +02:00
QString ScrobblingAPI20 : : ErrorString ( const ScrobbleErrorCode error ) {
2018-12-23 18:54:27 +01:00
switch ( error ) {
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NoError :
return QString ( " This error does not exist. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidService :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid service - This service does not exist. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidMethod :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid Method - No method with that name in this package. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : AuthenticationFailed :
2020-07-19 03:47:21 +02:00
return QString ( " Authentication Failed - You do not have permissions to access the service. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidFormat :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid format - This service doesn't exist in that format. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidParameters :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid parameters - Your request is missing a required parameter. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidResourceSpecified :
return QString ( " Invalid resource specified " ) ;
case ScrobbleErrorCode : : OperationFailed :
2020-07-19 03:47:21 +02:00
return QString ( " Operation failed - Most likely the backend service failed. Please try again. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidSessionKey :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid session key - Please re-authenticate. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidApiKey :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid API key - You must be granted a valid key by last.fm. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : ServiceOffline :
return QString ( " Service Offline - This service is temporarily offline. Try again later. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : SubscribersOnly :
return QString ( " Subscribers Only - This station is only available to paid last.fm subscribers. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidMethodSignature :
2020-07-19 03:47:21 +02:00
return QString ( " Invalid method signature supplied. " ) ;
case ScrobbleErrorCode : : UnauthorizedToken :
return QString ( " Unauthorized Token - This token has not been authorized. " ) ;
case ScrobbleErrorCode : : ItemUnavailable :
return QString ( " This item is not available for streaming. " ) ;
case ScrobbleErrorCode : : TemporarilyUnavailable :
return QString ( " The service is temporarily unavailable, please try again. " ) ;
case ScrobbleErrorCode : : LoginRequired :
return QString ( " Login: User requires to be logged in. " ) ;
case ScrobbleErrorCode : : TrialExpired :
return QString ( " Trial Expired - This user has no free radio plays left. Subscription required. " ) ;
case ScrobbleErrorCode : : ErrorDoesNotExist :
return QString ( " This error does not exist. " ) ;
case ScrobbleErrorCode : : NotEnoughContent :
return QString ( " Not Enough Content - There is not enough content to play this station. " ) ;
case ScrobbleErrorCode : : NotEnoughMembers :
return QString ( " Not Enough Members - This group does not have enough members for radio. " ) ;
case ScrobbleErrorCode : : NotEnoughFans :
return QString ( " Not Enough Fans - This artist does not have enough fans for for radio. " ) ;
case ScrobbleErrorCode : : NotEnoughNeighbours :
return QString ( " Not Enough Neighbours - There are not enough neighbours for radio. " ) ;
case ScrobbleErrorCode : : NoPeakRadio :
return QString ( " No Peak Radio - This user is not allowed to listen to radio during peak usage. " ) ;
case ScrobbleErrorCode : : RadioNotFound :
return QString ( " Radio Not Found - Radio station not found. " ) ;
case ScrobbleErrorCode : : APIKeySuspended :
2018-12-23 18:54:27 +01:00
return QString ( " Suspended API key - Access for your account has been suspended, please contact Last.fm " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : Deprecated :
return QString ( " Deprecated - This type of request is no longer supported. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : RateLimitExceeded :
2020-07-19 03:47:21 +02:00
return QString ( " Rate limit exceeded - Your IP has made too many requests in a short period. " ) ;
2018-12-23 18:54:27 +01:00
}
2020-07-19 03:47:21 +02:00
return QString ( " Unknown error. " ) ;
2018-12-23 18:54:27 +01:00
}
2020-04-25 00:07:42 +02:00
void ScrobblingAPI20 : : CheckScrobblePrevSong ( ) {
2021-10-30 02:21:29 +02:00
qint64 duration = QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) - static_cast < qint64 > ( timestamp_ ) ;
if ( duration < 0 ) duration = 0 ;
2020-04-25 00:07:42 +02:00
2021-07-11 05:18:39 +02:00
if ( ! scrobbled_ & & song_playing_ . is_metadata_good ( ) & & song_playing_ . is_radio ( ) & & duration > 30 ) {
2020-04-25 01:15:23 +02:00
Song song ( song_playing_ ) ;
song . set_length_nanosec ( duration * kNsecPerSec ) ;
Scrobble ( song ) ;
2020-04-25 00:07:42 +02:00
}
}