2018-12-23 18:54:27 +01:00
/*
* Strawberry Music Player
2023-03-25 14:25:21 +01:00
* Copyright 2018 - 2023 , 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
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>
2018-12-23 18:54:27 +01:00
2023-07-21 05:55:24 +02:00
# include "core/shared_ptr.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/logging.h"
2024-04-11 02:56:01 +02:00
# include "core/settings.h"
2022-12-28 03:12:00 +01:00
# include "utilities/timeconstants.h"
2018-12-23 18:54:27 +01:00
# include "internet/localredirectserver.h"
# include "settings/scrobblersettingspage.h"
2023-07-21 05:55:24 +02:00
# include "scrobblersettings.h"
2020-02-09 02:29:35 +01:00
# include "scrobblerservice.h"
2018-12-23 18:54:27 +01:00
# include "scrobblingapi20.h"
2023-03-25 14:25:21 +01:00
# include "scrobblercache.h"
2018-12-23 18:54:27 +01:00
# include "scrobblercacheitem.h"
2023-03-25 14:25:21 +01:00
# include "scrobblemetadata.h"
2018-12-23 18:54:27 +01:00
const char * ScrobblingAPI20 : : kApiKey = " 211990b4c96782c05d1536e7219eb56e " ;
2024-04-11 02:56:01 +02:00
namespace {
constexpr char kSecret [ ] = " 80fd738f49596e9709b1bf9319c444a8 " ;
constexpr int kScrobblesPerRequest = 50 ;
}
2018-12-23 18:54:27 +01:00
2023-07-21 05:55:24 +02:00
ScrobblingAPI20 : : ScrobblingAPI20 ( const QString & name , const QString & settings_group , const QString & auth_url , const QString & api_url , const bool batch , const QString & cache_file , SharedPtr < ScrobblerSettings > settings , SharedPtr < NetworkAccessManager > network , QObject * parent )
2024-04-04 21:17:07 +02:00
: ScrobblerService ( name , settings , parent ) ,
2021-07-11 07:40:57 +02:00
name_ ( name ) ,
settings_group_ ( settings_group ) ,
auth_url_ ( auth_url ) ,
api_url_ ( api_url ) ,
batch_ ( batch ) ,
2023-04-21 20:20:53 +02:00
network_ ( network ) ,
2023-03-25 14:25:21 +01:00
cache_ ( new ScrobblerCache ( cache_file , this ) ) ,
2021-07-11 07:40:57 +02:00
server_ ( nullptr ) ,
enabled_ ( false ) ,
prefer_albumartist_ ( false ) ,
subscriber_ ( false ) ,
submitted_ ( false ) ,
scrobbled_ ( false ) ,
2022-02-16 17:46:40 +01:00
timestamp_ ( 0 ) ,
submit_error_ ( false ) {
2021-01-30 21:50:28 +01:00
timer_submit_ . setSingleShot ( true ) ;
QObject : : connect ( & timer_submit_ , & QTimer : : timeout , this , & ScrobblingAPI20 : : Submit ) ;
2023-06-06 23:20:42 +02:00
ScrobblingAPI20 : : ReloadSettings ( ) ;
2023-03-25 14:25:21 +01:00
LoadSession ( ) ;
2021-01-30 21:50:28 +01:00
}
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 ( ) {
2024-04-11 02:56:01 +02:00
Settings 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 ( ) ;
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 ( ) {
2024-04-11 02:56:01 +02:00
Settings s ;
2018-12-23 18:54:27 +01:00
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 ( ) ;
2024-04-11 02:56:01 +02:00
Settings settings ;
2018-12-23 18:54:27 +01:00
settings . beginGroup ( settings_group_ ) ;
settings . remove ( " subscriber " ) ;
settings . remove ( " username " ) ;
settings . remove ( " session_key " ) ;
settings . endGroup ( ) ;
}
2023-04-21 02:11:23 +02:00
ScrobblingAPI20 : : ReplyResult ScrobblingAPI20 : : GetJsonObject ( QNetworkReply * reply , QJsonObject & json_obj , QString & error_description ) {
ReplyResult reply_error_type = ReplyResult : : ServerError ;
if ( reply - > error ( ) = = QNetworkReply : : NoError ) {
if ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) = = 200 ) {
reply_error_type = ReplyResult : : Success ;
}
else {
2024-04-09 23:20:26 +02:00
error_description = QStringLiteral ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ;
2023-04-21 02:11:23 +02:00
}
}
else {
2024-04-09 23:20:26 +02:00
error_description = QStringLiteral ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
2023-04-21 02:11:23 +02:00
}
// See if there is Json data containing "error" and "message" - then use that instead.
if ( reply - > error ( ) = = QNetworkReply : : NoError | | reply - > error ( ) > = 200 ) {
const QByteArray data = reply - > readAll ( ) ;
int error_code = 0 ;
2024-04-09 23:20:26 +02:00
if ( ! data . isEmpty ( ) & & ExtractJsonObj ( data , json_obj , error_description ) & & json_obj . contains ( QStringLiteral ( " error " ) ) & & json_obj . contains ( QStringLiteral ( " message " ) ) ) {
error_code = json_obj [ QStringLiteral ( " error " ) ] . toInt ( ) ;
QString error_message = json_obj [ QStringLiteral ( " message " ) ] . toString ( ) ;
error_description = QStringLiteral ( " %1 (%2) " ) . arg ( error_message ) . arg ( error_code ) ;
2023-04-21 02:11:23 +02:00
reply_error_type = ReplyResult : : APIError ;
}
const ScrobbleErrorCode lastfm_error_code = static_cast < ScrobbleErrorCode > ( error_code ) ;
2024-02-07 01:34:46 +01:00
if ( reply - > error ( ) = = QNetworkReply : : AuthenticationRequiredError | |
2023-04-21 02:11:23 +02:00
lastfm_error_code = = ScrobbleErrorCode : : InvalidSessionKey | |
lastfm_error_code = = ScrobbleErrorCode : : UnauthorizedToken | |
lastfm_error_code = = ScrobbleErrorCode : : LoginRequired | |
lastfm_error_code = = ScrobbleErrorCode : : APIKeySuspended
) {
// Session is probably expired
Logout ( ) ;
}
}
return reply_error_type ;
}
2023-07-18 19:20:51 +02:00
void ScrobblingAPI20 : : Authenticate ( ) {
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 ) ;
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 ;
2024-04-11 02:56:01 +02:00
url_query . addQueryItem ( QStringLiteral ( " api_key " ) , QLatin1String ( kApiKey ) ) ;
2024-04-09 23:20:26 +02:00
url_query . addQueryItem ( QStringLiteral ( " 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 ) ;
2024-04-09 23:20:26 +02:00
QMessageBox messagebox ( QMessageBox : : Information , tr ( " %1 Scrobbler Authentication " ) . arg ( name_ ) , tr ( " Open URL in web browser? " ) + QStringLiteral ( " <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 ) {
2024-04-11 02:56:01 +02:00
case QMessageBox : : Open : {
2018-12-24 00:15:53 +01:00
bool openurl_result = QDesktopServices : : openUrl ( url ) ;
if ( openurl_result ) {
break ;
}
2024-04-09 23:20:26 +02:00
QMessageBox messagebox_error ( QMessageBox : : Warning , tr ( " %1 Scrobbler Authentication " ) . arg ( name_ ) , tr ( " Could not open URL. Please open this URL in your browser " ) + QStringLiteral ( " :<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 ( ) ;
}
2022-07-26 20:37:06 +02:00
[[fallthrough]] ;
2022-03-22 21:09:05 +01:00
case QMessageBox : : Save :
QApplication : : clipboard ( ) - > setText ( url . toString ( ) ) ;
break ;
case QMessageBox : : Cancel :
if ( server_ ) {
server_ - > close ( ) ;
server_ - > deleteLater ( ) ;
server_ = nullptr ;
}
emit AuthenticationComplete ( false ) ;
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 ) ;
2024-04-09 23:20:26 +02:00
if ( url_query . hasQueryItem ( QStringLiteral ( " token " ) ) ) {
2024-04-11 02:56:01 +02:00
QString token = url_query . queryItemValue ( QStringLiteral ( " token " ) ) ;
2019-11-14 21:07:30 +01:00
RequestSession ( token ) ;
}
else {
AuthError ( tr ( " Invalid reply from web browser. Missing token. " ) ) ;
}
}
else {
2023-07-18 19:20:51 +02:00
AuthError ( tr ( " Received invalid reply from web browser. Try another browser. " ) ) ;
2019-11-14 21:07:30 +01:00
}
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 ;
2024-04-11 02:56:01 +02:00
session_url_query . addQueryItem ( QStringLiteral ( " api_key " ) , QLatin1String ( kApiKey ) ) ;
2024-04-09 23:20:26 +02:00
session_url_query . addQueryItem ( QStringLiteral ( " method " ) , QStringLiteral ( " auth.getSession " ) ) ;
session_url_query . addQueryItem ( QStringLiteral ( " token " ) , token ) ;
2018-12-23 18:54:27 +01:00
QString data_to_sign ;
2023-03-25 14:25:21 +01:00
for ( const Param & param : session_url_query . queryItems ( ) ) {
2018-12-23 18:54:27 +01:00
data_to_sign + = param . first + param . second ;
}
2024-04-11 02:56:01 +02:00
data_to_sign + = QLatin1String ( kSecret ) ;
2018-12-23 18:54:27 +01:00
QByteArray const digest = QCryptographicHash : : hash ( data_to_sign . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
2024-04-11 02:56:01 +02:00
const QString signature = QString : : fromLatin1 ( digest . toHex ( ) ) . rightJustified ( 32 , QLatin1Char ( ' 0 ' ) ) . toLower ( ) ;
2024-04-09 23:20:26 +02:00
session_url_query . addQueryItem ( QStringLiteral ( " api_sig " ) , signature ) ;
2024-04-11 02:56:01 +02:00
session_url_query . addQueryItem ( QString : : fromLatin1 ( QUrl : : toPercentEncoding ( QStringLiteral ( " format " ) ) ) , QString : : fromLatin1 ( QUrl : : toPercentEncoding ( QStringLiteral ( " json " ) ) ) ) ;
2018-12-23 18:54:27 +01:00
session_url . setQuery ( session_url_query ) ;
QNetworkRequest req ( session_url ) ;
2020-08-14 20:20:41 +02:00
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
2023-03-25 14:25:21 +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 ( ) ;
2023-04-21 02:11:23 +02:00
QJsonObject json_obj ;
QString error_message ;
if ( GetJsonObject ( reply , json_obj , error_message ) ! = ReplyResult : : Success ) {
AuthError ( error_message ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " session " ) ) ) {
AuthError ( QStringLiteral ( " Json reply from server is missing session. " ) ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue json_session = json_obj [ QStringLiteral ( " session " ) ] ;
2018-12-23 18:54:27 +01:00
if ( ! json_session . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
AuthError ( QStringLiteral ( " Json session is not an object. " ) ) ;
2018-12-23 18:54:27 +01:00
return ;
}
json_obj = json_session . toObject ( ) ;
if ( json_obj . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
AuthError ( QStringLiteral ( " Json session object is empty. " ) ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " subscriber " ) ) | | ! json_obj . contains ( QStringLiteral ( " name " ) ) | | ! json_obj . contains ( QStringLiteral ( " key " ) ) ) {
AuthError ( QStringLiteral ( " Json session object is missing values. " ) ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
subscriber_ = json_obj [ QStringLiteral ( " subscriber " ) ] . toBool ( ) ;
username_ = json_obj [ QStringLiteral ( " name " ) ] . toString ( ) ;
session_key_ = json_obj [ QStringLiteral ( " key " ) ] . toString ( ) ;
2021-07-11 07:40:57 +02:00
2024-04-11 02:56:01 +02:00
Settings s ;
2018-12-23 18:54:27 +01:00
s . beginGroup ( settings_group_ ) ;
s . setValue ( " subscriber " , subscriber_ ) ;
s . setValue ( " username " , username_ ) ;
s . setValue ( " session_key " , session_key_ ) ;
s . endGroup ( ) ;
emit AuthenticationComplete ( true ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-26 01:45:28 +01:00
2018-12-23 18:54:27 +01:00
}
QNetworkReply * ScrobblingAPI20 : : CreateRequest ( const ParamList & request_params ) {
ParamList params = ParamList ( )
2024-04-11 02:56:01 +02:00
< < Param ( QStringLiteral ( " api_key " ) , QLatin1String ( kApiKey ) )
< < Param ( QStringLiteral ( " sk " ) , session_key_ )
< < Param ( QStringLiteral ( " lang " ) , QLocale ( ) . name ( ) . left ( 2 ) . toLower ( ) )
2018-12-23 18:54:27 +01:00
< < 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 ) ) ;
2024-04-11 02:56:01 +02:00
url_query . addQueryItem ( QString : : fromLatin1 ( encoded_param . first ) , QString : : fromLatin1 ( encoded_param . second ) ) ;
2018-12-23 18:54:27 +01:00
data_to_sign + = param . first + param . second ;
}
2024-04-11 02:56:01 +02:00
data_to_sign + = QLatin1String ( kSecret ) ;
2018-12-23 18:54:27 +01:00
QByteArray const digest = QCryptographicHash : : hash ( data_to_sign . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
2024-04-11 02:56:01 +02:00
const QString signature = QString : : fromLatin1 ( digest . toHex ( ) ) . rightJustified ( 32 , QLatin1Char ( ' 0 ' ) ) . toLower ( ) ;
2018-12-23 18:54:27 +01:00
2024-04-11 02:56:01 +02:00
url_query . addQueryItem ( QStringLiteral ( " api_sig " ) , QString : : fromLatin1 ( QUrl : : toPercentEncoding ( signature ) ) ) ;
url_query . addQueryItem ( QStringLiteral ( " format " ) , QStringLiteral ( " json " ) ) ;
2018-12-23 18:54:27 +01:00
QUrl url ( api_url_ ) ;
QNetworkRequest req ( url ) ;
2020-08-14 20:20:41 +02:00
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
2024-04-11 02:56:01 +02:00
req . setHeader ( QNetworkRequest : : ContentTypeHeader , QStringLiteral ( " application/x-www-form-urlencoded " ) ) ;
2018-12-23 18:54:27 +01:00
QByteArray query = url_query . toString ( QUrl : : FullyEncoded ) . toUtf8 ( ) ;
2023-03-25 14:25:21 +01:00
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 ;
}
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
2023-07-21 05:55:24 +02:00
if ( ! authenticated ( ) | | ! song . is_metadata_good ( ) | | settings_ - > offline ( ) ) return ;
2018-12-23 18:54:27 +01:00
ParamList params = ParamList ( )
2024-04-11 02:56:01 +02:00
< < Param ( QStringLiteral ( " method " ) , QStringLiteral ( " track.updateNowPlaying " ) )
< < Param ( QStringLiteral ( " artist " ) , prefer_albumartist_ ? song . effective_albumartist ( ) : song . artist ( ) )
< < Param ( QStringLiteral ( " track " ) , StripTitle ( song . title ( ) ) ) ;
2019-10-19 02:56:23 +02:00
2023-03-25 14:25:21 +01:00
if ( ! song . album ( ) . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " album " ) , StripAlbum ( song . album ( ) ) ) ;
2021-07-13 23:18:12 +02:00
}
2019-11-20 21:30:41 +01:00
2022-12-27 21:15:20 +01:00
if ( ! prefer_albumartist_ & & ! song . albumartist ( ) . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " 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 ( ) ;
2023-04-21 02:11:23 +02:00
QJsonObject json_obj ;
QString error_message ;
if ( GetJsonObject ( reply , json_obj , error_message ) ! = ReplyResult : : Success ) {
Error ( error_message ) ;
2018-12-26 02:31:32 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " nowplaying " ) ) ) {
Error ( QStringLiteral ( " Json reply from server is missing nowplaying. " ) , json_obj ) ;
2018-12-26 02:31:32 +01:00
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 ;
2023-03-25 14:25:21 +01:00
cache_ - > Add ( song , timestamp_ ) ;
2018-12-23 18:54:27 +01:00
2023-07-21 05:55:24 +02:00
if ( settings_ - > offline ( ) ) return ;
2018-12-26 13:33:56 +01:00
2023-07-21 05:55:24 +02:00
if ( ! authenticated ( ) ) {
if ( settings_ - > show_error_dialog ( ) ) {
2022-03-22 21:09:05 +01:00
emit ErrorMessage ( tr ( " Scrobbler %1 is not authenticated! " ) . arg ( name_ ) ) ;
}
2018-12-23 18:54:27 +01:00
return ;
}
2022-02-16 17:46:40 +01:00
StartSubmit ( true ) ;
2018-12-23 18:54:27 +01:00
}
2022-02-16 17:46:40 +01:00
void ScrobblingAPI20 : : StartSubmit ( const bool initial ) {
2018-12-26 01:17:17 +01:00
2023-03-25 14:25:21 +01:00
if ( ! submitted_ & & cache_ - > Count ( ) > 0 ) {
2023-07-21 05:55:24 +02:00
if ( initial & & ( ! batch_ | | settings_ - > submit_delay ( ) < = 0 ) & & ! submit_error_ ) {
2022-02-16 17:46:40 +01:00
if ( timer_submit_ . isActive ( ) ) {
timer_submit_ . stop ( ) ;
}
Submit ( ) ;
}
else if ( ! timer_submit_ . isActive ( ) ) {
2023-07-21 05:55:24 +02:00
int submit_delay = static_cast < int > ( std : : max ( settings_ - > submit_delay ( ) , submit_error_ ? 30 : 5 ) * kMsecPerSec ) ;
2022-02-16 17:46:40 +01:00
timer_submit_ . setInterval ( submit_delay ) ;
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 ( ) {
2023-07-21 05:55:24 +02:00
if ( ! enabled ( ) | | ! authenticated ( ) | | settings_ - > offline ( ) ) return ;
2018-12-23 18:54:27 +01:00
2019-06-12 00:38:52 +02:00
qLog ( Debug ) < < name_ < < " Submitting scrobbles. " ;
2024-04-11 02:56:01 +02:00
ParamList params = ParamList ( ) < < Param ( QStringLiteral ( " method " ) , QStringLiteral ( " track.scrobble " ) ) ;
2018-12-23 18:54:27 +01:00
2020-07-21 03:14:02 +02:00
int i = 0 ;
2023-04-21 02:11:23 +02:00
ScrobblerCacheItemPtrList all_cache_items = cache_ - > List ( ) ;
ScrobblerCacheItemPtrList cache_items_sent ;
for ( ScrobblerCacheItemPtr cache_item : all_cache_items ) {
if ( cache_item - > sent ) continue ;
cache_item - > sent = true ;
2019-11-20 21:30:41 +01:00
if ( ! batch_ ) {
2023-04-21 02:11:23 +02:00
SendSingleScrobble ( cache_item ) ;
2019-11-20 21:30:41 +01:00
continue ;
}
2023-04-21 02:11:23 +02:00
cache_items_sent < < cache_item ;
2024-04-09 23:20:26 +02:00
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " artist " ) ) . arg ( i ) , prefer_albumartist_ ? cache_item - > metadata . effective_albumartist ( ) : cache_item - > metadata . artist ) ;
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " track " ) ) . arg ( i ) , StripTitle ( cache_item - > metadata . title ) ) ;
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " timestamp " ) ) . arg ( i ) , QString : : number ( cache_item - > timestamp ) ) ;
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " duration " ) ) . arg ( i ) , QString : : number ( cache_item - > metadata . length_nanosec / kNsecPerSec ) ) ;
2023-04-21 02:11:23 +02:00
if ( ! cache_item - > metadata . album . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " album " ) ) . arg ( i ) , StripAlbum ( cache_item - > metadata . album ) ) ;
2021-07-13 23:18:12 +02:00
}
2023-04-21 02:11:23 +02:00
if ( ! prefer_albumartist_ & & ! cache_item - > metadata . albumartist . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " albumArtist " ) ) . arg ( i ) , cache_item - > metadata . albumartist ) ;
2021-07-13 23:18:12 +02:00
}
2023-04-21 02:11:23 +02:00
if ( cache_item - > metadata . track > 0 ) {
2024-04-09 23:20:26 +02:00
params < < Param ( QStringLiteral ( " %1[%2] " ) . arg ( QStringLiteral ( " trackNumber " ) ) . arg ( i ) , QString : : number ( cache_item - > metadata . track ) ) ;
2021-07-13 23:18:12 +02:00
}
2020-07-21 03:14:02 +02:00
+ + i ;
2023-04-21 02:11:23 +02:00
if ( cache_items_sent . count ( ) > = kScrobblesPerRequest ) break ;
2018-12-23 18:54:27 +01:00
}
2023-04-21 02:11:23 +02:00
if ( ! batch_ | | cache_items_sent . count ( ) < = 0 ) return ;
2018-12-23 18:54:27 +01:00
2022-02-16 17:46:40 +01:00
submitted_ = true ;
2018-12-23 18:54:27 +01:00
QNetworkReply * reply = CreateRequest ( params ) ;
2023-04-21 02:11:23 +02:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , cache_items_sent ] ( ) { ScrobbleRequestFinished ( reply , cache_items_sent ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
2023-04-21 02:11:23 +02:00
void ScrobblingAPI20 : : ScrobbleRequestFinished ( QNetworkReply * reply , ScrobblerCacheItemPtrList cache_items ) {
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 ( ) ;
2022-02-16 17:46:40 +01:00
submitted_ = false ;
2023-04-21 02:11:23 +02:00
QJsonObject json_obj ;
QString error_message ;
if ( GetJsonObject ( reply , json_obj , error_message ) ! = ReplyResult : : Success ) {
Error ( error_message ) ;
cache_ - > ClearSent ( cache_items ) ;
2022-02-16 17:46:40 +01:00
submit_error_ = true ;
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2023-04-21 02:11:23 +02:00
cache_ - > Flush ( cache_items ) ;
2022-02-16 17:46:40 +01:00
submit_error_ = false ;
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " scrobbles " ) ) ) {
Error ( QStringLiteral ( " Json reply from server is missing scrobbles. " ) , json_obj ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_scrobbles = json_obj [ QStringLiteral ( " scrobbles " ) ] ;
2020-04-25 00:07:42 +02:00
if ( ! value_scrobbles . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles is not an object. " ) , json_obj ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles object is empty. " ) , value_scrobbles ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " @attr " ) ) | | ! json_obj . contains ( QStringLiteral ( " scrobble " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles object is missing values. " ) , json_obj ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_attr = json_obj [ QStringLiteral ( " @attr " ) ] ;
2020-07-21 03:14:02 +02:00
if ( ! value_attr . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles attr is not an object. " ) , value_attr ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles attr is empty. " ) , value_attr ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! obj_attr . contains ( QStringLiteral ( " accepted " ) ) | | ! obj_attr . contains ( QStringLiteral ( " ignored " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles attr is missing values. " ) , obj_attr ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
int accepted = obj_attr [ QStringLiteral ( " accepted " ) ] . toInt ( ) ;
int ignored = obj_attr [ QStringLiteral ( " ignored " ) ] . toInt ( ) ;
2020-07-21 03:14:02 +02:00
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 ;
2024-04-09 23:20:26 +02:00
QJsonValue value_scrobble = json_obj [ QStringLiteral ( " scrobble " ) ] ;
2020-07-21 03:14:02 +02:00
if ( value_scrobble . isObject ( ) ) {
QJsonObject obj_scrobble = value_scrobble . toObject ( ) ;
if ( obj_scrobble . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble object is empty. " ) , obj_scrobble ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2020-07-21 03:14:02 +02:00
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble array is empty. " ) , value_scrobble ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2020-07-21 03:14:02 +02:00
return ;
}
}
else {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble is not an object or array. " ) , value_scrobble ) ;
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2020-07-21 03:14:02 +02:00
2023-03-25 14:25:21 +01:00
for ( const QJsonValueRef value : array_scrobble ) {
2018-12-23 18:54:27 +01:00
if ( ! value . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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 ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_track . contains ( QStringLiteral ( " artist " ) ) | |
! json_track . contains ( QStringLiteral ( " album " ) ) | |
! json_track . contains ( QStringLiteral ( " albumArtist " ) ) | |
! json_track . contains ( QStringLiteral ( " track " ) ) | |
! json_track . contains ( QStringLiteral ( " timestamp " ) ) | |
! json_track . contains ( QStringLiteral ( " ignoredMessage " ) )
2018-12-23 18:54:27 +01:00
) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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
}
2024-04-09 23:20:26 +02:00
QJsonValue value_artist = json_track [ QStringLiteral ( " artist " ) ] ;
QJsonValue value_album = json_track [ QStringLiteral ( " album " ) ] ;
QJsonValue value_song = json_track [ QStringLiteral ( " track " ) ] ;
QJsonValue value_ignoredmessage = json_track [ QStringLiteral ( " 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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble values are not objects. " ) , json_track ) ;
2018-12-23 18:54:27 +01:00
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble values objects are empty. " ) , json_track ) ;
2018-12-23 18:54:27 +01:00
continue ;
}
2024-04-09 23:20:26 +02:00
if ( ! obj_artist . contains ( QStringLiteral ( " #text " ) ) | | ! obj_album . contains ( QStringLiteral ( " #text " ) ) | | ! obj_song . contains ( QStringLiteral ( " #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();
2024-04-09 23:20:26 +02:00
QString song = obj_song [ QStringLiteral ( " #text " ) ] . toString ( ) ;
bool ignoredmessage = obj_ignoredmessage [ QStringLiteral ( " code " ) ] . toVariant ( ) . toBool ( ) ;
QString ignoredmessage_text = obj_ignoredmessage [ QStringLiteral ( " #text " ) ] . toString ( ) ;
2018-12-25 23:58:55 +01:00
if ( ignoredmessage ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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
}
2022-02-16 17:46:40 +01:00
StartSubmit ( ) ;
2018-12-26 01:17:17 +01:00
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 ( )
2024-04-11 02:56:01 +02:00
< < Param ( QStringLiteral ( " method " ) , QStringLiteral ( " track.scrobble " ) )
< < Param ( QStringLiteral ( " artist " ) , prefer_albumartist_ ? item - > metadata . effective_albumartist ( ) : item - > metadata . artist )
< < Param ( QStringLiteral ( " track " ) , StripTitle ( item - > metadata . title ) )
< < Param ( QStringLiteral ( " timestamp " ) , QString : : number ( item - > timestamp ) )
< < Param ( QStringLiteral ( " duration " ) , QString : : number ( item - > metadata . length_nanosec / kNsecPerSec ) ) ;
2018-12-23 18:54:27 +01:00
2023-03-25 14:25:21 +01:00
if ( ! item - > metadata . album . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " album " ) , StripAlbum ( item - > metadata . album ) ) ;
2021-07-13 23:18:12 +02:00
}
2023-03-25 14:25:21 +01:00
if ( ! prefer_albumartist_ & & ! item - > metadata . albumartist . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " albumArtist " ) , item - > metadata . albumartist ) ;
2021-07-13 23:18:12 +02:00
}
2023-03-25 14:25:21 +01:00
if ( item - > metadata . track > 0 ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " trackNumber " ) , QString : : number ( item - > metadata . track ) ) ;
2021-07-13 23:18:12 +02:00
}
2018-12-23 18:54:27 +01:00
QNetworkReply * reply = CreateRequest ( params ) ;
2023-04-21 02:11:23 +02:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , item ] ( ) { SingleScrobbleRequestFinished ( reply , item ) ; } ) ;
2018-12-23 18:54:27 +01:00
}
2023-04-21 02:11:23 +02:00
void ScrobblingAPI20 : : SingleScrobbleRequestFinished ( QNetworkReply * reply , ScrobblerCacheItemPtr cache_item ) {
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 ( ) ;
2023-04-21 02:11:23 +02:00
QJsonObject json_obj ;
QString error_message ;
if ( GetJsonObject ( reply , json_obj , error_message ) ! = ReplyResult : : Success ) {
Error ( error_message ) ;
cache_item - > sent = false ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " scrobbles " ) ) ) {
Error ( QStringLiteral ( " Json reply from server is missing scrobbles. " ) , json_obj ) ;
2023-04-21 02:11:23 +02:00
cache_item - > sent = false ;
2018-12-23 18:54:27 +01:00
return ;
}
2023-04-21 02:11:23 +02:00
cache_ - > Remove ( cache_item ) ;
2018-12-26 01:17:17 +01:00
2024-04-09 23:20:26 +02:00
QJsonValue value_scrobbles = json_obj [ QStringLiteral ( " scrobbles " ) ] ;
2020-04-25 00:07:42 +02:00
if ( ! value_scrobbles . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles is not an object. " ) , json_obj ) ;
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles object is empty. " ) , value_scrobbles ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " @attr " ) ) | | ! json_obj . contains ( QStringLiteral ( " scrobble " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles object is missing values. " ) , json_obj ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_attr = json_obj [ QStringLiteral ( " @attr " ) ] ;
2020-07-21 03:14:02 +02:00
if ( ! value_attr . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles attr is empty. " ) , value_attr ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_scrobble = json_obj [ QStringLiteral ( " scrobble " ) ] ;
2020-07-21 03:14:02 +02:00
if ( ! value_scrobble . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble is empty. " ) , value_scrobble ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! obj_attr . contains ( QStringLiteral ( " accepted " ) ) | | ! obj_attr . contains ( QStringLiteral ( " ignored " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles attr is missing values. " ) , obj_attr ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj_scrobble . contains ( QStringLiteral ( " artist " ) ) | | ! json_obj_scrobble . contains ( QStringLiteral ( " album " ) ) | | ! json_obj_scrobble . contains ( QStringLiteral ( " albumArtist " ) ) | | ! json_obj_scrobble . contains ( QStringLiteral ( " track " ) ) | | ! json_obj_scrobble . contains ( QStringLiteral ( " timestamp " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles scrobble is missing values. " ) , json_obj_scrobble ) ;
2018-12-23 18:54:27 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue json_value_artist = json_obj_scrobble [ QStringLiteral ( " artist " ) ] ;
QJsonValue json_value_album = json_obj_scrobble [ QStringLiteral ( " album " ) ] ;
QJsonValue json_value_song = json_obj_scrobble [ QStringLiteral ( " track " ) ] ;
2018-12-26 01:17:17 +01:00
if ( ! json_value_artist . isObject ( ) | | ! json_value_album . isObject ( ) | | ! json_value_song . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble values are not objects. " ) , json_obj_scrobble ) ;
2018-12-26 01:17:17 +01:00
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 ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json scrobbles scrobble values objects are empty. " ) , json_obj_scrobble ) ;
2018-12-26 01:17:17 +01:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj_artist . contains ( QStringLiteral ( " #text " ) ) | | ! json_obj_album . contains ( QStringLiteral ( " #text " ) ) | | ! json_obj_song . contains ( QStringLiteral ( " #text " ) ) ) {
Error ( QStringLiteral ( " Json scrobbles scrobble values objects are missing #text. " ) , json_obj_artist ) ;
2018-12-26 01:17:17 +01:00
return ;
}
2021-03-21 04:43:55 +01:00
//QString artist = json_obj_artist["#text"].toString();
//QString album = json_obj_album["#text"].toString();
2024-04-09 23:20:26 +02:00
QString song = json_obj_song [ QStringLiteral ( " #text " ) ] . toString ( ) ;
2018-12-26 01:17:17 +01:00
2024-04-09 23:20:26 +02:00
int accepted = obj_attr [ QStringLiteral ( " 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 {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " 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
2023-07-21 05:55:24 +02:00
if ( ! authenticated ( ) ) settings_ - > ShowConfig ( ) ;
2018-12-23 18:54:27 +01:00
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 ( )
2024-04-11 02:56:01 +02:00
< < Param ( QStringLiteral ( " method " ) , QStringLiteral ( " track.love " ) )
< < Param ( QStringLiteral ( " artist " ) , prefer_albumartist_ ? song_playing_ . effective_albumartist ( ) : song_playing_ . artist ( ) )
< < Param ( QStringLiteral ( " track " ) , song_playing_ . title ( ) ) ;
2019-11-20 21:30:41 +01:00
2022-12-27 21:15:20 +01:00
if ( ! song_playing_ . album ( ) . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " album " ) , song_playing_ . album ( ) ) ;
2022-12-27 21:15:20 +01:00
}
2019-11-20 21:30:41 +01:00
2022-12-27 21:15:20 +01:00
if ( ! prefer_albumartist_ & & ! song_playing_ . albumartist ( ) . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
params < < Param ( QStringLiteral ( " albumArtist " ) , song_playing_ . albumartist ( ) ) ;
2022-12-27 21:15:20 +01: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 ] { 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 ( ) ;
2023-04-21 02:11:23 +02:00
QJsonObject json_obj ;
QString error_message ;
if ( GetJsonObject ( reply , json_obj , error_message ) ! = ReplyResult : : Success ) {
Error ( error_message ) ;
2019-06-12 00:38:52 +02:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( json_obj . contains ( QStringLiteral ( " error " ) ) ) {
QJsonValue json_value = json_obj [ QStringLiteral ( " error " ) ] ;
2019-06-12 00:38:52 +02:00
if ( ! json_value . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Error is not on object. " ) ) ;
2019-06-12 00:38:52 +02:00
return ;
}
QJsonObject json_obj_error = json_value . toObject ( ) ;
if ( json_obj_error . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Received empty json error object. " ) , json_obj ) ;
2019-06-12 00:38:52 +02:00
return ;
}
2024-04-09 23:20:26 +02:00
if ( json_obj_error . contains ( QStringLiteral ( " code " ) ) & & json_obj_error . contains ( QStringLiteral ( " #text " ) ) ) {
int code = json_obj_error [ QStringLiteral ( " code " ) ] . toInt ( ) ;
QString text = json_obj_error [ QStringLiteral ( " #text " ) ] . toString ( ) ;
QString error_reason = QStringLiteral ( " %1 (%2) " ) . arg ( text ) . arg ( code ) ;
2019-06-12 00:38:52 +02:00
Error ( error_reason ) ;
return ;
}
}
2024-04-09 23:20:26 +02:00
if ( json_obj . contains ( QStringLiteral ( " lfm " ) ) ) {
QJsonValue json_value = json_obj [ QStringLiteral ( " lfm " ) ] ;
2019-06-12 00:38:52 +02:00
if ( json_value . isObject ( ) ) {
QJsonObject json_obj_lfm = json_value . toObject ( ) ;
2024-04-09 23:20:26 +02:00
if ( json_obj_lfm . contains ( QStringLiteral ( " status " ) ) ) {
QString status = json_obj_lfm [ QStringLiteral ( " status " ) ] . toString ( ) ;
2019-06-12 00:38:52 +02:00
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 ) {
2023-04-21 02:11:23 +02:00
qLog ( Error ) < < name_ < < error ;
2018-12-23 18:54:27 +01:00
emit AuthenticationComplete ( false , error ) ;
2023-04-21 02:11:23 +02:00
2018-12-23 18:54:27 +01:00
}
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 ;
2023-07-21 05:55:24 +02:00
if ( settings_ - > show_error_dialog ( ) ) {
2022-03-22 21:09:05 +01:00
emit ErrorMessage ( tr ( " Scrobbler %1 error: %2 " ) . arg ( name_ , error ) ) ;
}
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 :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " This error does not exist. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidService :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid service - This service does not exist. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidMethod :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid Method - No method with that name in this package. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : AuthenticationFailed :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Authentication Failed - You do not have permissions to access the service. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidFormat :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid format - This service doesn't exist in that format. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidParameters :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid parameters - Your request is missing a required parameter. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidResourceSpecified :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid resource specified " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : OperationFailed :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Operation failed - Most likely the backend service failed. Please try again. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidSessionKey :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid session key - Please re-authenticate. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidApiKey :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid API key - You must be granted a valid key by last.fm. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : ServiceOffline :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Service Offline - This service is temporarily offline. Try again later. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : SubscribersOnly :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Subscribers Only - This station is only available to paid last.fm subscribers. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : InvalidMethodSignature :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Invalid method signature supplied. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : UnauthorizedToken :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Unauthorized Token - This token has not been authorized. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : ItemUnavailable :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " This item is not available for streaming. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : TemporarilyUnavailable :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " The service is temporarily unavailable, please try again. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : LoginRequired :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Login: User requires to be logged in. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : TrialExpired :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Trial Expired - This user has no free radio plays left. Subscription required. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : ErrorDoesNotExist :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " This error does not exist. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NotEnoughContent :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Not Enough Content - There is not enough content to play this station. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NotEnoughMembers :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Not Enough Members - This group does not have enough members for radio. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NotEnoughFans :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Not Enough Fans - This artist does not have enough fans for for radio. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NotEnoughNeighbours :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Not Enough Neighbours - There are not enough neighbours for radio. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : NoPeakRadio :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " No Peak Radio - This user is not allowed to listen to radio during peak usage. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : RadioNotFound :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Radio Not Found - Radio station not found. " ) ;
2020-07-19 03:47:21 +02:00
case ScrobbleErrorCode : : APIKeySuspended :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " 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 :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Deprecated - This type of request is no longer supported. " ) ;
2018-12-23 18:54:27 +01:00
case ScrobbleErrorCode : : RateLimitExceeded :
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Rate limit exceeded - Your IP has made too many requests in a short period. " ) ;
2018-12-23 18:54:27 +01:00
}
2024-04-09 23:20:26 +02:00
return QStringLiteral ( " Unknown error. " ) ;
2020-07-19 03:47:21 +02:00
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
}
}