Use refresh token for ListenBrainz

This commit is contained in:
Jonas Kvinge
2020-05-11 00:51:18 +02:00
parent 9a740f7962
commit ec7202e3f6
2 changed files with 72 additions and 32 deletions

View File

@@ -35,6 +35,7 @@
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonValue> #include <QJsonValue>
#include <QTimer>
#include <QtDebug> #include <QtDebug>
#include "core/application.h" #include "core/application.h"
@@ -53,12 +54,12 @@
const char *ListenBrainzScrobbler::kName = "ListenBrainz"; const char *ListenBrainzScrobbler::kName = "ListenBrainz";
const char *ListenBrainzScrobbler::kSettingsGroup = "ListenBrainz"; const char *ListenBrainzScrobbler::kSettingsGroup = "ListenBrainz";
const char *ListenBrainzScrobbler::kAuthUrl = "https://musicbrainz.org/oauth2/authorize"; const char *ListenBrainzScrobbler::kOAuthAuthorizeUrl = "https://musicbrainz.org/oauth2/authorize";
const char *ListenBrainzScrobbler::kAuthTokenUrl = "https://musicbrainz.org/oauth2/token"; const char *ListenBrainzScrobbler::kOAuthAccessTokenUrl = "https://musicbrainz.org/oauth2/token";
const char *ListenBrainzScrobbler::kRedirectUrl = "http://localhost"; const char *ListenBrainzScrobbler::kOAuthRedirectUrl = "http://localhost";
const char *ListenBrainzScrobbler::kApiUrl = "https://api.listenbrainz.org"; const char *ListenBrainzScrobbler::kApiUrl = "https://api.listenbrainz.org";
const char *ListenBrainzScrobbler::kClientID = "oeAUNwqSQer0er09Fiqi0Q"; const char *ListenBrainzScrobbler::kClientIDB64 = "b2VBVU53cVNRZXIwZXIwOUZpcWkwUQ==";
const char *ListenBrainzScrobbler::kClientSecret = "ROFghkeQ3F3oPyEhqiyWPA"; const char *ListenBrainzScrobbler::kClientSecretB64 = "Uk9GZ2hrZVEzRjNvUHlFaHFpeVdQQQ==";
const char *ListenBrainzScrobbler::kCacheFile = "listenbrainzscrobbler.cache"; const char *ListenBrainzScrobbler::kCacheFile = "listenbrainzscrobbler.cache";
const int ListenBrainzScrobbler::kScrobblesPerRequest = 10; const int ListenBrainzScrobbler::kScrobblesPerRequest = 10;
@@ -69,10 +70,14 @@ ListenBrainzScrobbler::ListenBrainzScrobbler(Application *app, QObject *parent)
server_(nullptr), server_(nullptr),
enabled_(false), enabled_(false),
expires_in_(-1), expires_in_(-1),
login_time_(0),
submitted_(false), submitted_(false),
scrobbled_(false), scrobbled_(false),
timestamp_(0) { timestamp_(0) {
refresh_login_timer_.setSingleShot(true);
connect(&refresh_login_timer_, SIGNAL(timeout()), SLOT(RequestAccessToken()));
ReloadSettings(); ReloadSettings();
LoadSession(); LoadSession();
@@ -98,16 +103,25 @@ void ListenBrainzScrobbler::LoadSession() {
expires_in_ = s.value("expires_in", -1).toInt(); expires_in_ = s.value("expires_in", -1).toInt();
token_type_ = s.value("token_type").toString(); token_type_ = s.value("token_type").toString();
refresh_token_ = s.value("refresh_token").toString(); refresh_token_ = s.value("refresh_token").toString();
login_time_ = s.value("login_time").toLongLong();
s.endGroup(); s.endGroup();
if (!refresh_token_.isEmpty()) {
qint64 time = expires_in_ - (QDateTime::currentDateTime().toTime_t() - login_time_);
if (time < 6) time = 6;
refresh_login_timer_.setInterval(time * kMsecPerSec);
refresh_login_timer_.start();
}
} }
void ListenBrainzScrobbler::Logout() { void ListenBrainzScrobbler::Logout() {
access_token_.clear(); access_token_.clear();
expires_in_ = -1;
token_type_.clear(); token_type_.clear();
refresh_token_.clear(); refresh_token_.clear();
expires_in_ = -1;
login_time_ = 0;
QSettings settings; QSettings settings;
settings.beginGroup(kSettingsGroup); settings.beginGroup(kSettingsGroup);
@@ -133,15 +147,15 @@ void ListenBrainzScrobbler::Authenticate(const bool https) {
connect(server_, SIGNAL(Finished()), this, SLOT(RedirectArrived())); connect(server_, SIGNAL(Finished()), this, SLOT(RedirectArrived()));
} }
QUrl redirect_url(kRedirectUrl); QUrl redirect_url(kOAuthRedirectUrl);
redirect_url.setPort(server_->url().port()); redirect_url.setPort(server_->url().port());
QUrlQuery url_query; QUrlQuery url_query;
url_query.addQueryItem("response_type", "code"); url_query.addQueryItem("response_type", "code");
url_query.addQueryItem("client_id", kClientID); url_query.addQueryItem("client_id", QByteArray::fromBase64(kClientIDB64));
url_query.addQueryItem("redirect_uri", redirect_url.toString()); url_query.addQueryItem("redirect_uri", redirect_url.toString());
url_query.addQueryItem("scope", "profile;email;tag;rating;collection;submit_isrc;submit_barcode"); url_query.addQueryItem("scope", "profile;email;tag;rating;collection;submit_isrc;submit_barcode");
QUrl url(kAuthUrl); QUrl url(kOAuthAuthorizeUrl);
url.setQuery(url_query); url.setQuery(url_query);
bool result = QDesktopServices::openUrl(url); bool result = QDesktopServices::openUrl(url);
@@ -165,7 +179,7 @@ void ListenBrainzScrobbler::RedirectArrived() {
AuthError(QUrlQuery(url).queryItemValue("error")); AuthError(QUrlQuery(url).queryItemValue("error"));
} }
else if (url_query.hasQueryItem("code")) { else if (url_query.hasQueryItem("code")) {
RequestSession(url, url_query.queryItemValue("code")); RequestAccessToken(url, url_query.queryItemValue("code"));
} }
else { else {
AuthError(tr("Redirect missing token code!")); AuthError(tr("Redirect missing token code!"));
@@ -185,15 +199,32 @@ void ListenBrainzScrobbler::RedirectArrived() {
} }
void ListenBrainzScrobbler::RequestSession(const QUrl &url, const QString &token) { void ListenBrainzScrobbler::RequestAccessToken(const QUrl &redirect_url, const QString &code) {
refresh_login_timer_.stop();
ParamList params = ParamList() << Param("client_id", QByteArray::fromBase64(kClientIDB64))
<< Param("client_secret", QByteArray::fromBase64(kClientSecretB64));
if (!code.isEmpty() && !redirect_url.isEmpty()) {
params << Param("grant_type", "authorization_code");
params << Param("code", code);
params << Param("redirect_uri", redirect_url.toString());
}
else if (!refresh_token_.isEmpty() && enabled_) {
params << Param("grant_type", "refresh_token");
params << Param("refresh_token", refresh_token_);
}
else {
return;
}
QUrl session_url(kAuthTokenUrl);
QUrlQuery url_query; QUrlQuery url_query;
url_query.addQueryItem("grant_type", "authorization_code"); for (const Param &param : params) {
url_query.addQueryItem("code", token); url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
url_query.addQueryItem("client_id", kClientID); }
url_query.addQueryItem("client_secret", kClientSecret);
url_query.addQueryItem("redirect_uri", url.toString()); QUrl session_url(kOAuthAccessTokenUrl);
QNetworkRequest req(session_url); QNetworkRequest req(session_url);
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@@ -257,7 +288,7 @@ void ListenBrainzScrobbler::AuthenticateReplyFinished(QNetworkReply *reply) {
return; return;
} }
if (!json_obj.contains("access_token") || !json_obj.contains("expires_in") || !json_obj.contains("token_type") || !json_obj.contains("refresh_token")) { if (!json_obj.contains("access_token") || !json_obj.contains("expires_in") || !json_obj.contains("token_type")) {
AuthError("Json access_token, expires_in or token_type is missing."); AuthError("Json access_token, expires_in or token_type is missing.");
return; return;
} }
@@ -265,7 +296,10 @@ void ListenBrainzScrobbler::AuthenticateReplyFinished(QNetworkReply *reply) {
access_token_ = json_obj["access_token"].toString(); access_token_ = json_obj["access_token"].toString();
expires_in_ = json_obj["expires_in"].toInt(); expires_in_ = json_obj["expires_in"].toInt();
token_type_ = json_obj["token_type"].toString(); token_type_ = json_obj["token_type"].toString();
refresh_token_ = json_obj["refresh_token"].toString(); if (json_obj.contains("refresh_token")) {
refresh_token_ = json_obj["refresh_token"].toString();
}
login_time_ = QDateTime::currentDateTime().toTime_t();
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
@@ -273,10 +307,18 @@ void ListenBrainzScrobbler::AuthenticateReplyFinished(QNetworkReply *reply) {
s.setValue("expires_in", expires_in_); s.setValue("expires_in", expires_in_);
s.setValue("token_type", token_type_); s.setValue("token_type", token_type_);
s.setValue("refresh_token", refresh_token_); s.setValue("refresh_token", refresh_token_);
s.setValue("login_time", login_time_);
s.endGroup(); s.endGroup();
if (expires_in_ > 0) {
refresh_login_timer_.setInterval(expires_in_ * kMsecPerSec);
refresh_login_timer_.start();
}
emit AuthenticationComplete(true); emit AuthenticationComplete(true);
qLog(Debug) << "ListenBrainz: Authentication was successful, got access token" << access_token_ << "expires in" << expires_in_;
DoSubmit(); DoSubmit();
} }
@@ -439,12 +481,7 @@ void ListenBrainzScrobbler::Scrobble(const Song &song) {
cache_->Add(song, timestamp_); cache_->Add(song, timestamp_);
if (app_->scrobbler()->IsOffline()) return; if (app_->scrobbler()->IsOffline() || !IsAuthenticated()) return;
if (!IsAuthenticated()) {
emit ErrorMessage("ListenBrainz is not authenticated!");
return;
}
if (!submitted_) { if (!submitted_) {
submitted_ = true; submitted_ = true;
@@ -472,7 +509,7 @@ void ListenBrainzScrobbler::DoSubmit() {
void ListenBrainzScrobbler::Submit() { void ListenBrainzScrobbler::Submit() {
qLog(Debug) << __PRETTY_FUNCTION__; qLog(Debug) << "ListenBrainz: Submitting scrobbles.";
submitted_ = false; submitted_ = false;

View File

@@ -30,6 +30,7 @@
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QJsonDocument> #include <QJsonDocument>
#include <QTimer>
#include "core/song.h" #include "core/song.h"
#include "scrobblerservice.h" #include "scrobblerservice.h"
@@ -77,6 +78,7 @@ class ListenBrainzScrobbler : public ScrobblerService {
private slots: private slots:
void RedirectArrived(); void RedirectArrived();
void AuthenticateReplyFinished(QNetworkReply *reply); void AuthenticateReplyFinished(QNetworkReply *reply);
void RequestAccessToken(const QUrl &redirect_url = QUrl(), const QString &code = QString());
void UpdateNowPlayingRequestFinished(QNetworkReply *reply); void UpdateNowPlayingRequestFinished(QNetworkReply *reply);
void ScrobbleRequestFinished(QNetworkReply *reply, QList<quint64>); void ScrobbleRequestFinished(QNetworkReply *reply, QList<quint64>);
@@ -84,19 +86,18 @@ class ListenBrainzScrobbler : public ScrobblerService {
QNetworkReply *CreateRequest(const QUrl &url, const QJsonDocument &json_doc); QNetworkReply *CreateRequest(const QUrl &url, const QJsonDocument &json_doc);
QByteArray GetReplyData(QNetworkReply *reply); QByteArray GetReplyData(QNetworkReply *reply);
void RequestSession(const QUrl &url, const QString &token);
void AuthError(const QString &error); void AuthError(const QString &error);
void Error(const QString &error, const QVariant &debug = QVariant()); void Error(const QString &error, const QVariant &debug = QVariant());
void DoSubmit(); void DoSubmit();
void CheckScrobblePrevSong(); void CheckScrobblePrevSong();
static const char *kAuthUrl; static const char *kOAuthAuthorizeUrl;
static const char *kAuthTokenUrl; static const char *kOAuthAccessTokenUrl;
static const char *kOAuthRedirectUrl;
static const char *kApiUrl; static const char *kApiUrl;
static const char *kClientID; static const char *kClientIDB64;
static const char *kClientSecret; static const char *kClientSecretB64;
static const char *kCacheFile; static const char *kCacheFile;
static const char *kRedirectUrl;
static const int kScrobblesPerRequest; static const int kScrobblesPerRequest;
Application *app_; Application *app_;
@@ -109,10 +110,12 @@ class ListenBrainzScrobbler : public ScrobblerService {
qint64 expires_in_; qint64 expires_in_;
QString token_type_; QString token_type_;
QString refresh_token_; QString refresh_token_;
quint64 login_time_;
bool submitted_; bool submitted_;
Song song_playing_; Song song_playing_;
bool scrobbled_; bool scrobbled_;
quint64 timestamp_; quint64 timestamp_;
QTimer refresh_login_timer_;
}; };