mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 11:36:59 +03:00
MacOS auth for Spotify
This commit is contained in:
@@ -298,7 +298,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS)
|
||||
|
||||
if(MSVC)
|
||||
unset(CUSTOM_COMMAND_PREFIX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ HTTPClient::Response::~Response() {
|
||||
|
||||
void HTTPClient::Response::rawRequest(const std::string& url,
|
||||
const std::string& method,
|
||||
const std::string& content,
|
||||
const std::vector<uint8_t>& content,
|
||||
Headers& headers) {
|
||||
urlParser = bell::URLParser::parse(url);
|
||||
|
||||
@@ -50,6 +50,10 @@ void HTTPClient::Response::rawRequest(const std::string& url,
|
||||
}
|
||||
|
||||
socketStream << reqEnd;
|
||||
// Write request body
|
||||
if (content.size() > 0) {
|
||||
socketStream.write((const char*)content.data(), content.size());
|
||||
}
|
||||
socketStream.flush();
|
||||
|
||||
// Parse response
|
||||
@@ -115,7 +119,13 @@ void HTTPClient::Response::readResponseHeaders() {
|
||||
|
||||
void HTTPClient::Response::get(const std::string& url, Headers headers) {
|
||||
std::string method = "GET";
|
||||
return this->rawRequest(url, method, "", headers);
|
||||
return this->rawRequest(url, method, {}, headers);
|
||||
}
|
||||
|
||||
void HTTPClient::Response::post(const std::string& url, Headers headers,
|
||||
const std::vector<uint8_t>& body) {
|
||||
std::string method = "POST";
|
||||
return this->rawRequest(url, method, body, headers);
|
||||
}
|
||||
|
||||
size_t HTTPClient::Response::contentLength() {
|
||||
|
||||
@@ -55,8 +55,10 @@ class HTTPClient {
|
||||
void connect(const std::string& url);
|
||||
|
||||
void rawRequest(const std::string& method, const std::string& url,
|
||||
const std::string& content, Headers& headers);
|
||||
const std::vector<uint8_t>& content, Headers& headers);
|
||||
void get(const std::string& url, Headers headers = {});
|
||||
void post(const std::string& url, Headers headers = {},
|
||||
const std::vector<uint8_t>& body = {});
|
||||
|
||||
std::string_view body();
|
||||
std::vector<uint8_t> bytes();
|
||||
@@ -102,5 +104,14 @@ class HTTPClient {
|
||||
response->get(url, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
static std::unique_ptr<Response> post(const std::string& url,
|
||||
Headers headers = {},
|
||||
const std::vector<uint8_t>& body = {}) {
|
||||
auto response = std::make_unique<Response>();
|
||||
response->connect(url);
|
||||
response->post(url, headers, body);
|
||||
return response;
|
||||
}
|
||||
};
|
||||
} // namespace bell
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic> // or std::atomic
|
||||
#include <functional> // for function
|
||||
#include <memory> // for shared_ptr
|
||||
#include <string> // for string
|
||||
#include <atomic>
|
||||
|
||||
namespace bell {
|
||||
class WrappedSemaphore;
|
||||
};
|
||||
namespace cspot {
|
||||
struct Context;
|
||||
|
||||
@@ -35,7 +32,6 @@ class AccessKeyFetcher {
|
||||
|
||||
private:
|
||||
std::shared_ptr<cspot::Context> ctx;
|
||||
std::shared_ptr<bell::WrappedSemaphore> updateSemaphore;
|
||||
|
||||
std::atomic<bool> keyPending = false;
|
||||
std::string accessKey;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "LoginBlob.h"
|
||||
@@ -14,6 +15,7 @@ struct Context {
|
||||
AudioFormat audioFormat = AudioFormat::AudioFormat_OGG_VORBIS_160;
|
||||
std::string deviceId;
|
||||
std::string deviceName;
|
||||
std::vector<uint8_t> authData;
|
||||
int volume;
|
||||
|
||||
std::string username;
|
||||
@@ -33,6 +35,7 @@ struct Context {
|
||||
ctx->session = std::make_shared<MercurySession>(ctx->timeProvider);
|
||||
ctx->config.deviceId = blob->getDeviceId();
|
||||
ctx->config.deviceName = blob->getDeviceName();
|
||||
ctx->config.authData = blob->authData;
|
||||
ctx->config.volume = 0;
|
||||
ctx->config.username = blob->getUserName();
|
||||
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#include <type_traits> // for remove_extent_t
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "BellLogger.h" // for AbstractLogger
|
||||
#include "CSpotContext.h" // for Context
|
||||
#include "Logger.h" // for CSPOT_LOG
|
||||
#include "MercurySession.h" // for MercurySession, MercurySession::Res...
|
||||
#include "Packet.h" // for cspot
|
||||
#include "TimeProvider.h" // for TimeProvider
|
||||
#include "Utils.h" // for string_format
|
||||
#include "WrappedSemaphore.h"
|
||||
#include "BellLogger.h" // for AbstractLogger
|
||||
#include "CSpotContext.h" // for Context
|
||||
#include "HTTPClient.h"
|
||||
#include "Logger.h" // for CSPOT_LOG
|
||||
#include "MercurySession.h" // for MercurySession, MercurySession::Res...
|
||||
#include "NanoPBExtensions.h" // for bell::nanopb::encode...
|
||||
#include "NanoPBHelper.h" // for pbEncode and pbDecode
|
||||
#include "Packet.h" // for cspot
|
||||
#include "TimeProvider.h" // for TimeProvider
|
||||
#include "Utils.h" // for string_format
|
||||
|
||||
#ifdef BELL_ONLY_CJSON
|
||||
#include "cJSON.h"
|
||||
#else
|
||||
@@ -21,6 +24,8 @@
|
||||
#include "nlohmann/json_fwd.hpp" // for json
|
||||
#endif
|
||||
|
||||
#include "protobuf/login5.pb.h" // for LoginRequest
|
||||
|
||||
using namespace cspot;
|
||||
|
||||
static std::string CLIENT_ID =
|
||||
@@ -31,9 +36,7 @@ static std::string SCOPES =
|
||||
"recently-played"; // Required access scopes
|
||||
|
||||
AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx)
|
||||
: ctx(ctx) {
|
||||
this->updateSemaphore = std::make_shared<bell::WrappedSemaphore>();
|
||||
}
|
||||
: ctx(ctx) {}
|
||||
|
||||
bool AccessKeyFetcher::isExpired() {
|
||||
if (accessKey.empty()) {
|
||||
@@ -65,40 +68,72 @@ void AccessKeyFetcher::updateAccessKey() {
|
||||
|
||||
keyPending = true;
|
||||
|
||||
CSPOT_LOG(info, "Access token expired, fetching new one...");
|
||||
// Prepare a protobuf login request
|
||||
static LoginRequest loginRequest = LoginRequest_init_zero;
|
||||
static LoginResponse loginResponse = LoginResponse_init_zero;
|
||||
|
||||
std::string url =
|
||||
string_format("hm://keymaster/token/authenticated?client_id=%s&scope=%s",
|
||||
CLIENT_ID.c_str(), SCOPES.c_str());
|
||||
auto timeProvider = this->ctx->timeProvider;
|
||||
// Assign necessary request fields
|
||||
loginRequest.client_info.client_id.funcs.encode = &bell::nanopb::encodeString;
|
||||
loginRequest.client_info.client_id.arg = &CLIENT_ID;
|
||||
|
||||
ctx->session->execute(
|
||||
MercurySession::RequestType::GET, url,
|
||||
[this, timeProvider](MercurySession::Response& res) {
|
||||
if (res.fail)
|
||||
return;
|
||||
auto accessJSON =
|
||||
std::string((char*)res.parts[0].data(), res.parts[0].size());
|
||||
#ifdef BELL_ONLY_CJSON
|
||||
cJSON* jsonBody = cJSON_Parse(accessJSON.c_str());
|
||||
this->accessKey =
|
||||
cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring;
|
||||
int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint;
|
||||
cJSON_Delete(jsonBody);
|
||||
#else
|
||||
auto jsonBody = nlohmann::json::parse(accessJSON);
|
||||
this->accessKey = jsonBody["accessToken"];
|
||||
int expiresIn = jsonBody["expiresIn"];
|
||||
#endif
|
||||
expiresIn = expiresIn / 2; // Refresh token before it expires
|
||||
loginRequest.client_info.device_id.funcs.encode = &bell::nanopb::encodeString;
|
||||
loginRequest.client_info.device_id.arg = &ctx->config.deviceId;
|
||||
|
||||
this->expiresAt =
|
||||
timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
||||
updateSemaphore->give();
|
||||
});
|
||||
loginRequest.login_method.stored_credential.username.funcs.encode =
|
||||
&bell::nanopb::encodeString;
|
||||
loginRequest.login_method.stored_credential.username.arg =
|
||||
&ctx->config.username;
|
||||
|
||||
updateSemaphore->twait(5000);
|
||||
// Set login method to stored credential
|
||||
loginRequest.which_login_method = LoginRequest_stored_credential_tag;
|
||||
loginRequest.login_method.stored_credential.data.funcs.encode =
|
||||
&bell::nanopb::encodeVector;
|
||||
loginRequest.login_method.stored_credential.data.arg = &ctx->config.authData;
|
||||
|
||||
// Max retry of 3, can receive different hash cat types
|
||||
int retryCount = 3;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
auto encodedRequest = pbEncode(LoginRequest_fields, &loginRequest);
|
||||
CSPOT_LOG(info, "Access token expired, fetching new one... %d",
|
||||
encodedRequest.size());
|
||||
|
||||
// Perform a login5 request, containing the encoded protobuf data
|
||||
auto response = bell::HTTPClient::post(
|
||||
"https://login5.spotify.com/v3/login",
|
||||
{{"Content-Type", "application/x-protobuf"}}, encodedRequest);
|
||||
|
||||
auto responseBytes = response->bytes();
|
||||
|
||||
// Deserialize the response
|
||||
pbDecode(loginResponse, LoginResponse_fields, responseBytes);
|
||||
|
||||
if (loginResponse.which_response == LoginResponse_ok_tag) {
|
||||
// Successfully received an auth token
|
||||
CSPOT_LOG(info, "Access token sucessfully fetched");
|
||||
success = true;
|
||||
|
||||
accessKey = std::string(loginResponse.response.ok.access_token);
|
||||
|
||||
// Expire in ~30 minutes
|
||||
int expiresIn = 3600 / 2;
|
||||
|
||||
if (loginResponse.response.ok.has_access_token_expires_in) {
|
||||
int expiresIn = loginResponse.response.ok.access_token_expires_in / 2;
|
||||
}
|
||||
|
||||
this->expiresAt =
|
||||
ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
||||
} else {
|
||||
CSPOT_LOG(error, "Failed to fetch access token");
|
||||
}
|
||||
|
||||
// Free up allocated memory for response
|
||||
pb_release(LoginResponse_fields, &loginResponse);
|
||||
|
||||
retryCount--;
|
||||
} while (retryCount >= 0 && !success);
|
||||
|
||||
// Mark as not pending for refresh
|
||||
keyPending = false;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ static long vorbisTellCb(TrackPlayer* self) {
|
||||
TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx,
|
||||
std::shared_ptr<cspot::TrackQueue> trackQueue,
|
||||
EOFCallback eof, TrackLoadedCallback trackLoaded)
|
||||
: bell::Task("cspot_player", 32 * 1024, 5, 1) {
|
||||
: bell::Task("cspot_player", 48 * 1024, 5, 1) {
|
||||
this->ctx = ctx;
|
||||
this->eofCallback = eof;
|
||||
this->trackLoaded = trackLoaded;
|
||||
|
||||
Reference in New Issue
Block a user