| 
					
				 | 
			
			
				@@ -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; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loginRequest.login_method.stored_credential.username.funcs.encode = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      &bell::nanopb::encodeString; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  loginRequest.login_method.stored_credential.username.arg = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      &ctx->config.username; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  // 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"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        this->expiresAt = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            timeProvider->getSyncedTimestamp() + (expiresIn * 1000); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        updateSemaphore->give(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // Free up allocated memory for response 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pb_release(LoginResponse_fields, &loginResponse); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  updateSemaphore->twait(5000); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    retryCount--; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } while (retryCount >= 0 && !success); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  // Mark as not pending for refresh 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   keyPending = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |