From a0632d299b85dc4ef7c0e03898f38e6deca0edc2 Mon Sep 17 00:00:00 2001 From: Aotrix <9aotrix@protonmail.com> Date: Tue, 28 Apr 2026 03:49:41 +0200 Subject: add HearthstoneTokenBuilder class --- battleNetApi/src/hearthstoneTokenBuilder.cpp | 140 +++++++++++++++++++++++++++ battleNetApi/src/main.cpp | 32 +++--- battleNetApi/src/token.cpp | 121 +---------------------- battleNetApi/src/tokenBuilder.cpp | 13 --- battleNetApi/src/tokenPool.cpp | 20 ---- 5 files changed, 162 insertions(+), 164 deletions(-) create mode 100644 battleNetApi/src/hearthstoneTokenBuilder.cpp delete mode 100644 battleNetApi/src/tokenPool.cpp (limited to 'battleNetApi/src') diff --git a/battleNetApi/src/hearthstoneTokenBuilder.cpp b/battleNetApi/src/hearthstoneTokenBuilder.cpp new file mode 100644 index 0000000..8403f46 --- /dev/null +++ b/battleNetApi/src/hearthstoneTokenBuilder.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include + +HearthstoneTokenBuilder::HearthstoneTokenBuilder(const std::string &iJsonPath) { + mJsonPaths.push_back(iJsonPath); +} +HearthstoneTokenBuilder::HearthstoneTokenBuilder( + const std::span &iJsonPaths) { + mJsonPaths.append_range(iJsonPaths); +} +HearthstoneTokenBuilder & +HearthstoneTokenBuilder::addJsonPath(const std::string &iJsonPath) { + mJsonPaths.push_back(iJsonPath); + return *this; +} +HearthstoneTokenBuilder & +HearthstoneTokenBuilder::addJsonPaths(const std::string &iJsonPath) { + mJsonPaths.push_back(iJsonPath); + return *this; +} +HearthstoneTokenBuilder &HearthstoneTokenBuilder::buildToken() { + if (hasExpired()) { + const std::pair kTokenContent{ + getTokenWithApi()}; + writeTokenToLocalFile(kTokenContent); + } + // return getTokenFromLocalFile(); + if (aTokenContainer.empty()) { + return std::nullopt; + } + Token aOutputToken{aTokenContainer.front()}; + aTokenContainer.pop(); + // mCurrentToken = std::make_unique(...); + return *this; +} +std::shared_ptr HearthstoneTokenBuilder::getToken() { + return mCurrentToken; +} +HearthstoneTokenBuilder &HearthstoneTokenBuilder::allowUpdateSourceFile() { + mUpdateSourceFile = true; + return *this; +} +bool HearthstoneTokenBuilder::isUpdateSourceFileAllowed() const { + return mUpdateSourceFile; +} + +const bool HearthstoneTokenBuilder::hasExpired(json iCredentials) const { + if (not iCredentials.contains("Token") or + not iCredentials["Token"].contains("ExpirationTime") or + not iCredentials["Token"].contains("Content")) { + return true; + } + if (iCredentials["Token"]["Content"].dump().length() < + kTOKEN_CONTENT_MIN_LENGTH) { + return true; + } + const std::uint64_t kExpirationTime{ + iCredentials["Token"]["ExpirationTime"]}; + const std::uint64_t kCurrentTime{static_cast( + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count())}; + return kCurrentTime >= kExpirationTime; +} + +size_t HearthstoneTokenBuilder::curlCallbackStringOutput( + void *iPtr, size_t iSize, size_t iNmemb, std::string *iUserData) { + size_t newLength{iSize * iNmemb}; + try { + iUserData->append(static_cast(iPtr), newLength); + } catch (std::bad_alloc &e) { + // handle memory problem + return 0; + } + return newLength; +} + +std::chrono::seconds HearthstoneTokenBuilder::getCurrentTime() const { + return std::chrono::duration_cast( + std::chrono::utc_clock::now().time_since_epoch()); +} + +std::pair +HearthstoneTokenBuilder::getTokenWithApi() const { + std::ifstream aCredentialsFile{mFilePath}; + json aCredentials = json::parse(aCredentialsFile); + if (not aCredentials.contains("Agent") or + not aCredentials["Agent"].contains("Username") or + not aCredentials["Agent"].contains("Password")) { + std::cerr << "Please define Agent/Username and Agent/Password in the " + "JSON credentials file\n"; + std::exit(EXIT_FAILURE); + } + const std::string kAgentUsername{aCredentials["Agent"]["Username"]}; + const std::string kAgentPassword{aCredentials["Agent"]["Password"]}; + CURL *aCurl{curl_easy_init()}; + std::string aTokenInfo; + if (aCurl and not kAgentUsername.empty() and not kAgentPassword.empty()) { + curl_easy_setopt(aCurl, CURLOPT_URL, "https://oauth.battle.net/token"); + curl_easy_setopt(aCurl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(aCurl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(aCurl, CURLOPT_WRITEFUNCTION, + curlCallbackStringOutput); + curl_easy_setopt(aCurl, CURLOPT_WRITEDATA, &aTokenInfo); + curl_easy_setopt( + aCurl, CURLOPT_USERPWD, + std::format("{}:{}", kAgentUsername, kAgentPassword).data()); + curl_easy_setopt(aCurl, CURLOPT_POSTFIELDS, + "grant_type=client_credentials"); + CURLcode result_code{curl_easy_perform(aCurl)}; + curl_easy_cleanup(aCurl); + if (result_code != CURLE_OK) { + std::cerr << "ERROR: token request failed" << std::endl; + std::exit(EXIT_FAILURE); + } + } + json aTokenJson = json::parse(aTokenInfo); + return std::make_pair(aTokenJson["access_token"], aTokenJson["expires_in"]); +} + +std::string HearthstoneTokenBuilder::getTokenFromLocalFile() const { + std::ifstream aTokenFile{mFilePath}; + return json::parse(aTokenFile)["Token"]["Content"]; +} + +void HearthstoneTokenBuilder::writeTokenToLocalFile( + const std::pair &iToken) const { + std::ifstream aTokenFileInput{mFilePath}; + json aCredentialsFile = json::parse(aTokenFileInput); + aCredentialsFile["Token"]["Content"] = iToken.first; + aCredentialsFile["Token"]["ExpirationTime"] = + std::chrono::duration_cast( + getCurrentTime() + std::chrono::seconds(iToken.second)) + .count(); + + std::ofstream aTokenFileOutput{mFilePath}; + aTokenFileOutput << std::setw(4) << aCredentialsFile; +} diff --git a/battleNetApi/src/main.cpp b/battleNetApi/src/main.cpp index ccab84c..dd93daa 100644 --- a/battleNetApi/src/main.cpp +++ b/battleNetApi/src/main.cpp @@ -1,12 +1,14 @@ #include "apiUrl.hpp" #include #include +#include #include -#include +#include +#include class DownloadMethod { public: - virtual json download() = 0; + virtual json download(const std::shared_ptr &iToken) = 0; virtual ~DownloadMethod() = default; }; @@ -14,16 +16,15 @@ class DefaultDownloadMethod : public DownloadMethod { public: DefaultDownloadMethod() = default; ~DefaultDownloadMethod() = default; - json download() override { + json download(const std::shared_ptr &iToken) override { const std::string kContentType{"cards"}; const std::string kRegion{"eu"}; ApiUrl aApiUrl(kContentType, kRegion); aApiUrl.setLocale("fr_FR"); aApiUrl.setGameMode("constructed"); - aApiUrl.setPageSize("100"); - std::optional aToken{TokenPool::availableToken()}; - if (aToken.has_value()) { - return HearthstoneApi::downloadCards(aApiUrl, aToken.value()); + aApiUrl.setPageSize(100); + if (iToken) { + return HearthstoneApi::downloadCards(aApiUrl, *iToken); } return json({}); }; @@ -37,9 +38,9 @@ class DownloaderContext { void setDownloadMethod(std::unique_ptr iDownloader) { mDownloader = std::move(iDownloader); } - json download() { + json download(const std::shared_ptr &iToken) { if (mDownloader) { - return mDownloader->download(); + return mDownloader->download(iToken); } else { std::cerr << "No download method available in DownloaderContext\n"; return json({}); @@ -52,20 +53,25 @@ class DownloaderContext { int main(int argc, char *argv[]) { DownloaderContext aDownloaderContext; + std::string aCredentialsFilePath; std::string aOutputJsonPath; if (argc < 3) { - TokenPool::feedTokenSource("tests/resources/credentials.json"); + aCredentialsFilePath = "tests/resources/credentials.json"; aDownloaderContext.setDownloadMethod( std::make_unique()); aOutputJsonPath = "tests/output/list_of_cards.json"; } else { - std::string aCredentialsFilePath{argv[1]}; - TokenPool::feedTokenSource(aCredentialsFilePath); + aCredentialsFilePath = argv[1]; aDownloaderContext.setDownloadMethod( std::make_unique()); aOutputJsonPath = argv[2]; } + HearthstoneTokenBuilder aHearthstoneTokenBuilder(aCredentialsFilePath); std::ofstream aOutputJsonFile(aOutputJsonPath); - aOutputJsonFile << aDownloaderContext.download().dump(4) << '\n'; + aOutputJsonFile << aDownloaderContext + .download( + aHearthstoneTokenBuilder.buildToken().getToken()) + .dump(4) + << '\n'; return 0; } diff --git a/battleNetApi/src/token.cpp b/battleNetApi/src/token.cpp index 1f952ff..b312cc1 100644 --- a/battleNetApi/src/token.cpp +++ b/battleNetApi/src/token.cpp @@ -1,122 +1,7 @@ #include "token.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -using json = nlohmann::json; +Token::Token(const std::string &iTokenText) : mTokenText{iTokenText} {} -Token::Token(const std::string &iTokenFilePath) - : mFilePath{iTokenFilePath}, mToken{generateToken()} {} +std::string Token::text() const { return mTokenText; } -std::string Token::text() const { return mToken; } - -const bool Token::hasExpired() const { - std::ifstream aTokenFile{mFilePath}; - json aCredentialsFile = json::parse(aTokenFile); - if (not aCredentialsFile.contains("Token") or - not aCredentialsFile["Token"].contains("ExpirationTime") or - not aCredentialsFile["Token"].contains("Content")) { - return true; - } - if (aCredentialsFile["Token"]["Content"].dump().length() < - kTokenContentMinLength) { - return true; - } - const std::uint64_t kExpirationTime{ - aCredentialsFile["Token"]["ExpirationTime"]}; - const std::uint64_t kCurrentTime{static_cast( - std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count())}; - if (kCurrentTime >= kExpirationTime) { - return true; - } - return false; -} - -std::string Token::getTokenFromLocalFile() const { - std::ifstream aTokenFile{mFilePath}; - return json::parse(aTokenFile)["Token"]["Content"]; -} - -std::string Token::generateToken() { - if (hasExpired()) { - const std::pair kTokenContent{ - getTokenWithApi()}; - writeTokenToLocalFile(kTokenContent); - } - return getTokenFromLocalFile(); -} - -void Token::writeTokenToLocalFile( - const std::pair &iToken) const { - std::ifstream aTokenFileInput{mFilePath}; - json aCredentialsFile = json::parse(aTokenFileInput); - aCredentialsFile["Token"]["Content"] = iToken.first; - aCredentialsFile["Token"]["ExpirationTime"] = - std::chrono::duration_cast( - getCurrentTime() + std::chrono::seconds(iToken.second)) - .count(); - - std::ofstream aTokenFileOutput{mFilePath}; - aTokenFileOutput << std::setw(4) << aCredentialsFile; -} - -std::pair Token::getTokenWithApi() const { - std::ifstream aCredentialsFile{mFilePath}; - json aCredentials = json::parse(aCredentialsFile); - if (not aCredentials.contains("Agent") or - not aCredentials["Agent"].contains("Username") or - not aCredentials["Agent"].contains("Password")) { - std::cerr << "Please define Agent/Username and Agent/Password in the " - "JSON credentials file\n"; - std::exit(EXIT_FAILURE); - } - const std::string kAgentUsername{aCredentials["Agent"]["Username"]}; - const std::string kAgentPassword{aCredentials["Agent"]["Password"]}; - CURL *aCurl{curl_easy_init()}; - std::string aTokenInfo; - if (aCurl and not kAgentUsername.empty() and not kAgentPassword.empty()) { - curl_easy_setopt(aCurl, CURLOPT_URL, "https://oauth.battle.net/token"); - curl_easy_setopt(aCurl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(aCurl, CURLOPT_SSL_VERIFYHOST, 0L); - curl_easy_setopt(aCurl, CURLOPT_WRITEFUNCTION, - curlCallbackStringOutput); - curl_easy_setopt(aCurl, CURLOPT_WRITEDATA, &aTokenInfo); - curl_easy_setopt( - aCurl, CURLOPT_USERPWD, - std::format("{}:{}", kAgentUsername, kAgentPassword).data()); - curl_easy_setopt(aCurl, CURLOPT_POSTFIELDS, - "grant_type=client_credentials"); - CURLcode result_code{curl_easy_perform(aCurl)}; - curl_easy_cleanup(aCurl); - if (result_code != CURLE_OK) { - std::cerr << "ERROR: token request failed" << std::endl; - std::exit(EXIT_FAILURE); - } - } - json aTokenJson = json::parse(aTokenInfo); - return std::make_pair(aTokenJson["access_token"], aTokenJson["expires_in"]); -} - -std::chrono::seconds Token::getCurrentTime() const { - return std::chrono::duration_cast( - std::chrono::utc_clock::now().time_since_epoch()); -} - -size_t Token::curlCallbackStringOutput(void *iPtr, size_t iSize, size_t iNmemb, - std::string *iUserData) { - size_t newLength{iSize * iNmemb}; - try { - iUserData->append(static_cast(iPtr), newLength); - } catch (std::bad_alloc &e) { - // handle memory problem - return 0; - } - return newLength; -} +void Token::setText(const std::string &iTokenText) { mTokenText = iTokenText; } diff --git a/battleNetApi/src/tokenBuilder.cpp b/battleNetApi/src/tokenBuilder.cpp index 733f5c1..9e2a2f0 100644 --- a/battleNetApi/src/tokenBuilder.cpp +++ b/battleNetApi/src/tokenBuilder.cpp @@ -1,14 +1 @@ #include - -TokenBuilder::TokenBuilder() { - static_assert( - false, "'TokenBuilder::TokenBuilder()' implementation is not complete"); -} -Token TokenBuilder::build() const { - static_assert(false, - "'TokenBuilder::build()' implementation is not complete"); -} -TokenBuilder &TokenBuilder::allowSourceFileUpdate() { - mAllowSourceFileUpdate = true; - return *this; -} diff --git a/battleNetApi/src/tokenPool.cpp b/battleNetApi/src/tokenPool.cpp deleted file mode 100644 index b74aae5..0000000 --- a/battleNetApi/src/tokenPool.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include - -std::optional TokenPool::availableToken() { - if (aTokenContainer.empty()) { - return std::nullopt; - } - Token aOutputToken{aTokenContainer.front()}; - aTokenContainer.pop(); - return aOutputToken; -} - -void TokenPool::feedTokenSource(const std::string &iJsonPath) { - aTokenContainer.emplace(iJsonPath); -} - -void TokenPool::feedTokenSource(const std::span &iJsonPaths) { - for (const std::string &iJsonPath : iJsonPaths) { - feedTokenSource(iJsonPath); - } -} -- cgit v1.2.3