AccessKeyFetcher.cpp 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. #include "AccessKeyFetcher.h"
  2. #include <cstring> // for strrchr
  3. #include <initializer_list> // for initializer_list
  4. #include <map> // for operator!=, operator==
  5. #include <type_traits> // for remove_extent_t
  6. #include <vector> // for vector
  7. #include "BellLogger.h" // for AbstractLogger
  8. #include "CSpotContext.h" // for Context
  9. #include "HTTPClient.h"
  10. #include "Logger.h" // for CSPOT_LOG
  11. #include "MercurySession.h" // for MercurySession, MercurySession::Res...
  12. #include "NanoPBExtensions.h" // for bell::nanopb::encode...
  13. #include "NanoPBHelper.h" // for pbEncode and pbDecode
  14. #include "Packet.h" // for cspot
  15. #include "TimeProvider.h" // for TimeProvider
  16. #include "Utils.h" // for string_format
  17. #ifdef BELL_ONLY_CJSON
  18. #include "cJSON.h"
  19. #else
  20. #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
  21. #include "nlohmann/json_fwd.hpp" // for json
  22. #endif
  23. #include "protobuf/login5.pb.h" // for LoginRequest
  24. using namespace cspot;
  25. static std::string CLIENT_ID =
  26. "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id
  27. static std::string SCOPES =
  28. "streaming,user-library-read,user-library-modify,user-top-read,user-read-"
  29. "recently-played"; // Required access scopes
  30. AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx)
  31. : ctx(ctx) {}
  32. bool AccessKeyFetcher::isExpired() {
  33. if (accessKey.empty()) {
  34. return true;
  35. }
  36. if (ctx->timeProvider->getSyncedTimestamp() > expiresAt) {
  37. return true;
  38. }
  39. return false;
  40. }
  41. std::string AccessKeyFetcher::getAccessKey() {
  42. if (!isExpired()) {
  43. return accessKey;
  44. }
  45. updateAccessKey();
  46. return accessKey;
  47. }
  48. void AccessKeyFetcher::updateAccessKey() {
  49. if (keyPending) {
  50. // Already pending refresh request
  51. return;
  52. }
  53. keyPending = true;
  54. // Prepare a protobuf login request
  55. static LoginRequest loginRequest = LoginRequest_init_zero;
  56. static LoginResponse loginResponse = LoginResponse_init_zero;
  57. // Assign necessary request fields
  58. loginRequest.client_info.client_id.funcs.encode = &bell::nanopb::encodeString;
  59. loginRequest.client_info.client_id.arg = &CLIENT_ID;
  60. loginRequest.client_info.device_id.funcs.encode = &bell::nanopb::encodeString;
  61. loginRequest.client_info.device_id.arg = &ctx->config.deviceId;
  62. loginRequest.login_method.stored_credential.username.funcs.encode =
  63. &bell::nanopb::encodeString;
  64. loginRequest.login_method.stored_credential.username.arg =
  65. &ctx->config.username;
  66. // Set login method to stored credential
  67. loginRequest.which_login_method = LoginRequest_stored_credential_tag;
  68. loginRequest.login_method.stored_credential.data.funcs.encode =
  69. &bell::nanopb::encodeVector;
  70. loginRequest.login_method.stored_credential.data.arg = &ctx->config.authData;
  71. // Max retry of 3, can receive different hash cat types
  72. int retryCount = 3;
  73. bool success = false;
  74. do {
  75. auto encodedRequest = pbEncode(LoginRequest_fields, &loginRequest);
  76. CSPOT_LOG(info, "Access token expired, fetching new one... %d",
  77. encodedRequest.size());
  78. // Perform a login5 request, containing the encoded protobuf data
  79. auto response = bell::HTTPClient::post(
  80. "https://login5.spotify.com/v3/login",
  81. {{"Content-Type", "application/x-protobuf"}}, encodedRequest);
  82. auto responseBytes = response->bytes();
  83. // Deserialize the response
  84. pbDecode(loginResponse, LoginResponse_fields, responseBytes);
  85. if (loginResponse.which_response == LoginResponse_ok_tag) {
  86. // Successfully received an auth token
  87. CSPOT_LOG(info, "Access token sucessfully fetched");
  88. success = true;
  89. accessKey = std::string(loginResponse.response.ok.access_token);
  90. // Expire in ~30 minutes
  91. int expiresIn = 3600 / 2;
  92. if (loginResponse.response.ok.has_access_token_expires_in) {
  93. int expiresIn = loginResponse.response.ok.access_token_expires_in / 2;
  94. }
  95. this->expiresAt =
  96. ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
  97. } else {
  98. CSPOT_LOG(error, "Failed to fetch access token");
  99. }
  100. // Free up allocated memory for response
  101. pb_release(LoginResponse_fields, &loginResponse);
  102. retryCount--;
  103. } while (retryCount >= 0 && !success);
  104. keyPending = false;
  105. }