diff options
| author | Aotrix <[email protected]> | 2025-11-17 13:30:23 +0100 |
|---|---|---|
| committer | Aotrix <[email protected]> | 2025-11-17 13:30:23 +0100 |
| commit | 9dab564d3aaabda88efda5e3289392d0be716084 (patch) | |
| tree | 4d2d0188119ba33f29c0dc6c362c26a47a9e637d /submodules/battleNetApi/src | |
| parent | f78e3e1428f59fb96cb02cf6fbaad692a582f9d5 (diff) | |
changed subprojects to submodules
Diffstat (limited to 'submodules/battleNetApi/src')
| -rw-r--r-- | submodules/battleNetApi/src/apiUrl.cpp | 40 | ||||
| -rw-r--r-- | submodules/battleNetApi/src/hearthstoneApi.cpp | 40 | ||||
| -rw-r--r-- | submodules/battleNetApi/src/token.cpp | 105 |
3 files changed, 185 insertions, 0 deletions
diff --git a/submodules/battleNetApi/src/apiUrl.cpp b/submodules/battleNetApi/src/apiUrl.cpp new file mode 100644 index 0000000..3de13cd --- /dev/null +++ b/submodules/battleNetApi/src/apiUrl.cpp @@ -0,0 +1,40 @@ +#include "apiUrl.hpp" +#include <format> + +ApiUrl::ApiUrl(const std::string &iContentType, const std::string &iRegion) + : mContentType{iContentType}, mRegion{iRegion} {} +void ApiUrl::setRegion(const std::string &iRegion) { + if (not iRegion.empty()) { + mRegion = iRegion; //!< The region of the data to receive, e.g. "eu" + } +} +void ApiUrl::setLocale(const std::string &iLocale) { + if (not iLocale.empty()) { + aParameters["locale"] = iLocale; //!< The locale to reflect in localized data, e.g. "fr_FR" or "ko_KR" + //!< If you do not supply a value, all translations are returned. + } +} +void ApiUrl::setGameMode(const std::string &iGameMode) { + if (not iGameMode.empty()) { + aParameters["gameMode"] = + iGameMode; //!< A recognized game mode, e.g. "constructed" + //!< @see https://community.developer.battle.net/documentation/hearthstone/guides/game-modes + } +} +void ApiUrl::setPageSize(const std::string &iPageSize) { + if (not iPageSize.empty()) { + aParameters["pageSize"] = iPageSize; //!< The number of results to choose per page, e.g. "40" + } +} +std::string ApiUrl::operator()() const { + std::string aFormattedParameters; + for (const auto &[key, value] : aParameters) { + aFormattedParameters += std::format("&{}={}", key, value); + } + if (not aFormattedParameters.empty()) { + aFormattedParameters[0] = '?'; + } + aFormattedParameters = + std::format("https://{}.api.blizzard.com/hearthstone/{}/{}", mRegion, mContentType, aFormattedParameters); + return aFormattedParameters; +} diff --git a/submodules/battleNetApi/src/hearthstoneApi.cpp b/submodules/battleNetApi/src/hearthstoneApi.cpp new file mode 100644 index 0000000..c34e6ad --- /dev/null +++ b/submodules/battleNetApi/src/hearthstoneApi.cpp @@ -0,0 +1,40 @@ +#include "hearthstoneApi.hpp" +#include <curl/curl.h> +#include <format> + +HearthstoneApi::HearthstoneApi(Token &&iToken) : mToken(iToken), mAuthorizationHeader(authorizationHeader()) {} + +std::uint64_t HearthstoneApi::numberOfCards(const std::string &iApiResponse) const { + const std::string aCardCountRegex{"\"cardCount\":(\\d+)"}; + return valueInJson<std::uint64_t>(iApiResponse, aCardCountRegex); +} +std::string HearthstoneApi::authorizationHeader() { return std::format("Authorization: Bearer {}", mToken.toString()); } + +size_t HearthstoneApi::curlCallbackStringOutput(void *iContent, size_t iSize, size_t iNmemb, std::string *iS) { + const size_t kNewLength = iSize * iNmemb; + try { + iS->append(static_cast<char *>(iContent), kNewLength); + } catch (std::bad_alloc &e) { + // handle memory problem + return 0; + } + return kNewLength; +} + +std::string HearthstoneApi::makeApiRequest(const std::string &iUrl) const { + CURL *curl = curl_easy_init(); + std::string request_output; + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, iUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallbackStringOutput); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request_output); + struct curl_slist *chunk = nullptr; + chunk = curl_slist_append(chunk, mAuthorizationHeader.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + curl_easy_perform(curl); + curl_easy_cleanup(curl); + } + return request_output; +} diff --git a/submodules/battleNetApi/src/token.cpp b/submodules/battleNetApi/src/token.cpp new file mode 100644 index 0000000..89f0d2a --- /dev/null +++ b/submodules/battleNetApi/src/token.cpp @@ -0,0 +1,105 @@ +#include "token.hpp" +#include <charconv> +#include <chrono> +#include <curl/curl.h> +#include <format> +#include <fstream> +#include <iostream> +#include <regex> +#include <system_error> + +Token::Token(const std::string &iTokenFilePath, const std::string &iUsername, const std::string &iPassword) + : mFilePath{iTokenFilePath}, mUsername{iUsername}, mPassword{iPassword}, mToken{getValidToken()} {} + +std::string Token::toString() const { return mToken; } + +std::string Token::getValidToken() const { + refreshToken(); + std::ifstream aTokenFile{mFilePath}; + std::string aLine; + std::getline(aTokenFile, aLine); + const std::string kToken{aLine.substr(0, aLine.find(" "))}; + return kToken; +} + +void Token::refreshToken() const { + std::ifstream aTokenFile{mFilePath}; + std::string aLine; + std::getline(aTokenFile, aLine); + if (aLine.empty()) { + writeNewTokenToFile(); + } else { + const std::string kTimeLimitText{aLine.substr(aLine.find(" ") + 1)}; + long int aTimeLimit; + const auto kConversionStatus{ + std::from_chars(kTimeLimitText.data(), kTimeLimitText.data() + kTimeLimitText.size(), aTimeLimit)}; + if (kConversionStatus.ec == std::errc{}) { + const bool kTokenExpired{ + std::chrono::duration_cast<std::chrono::seconds>(std::chrono::utc_clock::now().time_since_epoch()) + .count() >= aTimeLimit}; + if (kTokenExpired) { + writeNewTokenToFile(); + } + } + } +} + +void Token::writeNewTokenToFile() const { + const std::string kToken{getValidTokenWithApi()}; + if (not kToken.empty()) { + writeTokenToFile(kToken); + } +} + +void Token::writeTokenToFile(const std::string &iToken) const { + std::ofstream aTokenFile{mFilePath}; + std::chrono::seconds aExpiresAt{getCurrentTime() + std::chrono::hours(24)}; + aTokenFile << iToken << " " << aExpiresAt.count() << '\n'; +} + +std::string Token::getValidTokenWithApi() const { + CURL *aCurl{curl_easy_init()}; + std::string aTokenInfo; + if (aCurl) { + 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("{}:{}", mUsername, mPassword).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); + } + } + + return extractTokenWithRegex(aTokenInfo, "\"access_token\":\"(\\w+)\""); +} + +std::chrono::seconds Token::getCurrentTime() const { + return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::utc_clock::now().time_since_epoch()); +} + +std::string Token::extractTokenWithRegex(const std::string &iText, const std::string &iRegex) const { + const std::regex kRegex{iRegex}; + std::smatch aMatches; + if (std::regex_search(iText, aMatches, kRegex)) { + return aMatches[1].str(); + } + std::cerr << std::format("Unable to retrieve token with regex '{}'", iRegex); + return ""; +} + +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<char *>(iPtr), newLength); + } catch (std::bad_alloc &e) { + // handle memory problem + return 0; + } + return newLength; +} |
