moved authentication into sonos class
parent
5ba83790c8
commit
cd17134c19
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "sonos/sonos/noson"]
|
||||
path = sonos/sonos/noson
|
||||
url = https://github.com/janbar/noson.git
|
||||
|
|
@ -32,7 +32,6 @@
|
|||
|
||||
DevicePluginSonos::DevicePluginSonos()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -83,20 +82,22 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device)
|
|||
});
|
||||
}
|
||||
|
||||
if(!m_tokenRefreshTimer) {
|
||||
m_tokenRefreshTimer = new QTimer(this);
|
||||
m_tokenRefreshTimer->setSingleShot(false);
|
||||
connect(m_tokenRefreshTimer, &QTimer::timeout, this, &DevicePluginSonos::onRefreshTimeout);
|
||||
}
|
||||
|
||||
if (device->deviceClassId() == sonosConnectionDeviceClassId) {
|
||||
qCDebug(dcSonos()) << "Sonos OAuth setup complete";
|
||||
Sonos *sonos;
|
||||
if (m_setupSonosConnections.keys().contains(device->id())) {
|
||||
//Fresh device setup, has already a fresh access token
|
||||
sonos = m_setupSonosConnections.take(device->id());
|
||||
} else {
|
||||
//device loaded from the device database, needs a new access token;
|
||||
pluginStorage()->beginGroup(device->id().toString());
|
||||
QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray();
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
pluginStorage()->beginGroup(device->id().toString());
|
||||
QByteArray accessToken = pluginStorage()->value("access_token").toByteArray();
|
||||
pluginStorage()->endGroup();
|
||||
sonos = new Sonos(hardwareManager()->networkManager(), "b15cbf8c-a39c-47aa-bd93-635a96e9696c", "c086ba71-e562-430b-a52f-867c6482fd11", "", this);
|
||||
sonos->getAccessTokenFromRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
Sonos *sonos = new Sonos(hardwareManager()->networkManager(), accessToken, this);
|
||||
connect(sonos, &Sonos::connectionChanged, this, &DevicePluginSonos::onConnectionChanged);
|
||||
connect(sonos, &Sonos::householdIdsReceived, this, &DevicePluginSonos::onHouseholdIdsReceived);
|
||||
connect(sonos, &Sonos::groupsReceived, this, &DevicePluginSonos::onGroupsReceived);
|
||||
|
|
@ -115,22 +116,13 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device)
|
|||
DevicePairingInfo DevicePluginSonos::pairDevice(DevicePairingInfo &devicePairingInfo)
|
||||
{
|
||||
if (devicePairingInfo.deviceClassId() == sonosConnectionDeviceClassId) {
|
||||
QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
|
||||
QString clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11";
|
||||
|
||||
QUrl url("https://api.sonos.com/login/v3/oauth");
|
||||
QUrlQuery queryParams;
|
||||
queryParams.addQueryItem("client_id", clientId);
|
||||
queryParams.addQueryItem("redirect_uri", "https://127.0.0.1:8888");
|
||||
queryParams.addQueryItem("response_type", "code");
|
||||
queryParams.addQueryItem("scope", "playback-control-all");
|
||||
queryParams.addQueryItem("state", QUuid::createUuid().toString());
|
||||
url.setQuery(queryParams);
|
||||
|
||||
Sonos *sonos = new Sonos(hardwareManager()->networkManager(), "b15cbf8c-a39c-47aa-bd93-635a96e9696c", "c086ba71-e562-430b-a52f-867c6482fd11", "", this);
|
||||
QUrl url = sonos->getLoginUrl(QUrl("https://127.0.0.1:8000"));
|
||||
qCDebug(dcSonos()) << "Sonos url:" << url;
|
||||
|
||||
devicePairingInfo.setOAuthUrl(url);
|
||||
devicePairingInfo.setStatus(Device::DeviceErrorNoError);
|
||||
m_setupSonosConnections.insert(devicePairingInfo.deviceId(), sonos);
|
||||
return devicePairingInfo;
|
||||
}
|
||||
|
||||
|
|
@ -148,61 +140,39 @@ DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePai
|
|||
qCDebug(dcSonos()) << "Secret is" << secret;
|
||||
QUrl url(secret);
|
||||
QUrlQuery query(url);
|
||||
qCDebug(dcSonos()) << "Acess code is:" << query.queryItemValue("code");
|
||||
QByteArray accessCode = query.queryItemValue("code").toLocal8Bit();
|
||||
qCDebug(dcSonos()) << "Acess code is:" << accessCode;
|
||||
|
||||
QString accessCode = query.queryItemValue("code");
|
||||
Sonos *sonos = m_setupSonosConnections.value(devicePairingInfo.deviceId());
|
||||
|
||||
// Obtaining access token
|
||||
url = QUrl("https://api.sonos.com/login/v3/oauth/access");
|
||||
query.clear();
|
||||
query.addQueryItem("grant_type", "authorization_code");
|
||||
query.addQueryItem("code", accessCode);
|
||||
query.addQueryItem("redirect_uri", "https%3A%2F%2F127.0.0.1%3A8888");
|
||||
url.setQuery(query);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8");
|
||||
|
||||
QByteArray clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
|
||||
QByteArray clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11";
|
||||
|
||||
QByteArray auth = QByteArray(clientId + ':' + clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals);
|
||||
request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8());
|
||||
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, devicePairingInfo](){
|
||||
reply->deleteLater();
|
||||
if (!sonos) {
|
||||
m_setupSonosConnections.remove(devicePairingInfo.deviceId());
|
||||
sonos->deleteLater();
|
||||
devicePairingInfo.setStatus(Device::DeviceErrorHardwareFailure);
|
||||
return devicePairingInfo;
|
||||
}
|
||||
sonos->getAccessTokenFromAuthorizationCode(accessCode);
|
||||
connect(sonos, &Sonos::authenticationStatusChanged, this, [devicePairingInfo, this](bool authenticated){
|
||||
Sonos *sonos = static_cast<Sonos *>(sender());
|
||||
DevicePairingInfo info(devicePairingInfo);
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
|
||||
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) {
|
||||
if(!authenticated) {
|
||||
m_setupSonosConnections.remove(info.deviceId());
|
||||
sonos->deleteLater();
|
||||
info.setStatus(Device::DeviceErrorSetupFailed);
|
||||
emit pairingFinished(info);
|
||||
return;
|
||||
}
|
||||
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
|
||||
QByteArray accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
|
||||
qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString();
|
||||
QByteArray refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray();
|
||||
QByteArray accessToken = sonos->accessToken();
|
||||
QByteArray refreshToken = sonos->refreshToken();
|
||||
|
||||
pluginStorage()->beginGroup(info.deviceId().toString());
|
||||
pluginStorage()->setValue("access_token", accessToken);
|
||||
pluginStorage()->setValue("refresh_token", refreshToken);
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
//m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
//TODO
|
||||
}
|
||||
|
||||
info.setStatus(Device::DeviceErrorNoError);
|
||||
emit pairingFinished(info);
|
||||
});
|
||||
|
||||
devicePairingInfo.setStatus(Device::DeviceErrorAsync);
|
||||
return devicePairingInfo;
|
||||
}
|
||||
|
|
@ -348,39 +318,18 @@ void DevicePluginSonos::onConnectionChanged(bool connected)
|
|||
}
|
||||
}
|
||||
|
||||
void DevicePluginSonos::onRefreshTimeout()
|
||||
void DevicePluginSonos::onAuthenticationStatusChanged(bool authenticated)
|
||||
{
|
||||
qCDebug(dcSonos) << "Refresh authentication token";
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("grant_type", "refresh_token");
|
||||
query.addQueryItem("refresh_token", m_sonosConnectionRefreshToken);
|
||||
|
||||
QUrl url("https://api.sonos.com/login/v3/oauth");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
|
||||
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token")) {
|
||||
return;
|
||||
}
|
||||
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
|
||||
m_sonosConnectionAccessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcSonos()) << "Token refresh timer not initialized";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
});
|
||||
Sonos *sonosConnection = static_cast<Sonos *>(sender());
|
||||
Device *device = m_sonosConnections.key(sonosConnection);
|
||||
device->setStateValue(sonosConnectionLoggedInStateTypeId, authenticated);
|
||||
if (!authenticated) {
|
||||
//refresh access token needs to be refreshed
|
||||
pluginStorage()->beginGroup(device->id().toString());
|
||||
QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray();
|
||||
pluginStorage()->endGroup();
|
||||
sonosConnection->getAccessTokenFromRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
void DevicePluginSonos::onHouseholdIdsReceived(QList<QString> householdIds)
|
||||
|
|
@ -419,8 +368,9 @@ void DevicePluginSonos::onPlaylistSummaryReceived(const QString &householdId, So
|
|||
}
|
||||
}
|
||||
|
||||
void DevicePluginSonos::onGroupsReceived(QList<Sonos::GroupObject> groupObjects)
|
||||
void DevicePluginSonos::onGroupsReceived(const QString &householdId, QList<Sonos::GroupObject> groupObjects)
|
||||
{
|
||||
Q_UNUSED(householdId);
|
||||
Sonos *sonos = static_cast<Sonos *>(sender());
|
||||
Device *parentDevice = m_sonosConnections.key(sonos);
|
||||
if (!parentDevice)
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
|
||||
class DevicePluginSonos : public DevicePlugin
|
||||
{
|
||||
|
|
@ -54,8 +52,8 @@ public:
|
|||
private:
|
||||
PluginTimer *m_pluginTimer5sec = nullptr;
|
||||
PluginTimer *m_pluginTimer60sec = nullptr;
|
||||
QTimer *m_tokenRefreshTimer = nullptr;
|
||||
|
||||
QHash<DeviceId, Sonos *> m_setupSonosConnections;
|
||||
QHash<Device *, Sonos *> m_sonosConnections;
|
||||
QList<QByteArray> m_householdIds;
|
||||
|
||||
|
|
@ -63,16 +61,17 @@ private:
|
|||
QByteArray m_sonosConnectionRefreshToken;
|
||||
|
||||
QHash<QUuid, ActionId> m_pendingActions;
|
||||
|
||||
private slots:
|
||||
void onConnectionChanged(bool connected);
|
||||
void onRefreshTimeout();
|
||||
void onAuthenticationStatusChanged(bool authenticated);
|
||||
|
||||
void onHouseholdIdsReceived(QList<QString> householdIds);
|
||||
void onFavouritesReceived(const QString &householdId, QList<Sonos::FavouriteObject> favourites);
|
||||
void onPlaylistsReceived(const QString &householdId, QList<Sonos::PlaylistObject> playlists);
|
||||
void onPlaylistSummaryReceived(const QString &householdId, Sonos::PlaylistSummaryObject playlistSummary);
|
||||
|
||||
void onGroupsReceived(QList<Sonos::GroupObject> groupIds);
|
||||
void onGroupsReceived(const QString &householdId, QList<Sonos::GroupObject> groupIds);
|
||||
void onPlayBackStatusReceived(const QString &groupId, Sonos::PlayBackObject playBack);
|
||||
void onMetadataStatusReceived(const QString &groupId, Sonos::MetadataStatus metaDataStatus);
|
||||
void onVolumeReceived(const QString &groupId, Sonos::VolumeObject groupVolume);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@
|
|||
"displayNameEvent": "connected changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"id": "48b5c1bf-7df0-45d0-9ba3-290fc3acddc3",
|
||||
"name": "loggedIn",
|
||||
"displayName": "Logged in",
|
||||
"displayNameEvent": "Logged in changed",
|
||||
"defaultValue": false,
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
221
sonos/oauth.cpp
221
sonos/oauth.cpp
|
|
@ -1,221 +0,0 @@
|
|||
#include "oauth.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
OAuth::OAuth(QString clientId, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_clientId(clientId),
|
||||
m_authenticated(false)
|
||||
{
|
||||
m_networkManager = new QNetworkAccessManager(this);
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &OAuth::replyFinished);
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setSingleShot(false);
|
||||
|
||||
connect(m_timer, &QTimer::timeout, this, &OAuth::refreshTimeout);
|
||||
}
|
||||
|
||||
QUrl OAuth::url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
void OAuth::setUrl(const QUrl &url)
|
||||
{
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
QUrlQuery OAuth::query() const
|
||||
{
|
||||
return m_query;
|
||||
}
|
||||
|
||||
void OAuth::setQuery(const QUrlQuery &query)
|
||||
{
|
||||
m_query = query;
|
||||
}
|
||||
|
||||
QString OAuth::clientId() const
|
||||
{
|
||||
return m_clientId;
|
||||
}
|
||||
|
||||
void OAuth::setClientId(const QString &clientId)
|
||||
{
|
||||
m_clientId = clientId;
|
||||
}
|
||||
|
||||
QString OAuth::scope() const
|
||||
{
|
||||
return m_scope;
|
||||
}
|
||||
|
||||
void OAuth::setScope(const QString &scope)
|
||||
{
|
||||
m_scope = scope;
|
||||
}
|
||||
|
||||
QString OAuth::authorizationCode() const
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
|
||||
QString OAuth::bearerToken() const
|
||||
{
|
||||
return m_token;
|
||||
}
|
||||
|
||||
bool OAuth::authenticated() const
|
||||
{
|
||||
return m_authenticated;
|
||||
}
|
||||
|
||||
void OAuth::startAuthentication()
|
||||
{
|
||||
qCDebug(dcSonos) << "Start authentication";
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("client_id", m_clientId);
|
||||
query.addQueryItem("redirect_uri", m_redirectUri);
|
||||
query.addQueryItem("response_type", "code");
|
||||
query.addQueryItem("scope", m_scope);
|
||||
m_state = QUuid().toByteArray();
|
||||
query.addQueryItem("state", m_state);
|
||||
setQuery(query);
|
||||
|
||||
QNetworkRequest request(m_url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
m_tokenRequests.append(m_networkManager->post(request, m_query.toString().toUtf8()));
|
||||
}
|
||||
|
||||
void OAuth::setAuthenticated(const bool &authenticated)
|
||||
{
|
||||
if (authenticated) {
|
||||
qCDebug(dcSonos()) << "Authenticated successfully";
|
||||
} else {
|
||||
m_timer->stop();
|
||||
qCWarning(dcSonos) << "Authentication failed";
|
||||
}
|
||||
m_authenticated = authenticated;
|
||||
emit authenticationChanged();
|
||||
}
|
||||
|
||||
void OAuth::setToken(const QString &token)
|
||||
{
|
||||
m_token = token;
|
||||
emit tokenChanged();
|
||||
}
|
||||
|
||||
void OAuth::replyFinished(QNetworkReply *reply)
|
||||
{
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
reply->deleteLater();
|
||||
// token request
|
||||
if (m_tokenRequests.contains(reply)) {
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
m_tokenRequests.removeAll(reply);
|
||||
|
||||
// check HTTP status code
|
||||
if (status != 200) {
|
||||
qCWarning(dcSonos) << "Request token reply HTTP error:" << status << reply->errorString();
|
||||
qCWarning(dcSonos) << data;
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// check JSON
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSonos) << "Request token reply JSON error:" << error.errorString();
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.toVariant().toMap().contains("code")) {
|
||||
qCWarning(dcSonos) << "Could not get code" << jsonDoc.toJson();
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.toVariant().toMap().contains("state")) {
|
||||
qCWarning(dcSonos) << "Could not get state" << jsonDoc.toJson();
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonDoc.toVariant().toMap().value("state").toString() != m_state) {
|
||||
qCWarning(dcSonos) << "State doesn't match. Expected:" << m_state << "Received:" << jsonDoc.toVariant().toMap().value("state").toString();
|
||||
}
|
||||
|
||||
setToken(jsonDoc.toVariant().toMap().value("code").toString());
|
||||
setAuthenticated(true);
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString();
|
||||
qCDebug(dcSonos) << "Token will be refreshed in" << expireTime << "[s]";
|
||||
m_timer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
|
||||
} else if (m_refreshTokenRequests.contains(reply)) {
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
m_refreshTokenRequests.removeAll(reply);
|
||||
|
||||
// check HTTP status code
|
||||
if (status != 200) {
|
||||
qCWarning(dcSonos) << "Refresh token reply HTTP error:" << status << reply->errorString();
|
||||
qCWarning(dcSonos) << data;
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// check JSON
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSonos) << "Refresh token reply JSON error:" << error.errorString();
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.toVariant().toMap().contains("access_token")) {
|
||||
qCWarning(dcSonos) << "Could not get access token after refresh" << jsonDoc.toJson();
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setToken(jsonDoc.toVariant().toMap().value("access_token").toString());
|
||||
qCDebug(dcSonos) << "Token refreshed successfully";
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString();
|
||||
qCDebug(dcSonos) << "Token will be refreshed in" << expireTime << "[s]";
|
||||
m_timer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
|
||||
if (!authenticated())
|
||||
setAuthenticated(true);
|
||||
}
|
||||
}
|
||||
|
||||
void OAuth::refreshTimeout()
|
||||
{
|
||||
qCDebug(dcSonos) << "Refresh authentication token for";
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("grant_type", "refresh_token");
|
||||
query.addQueryItem("refresh_token", m_refreshToken);
|
||||
query.addQueryItem("client_id", m_clientId);
|
||||
|
||||
QNetworkRequest request(m_url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
m_refreshTokenRequests.append(m_networkManager->post(request, query.toString().toUtf8()));
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#ifndef OAUTH_H
|
||||
#define OAUTH_H
|
||||
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class OAuth : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OAuth(QString clientId, QObject *parent = nullptr);
|
||||
|
||||
QUrl url() const;
|
||||
void setUrl(const QUrl &url);
|
||||
|
||||
QUrlQuery query() const;
|
||||
void setQuery(const QUrlQuery &query);
|
||||
|
||||
QString clientId() const;
|
||||
void setClientId(const QString &clientId);
|
||||
|
||||
QString scope() const;
|
||||
void setScope(const QString &scope);
|
||||
|
||||
QByteArray authorizationCode() const;
|
||||
QByteArray bearerToken() const;
|
||||
|
||||
bool authenticated() const;
|
||||
|
||||
void startAuthentication();
|
||||
|
||||
private:
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QTimer *m_timer;
|
||||
QList<QNetworkReply *> m_tokenRequests;
|
||||
QList<QNetworkReply *> m_refreshTokenRequests;
|
||||
|
||||
QUrl m_url;
|
||||
QUrlQuery m_query;
|
||||
QString m_clientId;
|
||||
QString m_scope;
|
||||
QString m_state;
|
||||
QString m_redirectUri;
|
||||
QString m_responseType;
|
||||
|
||||
QString m_token;
|
||||
QString m_refreshToken;
|
||||
|
||||
bool m_authenticated;
|
||||
|
||||
void setAuthenticated(const bool &authenticated);
|
||||
void setToken(const QString &token);
|
||||
|
||||
private slots:
|
||||
void replyFinished(QNetworkReply *reply);
|
||||
void refreshTimeout();
|
||||
|
||||
signals:
|
||||
void authenticationChanged();
|
||||
void tokenChanged();
|
||||
};
|
||||
|
||||
#endif // OAUTH_H
|
||||
202
sonos/sonos.cpp
202
sonos/sonos.cpp
|
|
@ -26,17 +26,46 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUrlQuery>
|
||||
|
||||
Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &accessToken, QObject *parent) :
|
||||
Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, const QByteArray &refreshToken, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_accessToken(accessToken),
|
||||
m_clientKey(clientKey),
|
||||
m_clientSecret(clientSecret),
|
||||
m_refreshToken(refreshToken),
|
||||
m_networkManager(networkmanager)
|
||||
{
|
||||
if(!m_tokenRefreshTimer) {
|
||||
m_tokenRefreshTimer = new QTimer(this);
|
||||
m_tokenRefreshTimer->setSingleShot(true);
|
||||
connect(m_tokenRefreshTimer, &QTimer::timeout, this, &Sonos::onRefreshTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void Sonos::setAccessToken(const QByteArray &accessToken)
|
||||
QUrl Sonos::getLoginUrl(const QUrl &redirectUrl)
|
||||
{
|
||||
m_accessToken = accessToken;
|
||||
QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
|
||||
|
||||
QUrl url("https://api.sonos.com/login/v3/oauth");
|
||||
QUrlQuery queryParams;
|
||||
queryParams.addQueryItem("client_id", clientId);
|
||||
queryParams.addQueryItem("redirect_uri", redirectUrl.toString());
|
||||
queryParams.addQueryItem("response_type", "code");
|
||||
queryParams.addQueryItem("scope", "playback-control-all");
|
||||
queryParams.addQueryItem("state", QUuid::createUuid().toString());
|
||||
url.setQuery(queryParams);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
QByteArray Sonos::accessToken()
|
||||
{
|
||||
return m_accessToken;
|
||||
}
|
||||
|
||||
QByteArray Sonos::refreshToken()
|
||||
{
|
||||
return m_refreshToken;
|
||||
}
|
||||
|
||||
void Sonos::getHouseholds()
|
||||
|
|
@ -44,7 +73,7 @@ void Sonos::getHouseholds()
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/households"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
qDebug(dcSonos()) << "Sending request" << request.url() << request.rawHeaderList() << request.rawHeader("Authorization");
|
||||
|
|
@ -53,6 +82,12 @@ void Sonos::getHouseholds()
|
|||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
connectionChanged(true);
|
||||
|
||||
if (status == 401) {
|
||||
//Authentication required
|
||||
getAccessTokenFromRefreshToken(m_refreshToken);
|
||||
return;
|
||||
}
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSonos()) << "Request error:" << status << reply->errorString();
|
||||
|
|
@ -82,7 +117,7 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/favourites"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -90,7 +125,7 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId)
|
|||
object.insert("favoriteId", QJsonValue::fromVariant(favouriteId));
|
||||
QJsonDocument doc(object);
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData());
|
||||
QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact));
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
|
@ -101,8 +136,6 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId)
|
|||
emit actionExecuted(actionId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO parse response
|
||||
emit actionExecuted(actionId, true);
|
||||
});
|
||||
return actionId;
|
||||
|
|
@ -113,7 +146,7 @@ void Sonos::getFavorites(const QString &householdId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/favorites"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] {
|
||||
|
|
@ -126,7 +159,6 @@ void Sonos::getFavorites(const QString &householdId)
|
|||
return;
|
||||
}
|
||||
|
||||
//qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll();
|
||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll());
|
||||
if (!data.isObject())
|
||||
return;
|
||||
|
|
@ -156,10 +188,10 @@ void Sonos::getGroups(const QString &householdId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/groups"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] {
|
||||
reply->deleteLater();
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
|
|
@ -187,7 +219,7 @@ void Sonos::getGroups(const QString &householdId)
|
|||
group.displayName = obj["name"].toString();
|
||||
groupObjects.append(group);
|
||||
}
|
||||
emit groupsReceived(groupObjects);
|
||||
emit groupsReceived(householdId, groupObjects);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +228,7 @@ void Sonos::getGroupVolume(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, groupId, this] {
|
||||
|
|
@ -229,7 +261,7 @@ QUuid Sonos::setGroupVolume(const QString &groupId, int volume)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -260,7 +292,7 @@ QUuid Sonos::setGroupMute(const QString &groupId, bool mute)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume/mute"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -292,7 +324,7 @@ QUuid Sonos::setGroupRelativeVolume(const QString &groupId, int volumeDelta)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume/relative"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -324,7 +356,7 @@ void Sonos::getGroupPlaybackStatus(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, this, groupId] {
|
||||
|
|
@ -378,7 +410,7 @@ QUuid Sonos::groupLoadLineIn(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/lineIn"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -404,7 +436,7 @@ QUuid Sonos::groupPlay(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/play"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -432,7 +464,7 @@ QUuid Sonos::groupPause(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/pause"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -461,7 +493,7 @@ QUuid Sonos::groupSeek(const QString &groupId, int possitionMillis)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/seek"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -490,7 +522,7 @@ QUuid Sonos::groupSeekRelative(const QString &groupId, int deltaMillis)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/seekRelative"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -519,7 +551,7 @@ QUuid Sonos::groupSetPlayModes(const QString &groupId, PlayMode playMode)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -554,7 +586,7 @@ QUuid Sonos::groupSetShuffle(const QString &groupId, bool shuffle)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -586,7 +618,7 @@ QUuid Sonos::groupSetRepeat(const QString &groupId, RepeatMode repeatMode)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -630,7 +662,7 @@ QUuid Sonos::groupSetCrossfade(const QString &groupId, bool crossfade)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -662,7 +694,7 @@ QUuid Sonos::groupSkipToNextTrack(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/skipToNextTrack"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -688,7 +720,7 @@ QUuid Sonos::groupSkipToPreviousTrack(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/skipToPreviousTrack"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -714,7 +746,7 @@ QUuid Sonos::groupTogglePlayPause(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/togglePlayPause"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -740,7 +772,7 @@ void Sonos::getGroupMetadataStatus(const QString &groupId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playbackMetadata"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, groupId, this] {
|
||||
|
|
@ -871,7 +903,7 @@ void Sonos::getPlayerVolume(const QByteArray &playerId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] {
|
||||
|
|
@ -902,7 +934,7 @@ QUuid Sonos::setPlayerVolume(const QByteArray &playerId, int volume)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -934,7 +966,7 @@ QUuid Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume/relative"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -964,7 +996,7 @@ QUuid Sonos::setPlayerMute(const QByteArray &playerId, bool mute)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -994,7 +1026,7 @@ void Sonos::getPlaylist(const QString &householdId, const QString &playlistId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists/getPlaylist"));
|
||||
|
||||
|
||||
|
|
@ -1041,7 +1073,7 @@ void Sonos::getPlaylists(const QString &householdId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] {
|
||||
|
|
@ -1083,7 +1115,7 @@ QUuid Sonos::loadPlaylist(const QString &groupId, const QString &playlistId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playlists"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -1112,7 +1144,7 @@ void Sonos::getPlayerSettings(const QString &playerId)
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player"));
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] {
|
||||
|
|
@ -1142,7 +1174,7 @@ QUuid Sonos::setPlayerSettings(const QString &playerId, PlayerSettingsObject set
|
|||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_apiKey);
|
||||
request.setRawHeader("X-Sonos-Api-Key", m_clientKey);
|
||||
request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player"));
|
||||
QUuid actionId = QUuid::createUuid();
|
||||
|
||||
|
|
@ -1169,3 +1201,91 @@ QUuid Sonos::setPlayerSettings(const QString &playerId, PlayerSettingsObject set
|
|||
});
|
||||
return actionId;
|
||||
}
|
||||
|
||||
void Sonos::onRefreshTimeout()
|
||||
{
|
||||
qCDebug(dcSonos) << "Refresh authentication token";
|
||||
getAccessTokenFromRefreshToken(m_refreshToken);
|
||||
}
|
||||
|
||||
|
||||
void Sonos::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
|
||||
{
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("grant_type", "refresh_token");
|
||||
query.addQueryItem("refresh_token", refreshToken);
|
||||
|
||||
QUrl url("https://api.sonos.com/login/v3/oauth");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
QNetworkReply *reply = m_networkManager->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
|
||||
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token")) {
|
||||
emit authenticationStatusChanged(false);
|
||||
return;
|
||||
}
|
||||
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
|
||||
m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcSonos()) << "Token refresh timer not initialized";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
emit authenticationStatusChanged(true);;
|
||||
});
|
||||
}
|
||||
|
||||
void Sonos::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode)
|
||||
{
|
||||
// Obtaining access token
|
||||
QUrl url = QUrl(m_baseAuthorizationUrl);
|
||||
QUrlQuery query;
|
||||
query.clear();
|
||||
query.addQueryItem("grant_type", "authorization_code");
|
||||
query.addQueryItem("code", authorizationCode);
|
||||
query.addQueryItem("redirect_uri", "https%3A%2F%2F127.0.0.1%3A8888");
|
||||
url.setQuery(query);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8");
|
||||
|
||||
QByteArray auth = QByteArray(m_clientKey + ':' + m_clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals);
|
||||
request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8());
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
|
||||
qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) {
|
||||
emit authenticationStatusChanged(false);;
|
||||
return;
|
||||
}
|
||||
qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
|
||||
m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
|
||||
qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString();
|
||||
m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray();
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcSonos()) << "Token refresh timer not initialized";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
emit authenticationStatusChanged(true);;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#define SONOS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "network/networkaccessmanager.h"
|
||||
#include "devices/device.h"
|
||||
|
|
@ -150,14 +151,14 @@ public:
|
|||
};
|
||||
|
||||
struct PlayBackObject {
|
||||
QString itemId;
|
||||
bool isDucking;
|
||||
PlayBackState playbackState;
|
||||
PlayMode playMode;
|
||||
uint positionMillis;
|
||||
QString previousItemId;
|
||||
uint previousPositionMillis;
|
||||
QString queueVersion;
|
||||
QString itemId;
|
||||
bool isDucking;
|
||||
PlayBackState playbackState;
|
||||
PlayMode playMode;
|
||||
uint positionMillis;
|
||||
QString previousItemId;
|
||||
uint previousPositionMillis;
|
||||
QString queueVersion;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -206,9 +207,13 @@ public:
|
|||
QList<PlaylistTrackObject> tracks;
|
||||
};
|
||||
|
||||
explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &accessToken, QObject *parent = nullptr);
|
||||
explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &clientId, const QByteArray &clientSecret, const QByteArray &refreshToken = "", QObject *parent = nullptr);
|
||||
|
||||
void setAccessToken(const QByteArray &accessToken);
|
||||
QUrl getLoginUrl(const QUrl &redirectUrl);
|
||||
QByteArray accessToken();
|
||||
QByteArray refreshToken();
|
||||
void getAccessTokenFromRefreshToken(const QByteArray &refreshToken);
|
||||
void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode);
|
||||
|
||||
void getHouseholds();
|
||||
void getFavorites(const QString &householdId);
|
||||
|
|
@ -259,21 +264,27 @@ public:
|
|||
QUuid setPlayerSettings(const QString &playerId, PlayerSettingsObject settings);
|
||||
|
||||
private:
|
||||
QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth";
|
||||
QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth/access";
|
||||
QByteArray m_baseControlUrl = "https://api.ws.sonos.com/control/api/v1";
|
||||
QByteArray m_apiKey = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
|
||||
QByteArray m_clientKey;
|
||||
QByteArray m_clientSecret;
|
||||
|
||||
QByteArray m_accessToken;
|
||||
QByteArray m_refreshToken;
|
||||
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
QTimer *m_tokenRefreshTimer = nullptr;
|
||||
private slots:
|
||||
|
||||
void onRefreshTimeout();
|
||||
|
||||
signals:
|
||||
void connectionChanged(bool connected);
|
||||
void authenticationStatusChanged(bool authenticated);
|
||||
|
||||
void householdIdsReceived(QList<QString> householdIds);
|
||||
void favouritesReceived(const QString &householdId, QList<FavouriteObject> favourites);
|
||||
void playlistsReceived(const QString &householdId, QList<PlaylistObject> playlists);
|
||||
void groupsReceived(QList<GroupObject> groups);
|
||||
void groupsReceived(const QString &householdId, QList<GroupObject> groups);
|
||||
void playlistSummaryReceived(const QString &householdId, PlaylistSummaryObject playlistSummary);
|
||||
|
||||
void playBackStatusReceived(const QString &groupId, PlayBackObject playBack);
|
||||
|
|
@ -283,7 +294,5 @@ signals:
|
|||
void playerVolumeReceived(const QString &playerId, VolumeObject playerVolume);
|
||||
void playerSettingsRecieved(const QString &playerId, PlayerSettingsObject playerSettings);
|
||||
void actionExecuted(QUuid actionId,bool success);
|
||||
|
||||
};
|
||||
|
||||
#endif // SONOS_H
|
||||
|
|
|
|||
|
|
@ -11,5 +11,3 @@ SOURCES += \
|
|||
HEADERS += \
|
||||
devicepluginsonos.h \
|
||||
sonos.h \
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue