summaryrefslogtreecommitdiff
path: root/battleNetApi/src
diff options
context:
space:
mode:
Diffstat (limited to 'battleNetApi/src')
-rw-r--r--battleNetApi/src/apiUrl.cpp40
-rw-r--r--battleNetApi/src/hearthstoneApi.cpp40
-rw-r--r--battleNetApi/src/token.cpp105
3 files changed, 185 insertions, 0 deletions
diff --git a/battleNetApi/src/apiUrl.cpp b/battleNetApi/src/apiUrl.cpp
new file mode 100644
index 0000000..3de13cd
--- /dev/null
+++ b/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/battleNetApi/src/hearthstoneApi.cpp b/battleNetApi/src/hearthstoneApi.cpp
new file mode 100644
index 0000000..c34e6ad
--- /dev/null
+++ b/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/battleNetApi/src/token.cpp b/battleNetApi/src/token.cpp
new file mode 100644
index 0000000..89f0d2a
--- /dev/null
+++ b/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;
+}