├── .gitmodules ├── docs ├── Implementation │ ├── logging.md │ ├── handlers.md │ └── endpoints.md ├── Contributing │ └── contributing.md ├── Endpoints │ ├── Teamfight_Tactics │ │ ├── Status.md │ │ ├── Match.md │ │ ├── Summoner.md │ │ └── League.md │ ├── League_of_Legends │ │ ├── Champion.md │ │ ├── Spectator.md │ │ ├── Status.md │ │ ├── Clash.md │ │ ├── Match.md │ │ ├── Champion_Mastery.md │ │ ├── Summoner.md │ │ ├── Challenges.md │ │ └── League.md │ ├── Account.md │ ├── Leagends_of_Runeterra.md │ └── Valorant.md ├── Getting Started │ ├── usage.md │ └── installation.md └── index.md ├── .gitattributes ├── .gitignore ├── dir-structure.txt ├── .github └── workflows │ ├── ci.yaml │ └── build.yml ├── src └── riot-cpp │ ├── CMakeLists.txt │ ├── handling │ ├── rate_hierachy.h │ ├── region_count.h │ ├── rate_handler.h │ ├── handlers.h │ ├── region_count.cpp │ ├── handlers.cpp │ ├── rate_hierachy.cpp │ ├── scope_count.h │ └── rate_handler.cpp │ ├── logging │ ├── logger.h │ └── logger.cpp │ ├── client │ ├── client.h │ └── client.cpp │ ├── types │ ├── args.h │ └── region_enums.cpp │ └── query │ ├── endpoints.h │ └── url.h ├── LICENSE ├── mkdocs.yml ├── CMakeLists.txt ├── README.md └── test ├── Rate_Tests.cpp ├── type_tests.cpp ├── url_tests.cpp └── client_tests.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/Implementation/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | *to complete* 4 | -------------------------------------------------------------------------------- /docs/Implementation/handlers.md: -------------------------------------------------------------------------------- 1 | # Handlers 2 | 3 | *to complete* 4 | -------------------------------------------------------------------------------- /docs/Contributing/contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | *to complete* 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ./test/simdjson.cpp linguist-generated=true 2 | ./test/simdjson.h linguist-generated=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | build 3 | Debug/ 4 | docs/ 5 | site/ 6 | Release/ 7 | .cache/ 8 | .vs/ 9 | test/log_file.txt 10 | -------------------------------------------------------------------------------- /docs/Endpoints/Teamfight_Tactics/Status.md: -------------------------------------------------------------------------------- 1 | ### TFT-STATUS-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#tft-status-v1) documentation of TFT-STATUS-V1 4 | 5 | **v1** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Tft_Status.v1(""); 9 | // declaration 10 | std::unique_ptr> v1(std::string); 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Champion.md: -------------------------------------------------------------------------------- 1 | ### CHAMPION-V3 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#champion-v3) documentation of CHAMPION-V3 endpoint 4 | 5 | **rotations** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Champion.rotations(""); 9 | // declaration 10 | std::unique_ptr> rotations(std::string); 11 | ``` 12 | -------------------------------------------------------------------------------- /dir-structure.txt: -------------------------------------------------------------------------------- 1 | . 2 | ├── CMakeLists.txt 3 | ├── client 4 | │   ├── client.cpp 5 | │   └── client.h 6 | ├── handling 7 | │   ├── handlers.cpp 8 | │   ├── handlers.h 9 | │   └── structures 10 | │   ├── rate_structures.h 11 | │   └── request_history.cpp 12 | ├── logging 13 | │   ├── logger.cpp 14 | │   └── logger.h 15 | ├── query 16 | │   ├── query.cpp 17 | │   └── query.h 18 | └── version 19 | 20 | 5 directories, 12 files 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - uses: actions/cache@v2 18 | with: 19 | key: ${{ github.ref }} 20 | path: .cache 21 | - run: pip install mkdocs 22 | - run: pip install mkdocs-gitbook 23 | - run: mkdocs gh-deploy --force 24 | -------------------------------------------------------------------------------- /src/riot-cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CXX_STANDARD_REQUIRED TRUE) 3 | 4 | find_package(CURL REQUIRED) 5 | 6 | add_library(riot-cpp STATIC client/client.cpp 7 | handling/rate_hierachy.cpp 8 | handling/handlers.cpp 9 | handling/rate_handler.cpp 10 | handling/region_count.cpp 11 | logging/logger.cpp 12 | types/region_enums.cpp) 13 | 14 | target_include_directories(riot-cpp PRIVATE ${CURL_INCLUDE_DIRS}) 15 | 16 | target_link_libraries(riot-cpp ${CURL_LIBRARIES}) 17 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Spectator.md: -------------------------------------------------------------------------------- 1 | ### SPECTATOR-V4 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#spectator-v4) documentation of SPECTATOR-V4 4 | 5 | **by-summoner-id** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Spectator.by_summoner_id("", ""); 9 | // declaration 10 | std::unique_ptr> by_summoner_id(std::string, std::string); 11 | ``` 12 | **featured-games** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.Spectator.featured_games(""); 16 | // declaration 17 | std::unique_ptr> featured_games(std::string); 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Status.md: -------------------------------------------------------------------------------- 1 | ### LOL-STATUS 2 | 3 | View Riot's documentation of LOL-STATUS below, 4 | 5 | [**v3**](https://developer.riotgames.com/apis#lol-status-v3) 6 | 7 | *Warning: this endpoint is depricated.* 8 | 9 | ```cpp 10 | // function call 11 | std::unique_ptr> response = client_obj.Lol_Status.v3(""); 12 | // declaration 13 | std::unique_ptr> v3(std::string); 14 | ``` 15 | 16 | [**v4**](https://developer.riotgames.com/apis#lol-status-v4) 17 | ```cpp 18 | // function call 19 | std::unique_ptr> response = client_obj.Lol_Status.v4(""); 20 | // declaration 21 | std::unique_ptr> v4(std::string); 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/Endpoints/Teamfight_Tactics/Match.md: -------------------------------------------------------------------------------- 1 | ### TFT-MATCH-V5 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#tft-match-v1) documentation of TFT-MATCH-V5 4 | 5 | 6 | **by-puuid** 7 | ```cpp 8 | // function call 9 | std::unique_ptr> response = client_obj.Tft_Match.by_puuid("", "", {"", }, ...); 10 | // declaration 11 | std::unique_ptr> by_puuid(std::string, std::string, std::pair, ...); 12 | ``` 13 | **by-match** 14 | ```cpp 15 | // function call 16 | std::unique_ptr> response = client_obj.Tft_Match.by_match("", ""); 17 | // declaration 18 | std::unique_ptr> by_match(std::string, std::string); 19 | ``` 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install cURL 17 | run: sudo apt install libcurl4-openssl-dev 18 | - name: Clone Catch2 19 | run: git clone https://github.com/catchorg/Catch2.git 20 | - name: Build Catch2 21 | run: cd Catch2 && cmake -Bbuild -H. -DBUILD_TESTING=OFF 22 | - name: Install Catch2 23 | run: cd Catch2 && sudo cmake --build build/ --target install 24 | - name: Create build directory 25 | run: mkdir build 26 | - name: Configure Cmake 27 | run: cd build && cmake .. 28 | - name: Compilation Check 29 | run: cd build && make 30 | -------------------------------------------------------------------------------- /docs/Implementation/endpoints.md: -------------------------------------------------------------------------------- 1 | # Endpoints 2 | Riot-cpp implements all offical riot api endpoints not including the following 3 | * LOR-DECK-V1 4 | * LOR-INVECTORY-V1 5 | * TOURNAMENT-V4 6 | * TOURNAMENT-STUB-V4 7 | 8 | The following code assumes the client has already been initialised as follows 9 | ```cpp 10 | // #include "path/to/client.h" 11 | 12 | client::RiotApiClient client_obj{"", // std::string 13 | "", // std::string 14 | logging::LEVEL::, // logging::LEVEL 15 | verbosity // bool 16 | } 17 | ``` 18 | 19 | ### ACCOUNT-V1 20 | 21 | #### Usage 22 | 23 | ```cpp 24 | // by-puuid 25 | Json::Value result = client_obj.Account.by_puuid("", "puuid"); 26 | ``` 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/Endpoints/Account.md: -------------------------------------------------------------------------------- 1 | ### ACCOUNT-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#account-v1) documentation of ACCOUNT-V1 4 | 5 | **by-puuid** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Account.by_puuid("", ""); 9 | // declaration 10 | std::unique_ptr> 11 | ``` 12 | **by-riot-id** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.Account.by_riot_id("", "", ""); 16 | // declaration 17 | std::unique_ptr> by_riot_id(std::string, std::string, std::string); 18 | ``` 19 | **by-game** 20 | ```cpp 21 | // function call 22 | std::unique_ptr> response = client_obj.Account.by_game("", "", ""); 23 | // declaration 24 | std::unique_ptr> by_game(std::string, std::string, std::string); 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/Getting Started/usage.md: -------------------------------------------------------------------------------- 1 | ### Basic Usage 2 | 3 | Below demonstrates a request to Match V5 endpoint using PUUID with optional arguements. All endpoints return a Json::Value object. 4 | 5 | ```Cpp 6 | #include 7 | #include "path/to/client.h" 8 | 9 | int main() { 10 | 11 | client::RiotApiClient example_client("", "", logging::LEVEL::, ); 12 | Json::Value response; 13 | 14 | response = example_client.Match.by_puuid("routing", "puuid", {"startTime", }, {"endTime", }, ...); 15 | } 16 | ``` 17 | *Note: All std::pair arguments are optional* 18 | 19 | ### Including your API Key 20 | 21 | It is highly recommended not to included api keys in source code as one may unintentionally share source coded publically with the api key exposed. 22 | 23 | Riot-cpp accepts a path to a json file to extract your api key. The file should be of the following format. 24 | 25 | ```Json 26 | { 27 | "api-key" : "" 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/rate_hierachy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "scope_count.h" 6 | 7 | #ifdef DEBUG_MODE 8 | #define rcp_assert(x, msg) if (!x) {std::cerr << "ASSERTION FAILED: " << msg << std::endl;} 9 | #else 10 | #define rcp_assert(x, msg) 11 | #endif 12 | namespace riotcpp { 13 | namespace rate { 14 | 15 | class RateHierachy { 16 | 17 | private: 18 | 19 | std::vector hierachy_; 20 | 21 | public: 22 | 23 | RateHierachy(const int durations[], const int limits[], const int counts[], const unsigned size); 24 | RateHierachy(const std::vector& durations, const std::vector& limits, const std::vector counts); 25 | // TODO: There shouldn't be a string constructor as the arguments may be invalid 26 | RateHierachy(const std::string& description); 27 | 28 | int get_wait_time(); 29 | void insert_request(unsigned server_time); 30 | 31 | std::string to_string() const; 32 | }; 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Tan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Getting Started/installation.md: -------------------------------------------------------------------------------- 1 | ### Installation 2 | 3 | Currently the repository must be cloned or downloaded via the releases. 4 | 5 | ```git 6 | git clone git+https://github.com/Dan-Tan/riot-cpp.git 7 | ``` 8 | Option 1: CMake 9 | 10 | Linking to executable or library 11 | 12 | ```cmake 13 | add_subdirectory() 14 | target_link_libraries( riot-cpp) 15 | ``` 16 | 17 | Option 2: Manually with libriot-cpp. 18 | 19 | The shared object file is located as $riot-cpp/build/src/libriot-cpp.so$ and can be manually linked with your compiler of choice. You must include the following in your includes 20 | 21 | ```cpp 22 | #include "path/to/riot-cpp/src/client/client.h" 23 | ``` 24 | 25 | See options for linking with gcc, [Options for Linking](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html) 26 | 27 | ### Dependencies 28 | 29 | The following libaries are reqired for usage: 30 | 31 | - C++ 20 32 | - [Jsoncpp](https://curl.se/libcurl/) 33 | - [libcurl](https://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html) 34 | 35 | *To do: add version requirement information* 36 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Clash.md: -------------------------------------------------------------------------------- 1 | ### CLASH-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#clash-v1) documentation of CLASH-V1. 4 | 5 | **by-summoner-id** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Clash.by_summoner_id("", "") 9 | // declaration 10 | std::unique_ptr> by_summoner_id(std::string, std::string); 11 | ``` 12 | **by-team** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.Clash.by_team("", ""); 16 | // declaration 17 | std::unique_ptr> by_team(std::string, std::string); 18 | ``` 19 | **tournament-by-team** 20 | ```cpp 21 | // function call 22 | std::unique_ptr> response = client_obj.Clash.tournament_by_team("", "") 23 | // declaration 24 | std::unique_ptr> tournament_by_team(std::string, std::string) 25 | ``` 26 | **by-tournament** 27 | ```cpp 28 | // function call 29 | std::unique_ptr> response = client_obj.Clash.by_tournament("", "") 30 | // declaration 31 | std::unique_ptr> by_tournament(std::string, std::string); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/Endpoints/Teamfight_Tactics/Summoner.md: -------------------------------------------------------------------------------- 1 | ### TFT-SUMMONER-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#tft-summoner-v1) documentation of TFT-SUMMONER-V1 4 | 5 | 6 | **by-account** 7 | ```cpp 8 | // function call 9 | std::unique_ptr> response = client_obj.Tft_Summoner.by_account("", ""); 10 | // declaration 11 | std::unique_ptr> by_account(std::string, std::string); 12 | ``` 13 | **by-name** 14 | ```cpp 15 | // function call 16 | std::unique_ptr> response = client_obj.Tft_Summoner.by_name("", ""); 17 | // declaration 18 | std::unique_ptr> by_name(std::string, std::string); 19 | ``` 20 | **by-puuid** 21 | ```cpp 22 | // function call 23 | std::unique_ptr> response = client_obj.Tft_Summoner.by_puuid("", ""); 24 | // declaration 25 | std::unique_ptr> by_puuid(std::string, std::string); 26 | ``` 27 | **by-summoner-id** 28 | ```cpp 29 | // function call 30 | std::unique_ptr> response = client_obj.Tft_Summoner.by_summoner_id("", ""); 31 | // declaration 32 | std::unique_ptr> by_summoner_id(std::string, std::string); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Match.md: -------------------------------------------------------------------------------- 1 | ### MATCH-V5 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#match-v5) documentation. 4 | 5 | **by-puuid** 6 | 7 | Querying MATCH-V5 using puuid accepts a large number of optional arguments. This method allows for all optional arguments, however, the string argument *type* must be passed first. Currently, *type* must be used if other optional fields are being used (I will overload to solve this soon). 8 | ```cpp 9 | // function call 10 | std::unique_ptr> response = client_obj.Match.by_puuid("", "", {"optional_key", }, ...); 11 | // declaration 12 | std::unique_ptr> by_puuid(std::string, std::string, std::pair, ...); 13 | ``` 14 | **by-match-id** 15 | ```cpp 16 | // function call 17 | std::unique_ptr> response = client_obj.Match.by_match_id("", ""); 18 | // declaration 19 | std::unique_ptr> by_match_id(std::string, std::string); 20 | ``` 21 | **timeline** 22 | ```cpp 23 | // function call 24 | std::unique_ptr> response = client_obj.Match.timeline("", ""); 25 | // declaration 26 | std::unique_ptr> timeline(std::string, std::string); 27 | ``` 28 | -------------------------------------------------------------------------------- /src/riot-cpp/logging/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../query/endpoints.h" 7 | #include "../handling/scope_count.h" 8 | #include "../handling/region_count.h" 9 | 10 | namespace riotcpp { 11 | namespace logging { 12 | 13 | enum LEVEL : int { 14 | DEBUG, 15 | INFO, 16 | WARNING, 17 | ERRORS, 18 | CRITICAL 19 | }; 20 | 21 | class Logger { 22 | private: 23 | std::string _log_path; 24 | LEVEL _log_level; 25 | 26 | std::ofstream _log_file; 27 | bool _incoming = false; 28 | bool _verbose; 29 | bool _log_q_time; 30 | 31 | public: 32 | Logger(std::string log_path, LEVEL log_level = LEVEL::INFO, bool verbose = false, bool log_q_time = false); 33 | ~Logger(); 34 | 35 | Logger& operator<<(const LEVEL& log_level); 36 | Logger& operator<<(const std::string& message); 37 | Logger& operator<<(const char* message); 38 | Logger& operator<<(const int err_code); 39 | Logger& operator<<(const rate::RegionCount& region_count); 40 | Logger& operator<<(const query::RiotHeader& response); 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/region_count.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "rate_hierachy.h" 8 | namespace riotcpp { 9 | namespace rate { 10 | 11 | class RegionCount { 12 | 13 | private: 14 | RateHierachy app_limits_ = {{0}, {0}, {0}}; 15 | std::unordered_map method_limits_ {}; 16 | 17 | public: 18 | RegionCount() = default; 19 | ~RegionCount() = default; 20 | 21 | /** 22 | * Initialise Application Limits, simple wrapper of rate hierachy limits 23 | */ 24 | void init_limits(const std::vector& durations, const std::vector& limits, const std::vector& counts); 25 | /** 26 | * Initialise Application Limits, simple wrapper of rate hierachy limits using string 27 | */ 28 | void init_limits(const std::string& description); 29 | int get_wait_time(const std::string& method_key); 30 | void insert_request(unsigned server_time, 31 | const std::string& method_key, 32 | const std::string& method_limits); 33 | 34 | std::string to_string() const; 35 | 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/Endpoints/Leagends_of_Runeterra.md: -------------------------------------------------------------------------------- 1 | ### LOR-MATCH-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#lor-match-v1) documentation of LOR-MATCH-V1 4 | 5 | **by-puuid** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Lor_Match.by_puuid("", ""); 9 | // declaration 10 | std::unique_ptr> by_puuid(std::string, std::string); 11 | ``` 12 | **by-match** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.Lor_Match.by_match("", "> by_match(std::string, std::string); 18 | ``` 19 | ### LOR-RANKED-V1 20 | 21 | View [Riot's](https://developer.riotgames.com/apis#lor-ranked-v1) documentation of LOR-RANKED-V1 22 | 23 | **leaderboards** 24 | ```cpp 25 | // function call 26 | std::unique_ptr> response = client_obj.Lor_Ranked.leaderboards(""); 27 | // declaration 28 | std::unique_ptr> leaderboards(std::string); 29 | ``` 30 | 31 | ### LOR-STATUS 32 | 33 | View [Riot's](https://developer.riotgames.com/apis#lor-status-v1) documentation of LOR-STATUS-V1 34 | 35 | **v1** 36 | ```cpp 37 | // function call 38 | std::unique_ptr> response = client_obj.Lor_Status.v1(""); 39 | // declaration 40 | std::unique_ptr> v1(std::string); 41 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Champion_Mastery.md: -------------------------------------------------------------------------------- 1 | ### CHAMPION-MASTERY-V4 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#champion-mastery-v4) documentation CHAMPION-MASTERY-V4. 4 | 5 | **by-summoner-id** 6 | ```cpp 7 | // functional call 8 | std::unique_ptr> response = client_obj.Champion_Mastery.by_summoner_id("", ""); 9 | // declaration 10 | std::unique_ptr> by_summoner_id(std::string, std::string); 11 | ``` 12 | **by-summoner-by-champion** 13 | ```cpp 14 | // functional call 15 | std::unique_ptr> response = client_obj.Champion_Mastery.by_summoner_by_champion("", "", ); 16 | // declaration 17 | std::unique_ptr> by_summoner_by_champion(std::string, std::string, int); 18 | ``` 19 | **by-summoner-top** 20 | ```cpp 21 | // functional call 22 | std::unique_ptr> response = client_obj.Champion_Mastery.by_summoner_top("", "", {"count", }); 23 | // declaration 24 | std::unique_ptr> by_summoner_top(std::string, std::string, std::pair, int); 25 | ``` 26 | **scores-by-summoner** 27 | ```cpp 28 | // functional call 29 | std::unique_ptr> response = client_obj.Champion_Mastery.scores_by_summoner("", "") 30 | // declaration 31 | std::unique_ptr> scores_by_summoner(std::string, std::string); 32 | ``` 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Summoner.md: -------------------------------------------------------------------------------- 1 | ### SUMMONER-V4 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#summoner-v4) documentation of SUMMONER-V4. 4 | 5 | 6 | **by-account-id** 7 | ```cpp 8 | // function call 9 | std::unique_ptr> response = client_obj.Summoner.by_account_id("", "") 10 | // declaration 11 | std::unique_ptr> by_account_id(std::string, std::string); 12 | ``` 13 | **by-name** 14 | ```cpp 15 | // function call 16 | std::unique_ptr> response = client_obj.Summoner.by_name("", ""); 17 | // declaration 18 | std::unique_ptr> by_name(std::string, std::string); 19 | ``` 20 | **by-puuid** 21 | ```cpp 22 | // function call 23 | std::unique_ptr> response = client_obj.Summoner.by_puuid("", ""); 24 | // declaration 25 | std::unique_ptr> by_puuid(std::string, std::string); 26 | ``` 27 | **by-summoner-id** 28 | ```cpp 29 | // function call 30 | std::unique_ptr> response = client_obj.Summoner.by_summoner_id("", ""); 31 | // declaration 32 | std::unique_ptr> by_summoner_id(std::string, std::string); 33 | ``` 34 | **by-rso-puuid** 35 | ```cpp 36 | // function call 37 | std::unique_ptr> response = client_obj.Summoner.by_rso_puuid("", ""); 38 | // declaration 39 | std::unique_ptr> 40 | ``` 41 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/rate_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "region_count.h" 7 | #include "../query/endpoints.h" 8 | #include "../types/args.h" 9 | 10 | namespace riotcpp { 11 | namespace rate { 12 | 13 | /** 14 | * All request sent by this client need to be approved by the rate limiter. 15 | * Rate Counts are instantiated after the first request 16 | */ 17 | class RateHandler { 18 | private: 19 | std::array platform_counts; 20 | std::array region_counts; 21 | std::array val_plaform_counts; 22 | 23 | bool initialised = false; 24 | 25 | bool initialise_counts(const query::RiotHeader&); 26 | 27 | public: 28 | RateHandler() = default; 29 | ~RateHandler() = default; 30 | 31 | /** 32 | * @param (out) request if a wait is required the send_time field will be update the earliest time the request can be sent 33 | * @return true if request can be sent immediately, false the client should wait at least until send_time to send 34 | */ 35 | bool check_rate_limits(std::shared_ptr); 36 | 37 | /** 38 | * update the rate counts with a request. Counts are only incremented upon successful request 39 | * @param request that was sent 40 | */ 41 | void insert_request(std::shared_ptr); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Riot-cpp 2 | nav: 3 | - Home: index.md 4 | - Getting Started: 5 | - Installation: 'Getting Started/installation.md' 6 | - Basic Usage: 'Getting Started/usage.md' 7 | - Endpoints: 8 | - Account: 'Endpoints/Account.md' 9 | - Legends of Runeterra: 'Endpoints/Leagends_of_Runeterra.md' 10 | - Valorant: 'Endpoints/Valorant.md' 11 | - League of Legends: 12 | - Challenges: 'Endpoints/League_of_Legends/Challenges.md' 13 | - Champion: 'Endpoints/League_of_Legends/Champion.md' 14 | - Champion Mastery: 'Endpoints/League_of_Legends/Champion_Mastery.md' 15 | - Clash: 'Endpoints/League_of_Legends/Clash.md' 16 | - League: 'Endpoints/League_of_Legends/League.md' 17 | - Match: 'Endpoints/League_of_Legends/Match.md' 18 | - Spectator: 'Endpoints/League_of_Legends/Spectator.md' 19 | - Status: 'Endpoints/League_of_Legends/Status.md' 20 | - Summoner: 'Endpoints/League_of_Legends/Summoner.md' 21 | - Teamfight Tactics: 22 | - League: 'Endpoints/Teamfight_Tactics/League.md' 23 | - Match: 'Endpoints/Teamfight_Tactics/Match.md' 24 | - Status: 'Endpoints/Teamfight_Tactics/Status.md' 25 | - Summoner: 'Endpoints/Teamfight_Tactics/Summoner.md' 26 | - Implementation: 27 | - Endpoint Structure: 'Implementation/endpoints.md' 28 | - Handlers: 'Implementation/handlers.md' 29 | - Logging: 'Implementation/logging.md' 30 | - Contributing: 31 | - How to Contribute: 'Contributing/contributing.md' 32 | - Authors: 'Contributing/contributing.md' 33 | 34 | plugins: [] 35 | use_directory_urls: false 36 | 37 | theme: 38 | name: gitbook 39 | highlightjs: true 40 | hljs_style: Tokyo Night Light 41 | hljs_languages: 42 | - c++ 43 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/Challenges.md: -------------------------------------------------------------------------------- 1 | ### LOL-CHALLENGES-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#lol-challenges-v1) documentation of LOL-CHALLENGES-V1. 4 | 5 | **config** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Lol_Challenges.config(""); 9 | // declaration 10 | std::unique_ptr> config(std::string); 11 | ``` 12 | **percentiles** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.Lol_Challenges.percentiles(""); 16 | // declaration 17 | std::unique_ptr> percentiles(std::string); 18 | ``` 19 | **challenge-config** 20 | ```cpp 21 | // function call 22 | std::unique_ptr> response = client_obj.Lol_Challenges.challenge_config("", ); 23 | // declaration 24 | std::unique_ptr> config(std::string, int); 25 | ``` 26 | **challenge-leaderboard** 27 | ```cpp 28 | // function call 29 | std::unique_ptr> response = client_obj.Lol_Challenges.challenge_leaderboard("", , "", {"limit", }); 30 | // declaration 31 | std::unique_ptr> challenge_leaderboard(std::string, int, std::string, std::pair); 32 | ``` 33 | **challenge-percentiles** 34 | ```cpp 35 | std::unique_ptr> client_obj.Lol_Challenges.challenge_percentiles("", ) 36 | // declaration 37 | std::unique_ptr> challenge_percentiles(std::string, int); 38 | ``` 39 | **by-puuid** 40 | ```cpp 41 | // function call 42 | std::unique_ptr> response = client_obj.Lol_Challenges.by_puuid("", ""); 43 | // declaration 44 | std::unique_ptr> config(std::string, std::string); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/Endpoints/League_of_Legends/League.md: -------------------------------------------------------------------------------- 1 | ### LEAGUE-V4 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#league-v4) documentation of LEAGUE-V4. 4 | 5 | **challenger** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.League.challenger("", ""); 9 | // declaration 10 | std::unique_ptr> challenger(std::string, std::string); 11 | ``` 12 | **grandmaster** 13 | ```cpp 14 | // function call 15 | std::unique_ptr> response = client_obj.League.grandmaster("", ""); 16 | // declaration 17 | std::unique_ptr> grandmaster(std::string, std::string); 18 | ``` 19 | **master** 20 | ```cpp 21 | // function call 22 | std::unique_ptr> response = client_obj.League.master("", ""); 23 | // declaration 24 | std::unique_ptr> master(std::string, std::string); 25 | ``` 26 | **by-summoner-id** 27 | ```cpp 28 | // function call 29 | std::unique_ptr> response = client_obj.League.by_summoner_id("", ""); 30 | // declaration 31 | std::unique_ptr> by_summoner_id("", ""); 32 | ``` 33 | **league-id** 34 | ```cpp 35 | // function call 36 | std::unique_ptr> response = client_obj.League.by_league_id("", ""); 37 | // declaration 38 | std::unique_ptr> by_league_id(std::string, std::string) 39 | ``` 40 | **specfic-league** 41 | ```cpp 42 | // function call 43 | std::unique_ptr> response = client_obj.League.specific_league("", "", "", "", {"page", }); 44 | // declaration 45 | std::unique_ptr> specific_league(std::string, std::string, std::string, std::string, std::pair); 46 | ``` 47 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/handlers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "region_count.h" 4 | #include "../query/endpoints.h" 5 | #include "../types/args.h" 6 | #include "../logging/logger.h" 7 | #include "rate_handler.h" 8 | 9 | namespace riotcpp { 10 | namespace rate { 11 | 12 | struct ResponseHandler { 13 | ResponseHandler(logging::Logger *logger) {this->_logger = logger;}; 14 | bool review_request(std::shared_ptr request); 15 | bool validate_request(std::shared_ptr request) {return true;}; 16 | 17 | std::array, NUM_PLATFORMS> platform_errors; 18 | std::array, NUM_REGIONS> region_errors; 19 | std::array, NUM_VAL_PLATFORMS> val_platform_errors; 20 | logging::Logger *_logger; 21 | 22 | int MAX_INTERNAL_DENIALS = 2; // 500 23 | int MAX_SERVICE_UNAVAILABLE = 2; // 503 24 | 25 | private: 26 | bool handle_server_error(const long code, const args::routing); 27 | void reset_server_error_count(const args::routing&); 28 | }; 29 | 30 | 31 | class RequestHandler { 32 | public: 33 | RequestHandler(logging::Logger *logger) : rate_handler(), response_handler(logger) {}; 34 | ~RequestHandler() = default; 35 | 36 | inline bool review_request(std::shared_ptr request) { 37 | this->rate_handler.check_rate_limits(request); // insert_request only 200 38 | return this->response_handler.review_request(request); 39 | }; 40 | inline bool validate_request(std::shared_ptr request) { 41 | this->rate_handler.insert_request(request); 42 | return true; 43 | }; 44 | private: 45 | RateHandler rate_handler; 46 | ResponseHandler response_handler; 47 | }; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to Riot-cpp 2 | 3 | Simple Api client for the Riot Games Resful API implemented in C++ 20. 4 | 5 | This library implements rate limiting to prevent api key blacklisting from exceeding Riot's rate limits. The library handles server response error's and will retry when a successful request is possible (429, 500, 503 errors). The client contains a logging class to help with debugging dependent code as well as providing information of the running of the client. Riot-cpp will adapt to the specific api key's rate limits after the first request and try to recover from errors. 6 | 7 | Riot-cpp uses [libcurl](https://curl.se/libcurl/) to send https get requests and [jsoncpp](https://open-source-parsers.github.io/jsoncpp-docs/doxygen/index.html) to handle json data structure encoding and json file parsing. 8 | 9 | ## Installation 10 | 11 | Currently the repository must be cloned or downloaded via the releases. 12 | 13 | ```git 14 | git clone git+https://github.com/Dan-Tan/riot-cpp.git 15 | ``` 16 | Option 1: CMake 17 | 18 | Linking to executable or library 19 | 20 | ```cmake 21 | add_subdirectory() 22 | target_link_libraries( riot-cpp) 23 | ``` 24 | 25 | Option 2: Manually with libriot-cpp. 26 | 27 | The shared object file is located as $riot-cpp/build/src/libriot-cpp.so$ and can be manually linked with your compiler of choice. You must include the following in your includes 28 | 29 | ```cpp 30 | #include "path/to/riot-cpp/src/client/client.h" 31 | ``` 32 | 33 | See options for linking with gcc, [Options for Linking](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html) 34 | 35 | ### Endpoints 36 | Riot-cpp implements all offical riot api endpoints not including the following: 37 | 38 | * LOR-DECK-V1 39 | * LOR-INVENTORY-V1 40 | * TOURNAMENT-V4 41 | * TOURNAMENT-STUB-V4 42 | 43 | ### Disclaimer 44 | 45 | Riot-cpp is not developed or affiliated with Riot games. 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18.4) 2 | project(RIOTAPICLIENT) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | set(CMAKE_CXX_STANDARD 20) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | IF (DEBUG_MODE) 9 | set(GCC_CXX_FLAGS ${GCC_CXX_FLAGS} "-static-libgcc -static-libstdc++ -static -ggdb3 -O0") 10 | ELSE(DEBUG_MODE) 11 | set(GCC_CXX_FLAGS ${GCC_CXX_FLAGS} "-static-libgcc -static-libstdc++ -static -ggdb3 -O3") 12 | ENDIF(DEBUG_MODE) 13 | 14 | find_package(Catch2 3 REQUIRED) 15 | 16 | IF (URL_TESTS) 17 | add_executable(url_tests test/url_tests.cpp) 18 | target_link_libraries(url_tests Catch2::Catch2WithMain) 19 | ENDIF(URL_TESTS) 20 | 21 | add_subdirectory(src/riot-cpp) 22 | 23 | target_compile_options(riot-cpp PRIVATE -fsanitize=address -static-libasan) 24 | target_link_options(riot-cpp PRIVATE -fsanitize=address -static-libasan) 25 | 26 | add_executable(client_tests test/client_tests.cpp test/simdjson.cpp) 27 | target_link_libraries(client_tests riot-cpp) 28 | target_link_libraries(client_tests Catch2::Catch2WithMain) 29 | 30 | target_compile_options(client_tests PRIVATE -fsanitize=address -static-libasan) 31 | target_link_options(client_tests PRIVATE -fsanitize=address -static-libasan) 32 | 33 | 34 | IF(BUILD_TESTING) 35 | add_executable(rate_tests test/Rate_Tests.cpp) 36 | target_link_libraries(rate_tests riot-cpp) 37 | target_link_libraries(rate_tests Catch2::Catch2WithMain) 38 | 39 | target_compile_options(rate_tests PRIVATE -fsanitize=address -static-libasan) 40 | target_link_options(rate_tests PRIVATE -fsanitize=address -static-libasan) 41 | 42 | add_executable(util_tests test/type_tests.cpp) 43 | target_link_libraries(util_tests riot-cpp) 44 | target_link_libraries(util_tests Catch2::Catch2WithMain) 45 | 46 | target_compile_options(util_tests PRIVATE -fsanitize=address -static-libasan) 47 | target_link_options(util_tests PRIVATE -fsanitize=address -static-libasan) 48 | ENDIF(BUILD_TESTING) 49 | -------------------------------------------------------------------------------- /docs/Endpoints/Teamfight_Tactics/League.md: -------------------------------------------------------------------------------- 1 | ### TFT-LEAGUE-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#tft-league-v1) documentation of TFT-LEAGUE-V1 4 | 5 | 6 | **challenger** 7 | ```cpp 8 | // function call 9 | std::unique_ptr> response = client_obj.Tft_League.challenger(""); 10 | // declaration 11 | std::unique_ptr> challenger(std::string); 12 | ``` 13 | **grandmaster** 14 | ```cpp 15 | // function call 16 | std::unique_ptr> response = client_obj.Tft_League.grandmaster(""); 17 | // declaration 18 | std::unique_ptr> grandmaster(std::string); 19 | ``` 20 | **master** 21 | ```cpp 22 | // function call 23 | std::unique_ptr> response = client_obj.Tft_League.master(""); 24 | // declaration 25 | std::unique_ptr> master(std::string); 26 | ``` 27 | **by-summoner-id** 28 | ```cpp 29 | // function call 30 | std::unique_ptr> response = client_obj.Tft_League.by_summoner_id("", ""); 31 | // declaration 32 | std::unique_ptr> by_summoner_id(std::string, std::string); 33 | ``` 34 | **by-league-id** 35 | ```cpp 36 | // function call 37 | std::unique_ptr> response = client_obj.Tft_League.by_league_id("", ""); 38 | // declaration 39 | std::unique_ptr> by_league_id(std::string, std::string); 40 | ``` 41 | **queue-top** 42 | ```cpp 43 | // function call 44 | std::unique_ptr> response = client_obj.Tft_League.queue_top("", ""); 45 | // declaration 46 | std::unique_ptr> queue_top(std::string, std::string); 47 | ``` 48 | **by-tier-division** 49 | ```cpp 50 | // function call 51 | std::unique_ptr> response = client_obj.Tft_League.by_tier_division("", "", "", {"count", }); 52 | // declaration 53 | std::unique_ptr> by_tier_division(std::string, std::string, std::string, std::pair); 54 | ``` 55 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/region_count.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "region_count.h" 3 | namespace riotcpp { 4 | namespace rate { 5 | 6 | int RegionCount::get_wait_time(const std::string& method_key) { 7 | int app_wait_time = this->app_limits_.get_wait_time(); 8 | 9 | auto method_hierachy = this->method_limits_.find(method_key); 10 | 11 | if (method_hierachy == this->method_limits_.end()) { 12 | return app_wait_time; 13 | } 14 | 15 | int method_wait_time = method_hierachy->second.get_wait_time(); 16 | return std::max(app_wait_time, method_wait_time); 17 | } 18 | 19 | void RegionCount::insert_request(unsigned server_time, const std::string& method_key, const std::string& method_limits) { 20 | this->app_limits_.insert_request(server_time); 21 | 22 | // if exists insert else construct and insert 23 | auto method_hierachy = this->method_limits_.find(method_key); 24 | if (method_hierachy != this->method_limits_.end()) { 25 | method_hierachy->second.insert_request(server_time); 26 | return; 27 | } 28 | 29 | RateHierachy new_hierachy = RateHierachy(method_limits); 30 | new_hierachy.insert_request(server_time); 31 | this->method_limits_.insert_or_assign(method_key, RateHierachy(method_limits)); 32 | } 33 | 34 | std::string RegionCount::to_string() const { 35 | std::stringstream ss; 36 | ss << "Application Limit\n " << this->app_limits_.to_string() << "Method Limits\n"; 37 | for (auto& key_pair : this->method_limits_) { 38 | ss << key_pair.first; 39 | ss << key_pair.second.to_string(); 40 | } 41 | return ss.str(); 42 | } 43 | 44 | void RegionCount::init_limits(const std::vector& durations, const std::vector& limits, const std::vector& counts) { 45 | this->app_limits_ = RateHierachy(durations, limits, counts); 46 | } 47 | 48 | void RegionCount::init_limits(const std::string& description) { 49 | this->app_limits_ = RateHierachy(description); 50 | } 51 | 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/Endpoints/Valorant.md: -------------------------------------------------------------------------------- 1 | ### VAL-CONTENT-V1 2 | 3 | View [Riot's](https://developer.riotgames.com/apis#val-content-v1) documentation of VAL-CONTENT-V1 4 | 5 | **content** 6 | ```cpp 7 | // function call 8 | std::unique_ptr> response = client_obj.Val_Content.content("", {"locale", ""}); 9 | // declaration 10 | std::unique_ptr> content(std::string, std::pair); 11 | ``` 12 | 13 | ### VAL-MATCH-V1 14 | 15 | 16 | View [Riot's](https://developer.riotgames.com/apis#val-match-v1) documentation of VAL-MATCH-V1 17 | 18 | 19 | **by-match** 20 | ```cpp 21 | // function call 22 | std::unique_ptr> response = client_obj.Val_Match.by_match("", ""); 23 | // declaration 24 | std::unique_ptr> by_match(std::string, std::string); 25 | ``` 26 | **by-puuid** 27 | ```cpp 28 | // function call 29 | std::unique_ptr> response = client_obj.Val_Match.by_puuid("", ""); 30 | // declaration 31 | std::unique_ptr> by_puuid(std::string, std::string); 32 | ``` 33 | **by-queue** 34 | ```cpp 35 | // function call 36 | std::unique_ptr> response = client_obj.Val_Match.by_queue("", ""); 37 | // declaration 38 | std::unique_ptr> by_queue(std::string, std::string); 39 | ``` 40 | 41 | ### VAL-RANKED-V1 42 | 43 | View [Riot's](https://developer.riotgames.com/apis#val-ranked-v1) documentation of VAL-RANKED-V1 44 | 45 | **by-act** 46 | ```cpp 47 | // function call 48 | std::unique_ptr> response = client_obj.Val_Ranked.by_act("", "", {"locale", ""}); 49 | // declaration 50 | std::unique_ptr> by_act(std::string, std::string, std::pair); 51 | ``` 52 | 53 | ### VAL-STATUS-V1 54 | 55 | View [Riot's](https://developer.riotgames.com/apis#val-status-v1) documentation of VAL-STATUS-V1 56 | 57 | **platform_data** 58 | ```cpp 59 | // function call 60 | std::unique_ptr> response = client_obj.Val_Status.platform_data(""); 61 | // declaration 62 | std::unique_ptr> platform_data(std::string); 63 | ``` 64 | -------------------------------------------------------------------------------- /src/riot-cpp/client/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "../query/endpoints.h" 10 | #include "../handling/handlers.h" 11 | #include "../logging/logger.h" 12 | #include 13 | namespace riotcpp { 14 | namespace client { 15 | 16 | using json_text = std::vector; 17 | 18 | using opt_args = std::pair; 19 | 20 | class RiotApiClient { 21 | public: 22 | RiotApiClient(std::string path_to_config, std::string path_to_log, logging::LEVEL report_level = logging::LEVEL::INFO, bool verbose_logging = false); 23 | ~RiotApiClient(); 24 | 25 | const query::Account_v1 Account; 26 | const query::Champion_Mastery_v4 Champion_Mastery; 27 | const query::Champion_v3 Champion; 28 | const query::Clash_v1 Clash; 29 | const query::League_exp_v4 League_exp; 30 | const query::League_v4 League; 31 | const query::Lol_Challenges_v1 Lol_Challenges; 32 | const query::Lol_Status_v4 Lol_Status; 33 | const query::Lor_Match_v1 Lor_Match; 34 | const query::Lor_Ranked_v1 Lor_Ranked; 35 | const query::Lor_Status_v1 Lor_Status; 36 | const query::Match_v5 Match; 37 | const query::Spectator_Tft_v5 Spectator_Tft; 38 | const query::Spectator_v5 Spectator; 39 | const query::Summoner_v4 Summoner; 40 | const query::Tft_League_v1 Tft_League; 41 | const query::Tft_Match_v1 Tft_Match; 42 | const query::Tft_Status_v1 Tft_Status; 43 | const query::Tft_Summoner_v1 Tft_Summoner; 44 | const query::Val_Content_v1 Val_Content; 45 | const query::Val_Match_v1 Val_Match; 46 | const query::Val_Ranked_v1 Val_Ranked; 47 | const query::Val_Status_v1 Val_Status; 48 | 49 | 50 | protected: 51 | 52 | rate::RequestHandler request_handler; 53 | logging::Logger logger; 54 | 55 | private: 56 | std::unique_ptr query(std::shared_ptr request); 57 | std::function(std::shared_ptr)> endpoint_call; 58 | bool get(std::shared_ptr request); 59 | 60 | 61 | CURL* easy_handle = nullptr; 62 | struct curl_slist *header = nullptr; 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/handlers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "handlers.h" 10 | 11 | namespace riotcpp { 12 | namespace rate { 13 | 14 | /** 15 | * Update server error tracker and indicate whether or not a request should be resent. 16 | * Return false to not resent and true to resend. 17 | */ 18 | bool ResponseHandler::handle_server_error(const long response_code, const args::routing route) { 19 | std::size_t ind = response_code == 500 ? 0 : 1; 20 | int limit = response_code == 500 ? this->MAX_INTERNAL_DENIALS : this->MAX_SERVICE_UNAVAILABLE; 21 | switch (route.indicator) { 22 | case REGIONAL_INDICATOR: 23 | this->region_errors[static_cast(route.routng.reg)][ind] += 1; 24 | return !(this->region_errors[static_cast(route.routng.reg)][ind] >= limit); 25 | case PLATFORM_INDICATOR: 26 | this->platform_errors[static_cast(route.routng.pltform)][ind] += 1; 27 | return !(this->platform_errors[static_cast(route.routng.pltform)][ind] >= limit); 28 | case VAL_PLATFORM_INDICATOR: 29 | this->val_platform_errors[static_cast(route.routng.vpltform)][ind] += 1; 30 | return !(this->val_platform_errors[static_cast(route.routng.vpltform)][ind] >= limit); 31 | default: 32 | throw std::invalid_argument("Invalid route indicator: " + std::to_string(route.indicator)); 33 | } 34 | return false; 35 | } 36 | 37 | void ResponseHandler::reset_server_error_count(const args::routing& route) { 38 | 39 | switch (route.indicator) { 40 | case REGIONAL_INDICATOR: 41 | this->region_errors[static_cast(route.routng.reg)] ={0, 0}; 42 | break; 43 | case PLATFORM_INDICATOR: 44 | this->platform_errors[static_cast(route.routng.pltform)] ={0, 0}; 45 | break; 46 | case VAL_PLATFORM_INDICATOR: 47 | this->val_platform_errors[static_cast(route.routng.vpltform)] ={0, 0}; 48 | break; 49 | default: 50 | throw std::invalid_argument("Invalid route indicator: " + std::to_string(route.indicator)); 51 | } 52 | } 53 | 54 | bool ResponseHandler::review_request(std::shared_ptr request) { 55 | long response_code = request->last_response; 56 | if (response_code == 200 || response_code == -2 || response_code == 429) { 57 | this->reset_server_error_count(request->route); 58 | return true; 59 | } else if (response_code == 500) { 60 | return this->handle_server_error(response_code, request->route); 61 | } else { 62 | return false; 63 | } 64 | return true; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/rate_hierachy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "rate_hierachy.h" 6 | 7 | namespace riotcpp { 8 | namespace rate { 9 | 10 | static inline int chars_to_int(char nums[10], int num_digits) { 11 | int to_return = 0; 12 | for (int i = 0; i < num_digits; i++) { 13 | to_return += ((int) (nums[i] - '0')) * (10^(num_digits - i - 1)); 14 | } 15 | return to_return; 16 | } 17 | 18 | static std::pair, std::vector> extract_duration_limits(const std::string& description) { 19 | // this function assumes valid format of descriptor "20:1, 100:120"... 20 | int num_digits = 0; 21 | char nums[10]; 22 | bool limit_add = true; 23 | std::vector limit; 24 | std::vector duration; 25 | 26 | for (const char& pos_digit : description) { 27 | if (std::isdigit(pos_digit)) { 28 | nums[num_digits] = pos_digit; 29 | } else { 30 | if (limit_add) { 31 | limit.push_back(chars_to_int(nums, num_digits)); 32 | } else { 33 | duration.push_back(chars_to_int(nums, num_digits)); 34 | } 35 | limit_add = !limit_add; 36 | num_digits = 0; 37 | } 38 | } 39 | 40 | return {duration, limit}; 41 | } 42 | 43 | RateHierachy::RateHierachy(const int durations[], const int limits[], const int counts[], const unsigned size) { 44 | this->hierachy_.reserve(size); 45 | for (int i = 0; i < size; i++) { 46 | this->hierachy_.push_back(ScopeCount(durations[i], limits[i], counts[i])); 47 | } 48 | } 49 | 50 | RateHierachy::RateHierachy(const std::vector& durations, const std::vector& limits, const std::vector counts) { 51 | rcp_assert(durations.size() == limits.size(), "Mismatched Duration and Limit sized given"); 52 | rcp_assert(counts.size() == limits.size(), "Mismatched Count and Limit sized given"); 53 | this->hierachy_.reserve(durations.size()); 54 | for (int i = 0; i < durations.size(); i++) { 55 | this->hierachy_.push_back(ScopeCount(durations[i], limits[i], counts[i])); 56 | } 57 | } 58 | 59 | RateHierachy::RateHierachy(const std::string& description) { 60 | 61 | std::pair, std::vector> description_limit = extract_duration_limits(description); 62 | 63 | int size = description_limit.first.size(); 64 | this->hierachy_.reserve(size); 65 | 66 | for (int i = 0; i < size; i++) { 67 | this->hierachy_.push_back(ScopeCount(description_limit.first[i], description_limit.second[i], 0)); 68 | } 69 | } 70 | 71 | int RateHierachy::get_wait_time() { 72 | rcp_assert(this->hierachy_.size() > 0, "Empty scope hierachy, check if initialised properly"); 73 | const auto find_longest = [](int current_max, ScopeCount& next_scope){ 74 | int next_wait = next_scope.get_wait_time(); 75 | return next_wait * (next_wait > current_max) + current_max * (next_wait <= current_max); 76 | }; 77 | return std::accumulate(this->hierachy_.begin(), this->hierachy_.end(), 0, find_longest); 78 | } 79 | 80 | void RateHierachy::insert_request(unsigned server_time) { 81 | for (ScopeCount& scope_count : this->hierachy_) { 82 | scope_count.insert_request(server_time); 83 | } 84 | } 85 | 86 | std::string RateHierachy::to_string() const { 87 | std::stringstream ss; 88 | for (auto& scope_count : this->hierachy_) { 89 | ss << scope_count.to_string() << '\n'; 90 | } 91 | return ss.str(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Riot-cpp 2 | **The documentation is wildly out of date. See endpoints.h or client_tests.cpp to view available endpoints.** 3 | 4 | 5 | [![ci](https://github.com/Dan-Tan/riot-cpp/workflows/ci/badge.svg)](https://github.com/Dan-Tan/riot-cpp/actions?query=workflow:"ci") 6 | [![build](https://github.com/Dan-Tan/riot-cpp/workflows/build/badge.svg)](https://github.com/Dan-Tan/riot-cpp/actions?query=workflow:"build") 7 | 8 | Simple Api client for the Riot Games Resful API implemented in C++ 20. 9 | 10 | This library implements rate limiting to prevent api key blacklisting from exceeding Riot's rate limits. The library handles server response error's and will retry when a successful request is possible (429, 500, 503 errors). The client contains a logging class to help with debugging dependent code as well as providing information of the running of the client. Riot-cpp will adapt to the specific api key's rate limits after the first request and try to recover from errors. 11 | 12 | Riot-cpp uses [libcurl](https://curl.se/libcurl/) to send https get requests. The Json parser [simdjson](https://github.com/simdjson/simdjson) is used as a dependency for testing but NOT in the library. Parsing is done by the user. 13 | 14 | Documentation is located at following page. 15 | 16 | [![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://dan-tan.github.io/riot-cpp/) 17 | 18 | # Basic Usage 19 | 20 | Below demonstrates a request to Match V5 endpoint using PUUID with optional arguements. All endpoints return a unique pointer to a vector of chars. Dependency on Jsoncpp or an individual Json library was remoed to allow the user to decide how they wanted to parse json, allows flexibility for users if performance is a key concern for large responses. 21 | 22 | ```Cpp 23 | #include "path/to/client.h" 24 | 25 | using json_ptr = std::unique_ptr> 26 | 27 | int main() { 28 | 29 | client::RiotApiClient example_client("", "", logging::LEVEL::, ); 30 | 31 | json_ptr response = example_client.Match.by_puuid("routing", "puuid", {"startTime", }, {"endTime", }, ...); 32 | } 33 | ``` 34 | 35 | ## Including your API Key 36 | 37 | It is highly recommended not to included api keys in source code as one may unintentionally share source coded publically with the api key exposed. 38 | 39 | Riot-cpp accepts a path to a json file to extract your api key. The file should be of the following format. 40 | 41 | ```Json 42 | { 43 | "api-key" : "" 44 | } 45 | ``` 46 | Techincally, as long as "RGAPI-..." is anywhere in the file the client will find it. 47 | 48 | ## Installation 49 | 50 | Currently the repository must be cloned or downloaded via the releases. 51 | 52 | ```bash 53 | git clone git+https://github.com/Dan-Tan/riot-cpp.git 54 | ``` 55 | 56 | Git submodules are highly recommended for use as a project dependency. 57 | 58 | ```bash 59 | git submodule add git+https://github.com/Dan-Tan/riot-cpp.git 60 | ``` 61 | ### Option 1: CMake 62 | 63 | Linking to executable or library 64 | 65 | ```cmake 66 | add_subdirectory() 67 | target_include_directories( ) 68 | target_link_libraries( riot-cpp) 69 | ``` 70 | 71 | ### Option 2: Manually with libriot-cpp. 72 | 73 | The shared object file is located as "riot-cpp/build/src/riot-cpp/libriot-cpp.so" and can be manually linked with your compiler of choice. You must include the following in your includes 74 | 75 | ```cpp 76 | #include "path/to/riot-cpp/src/client/client.h" 77 | ``` 78 | 79 | See options for linking with gcc, [Options for Linking](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html) 80 | 81 | ## Disclaimer 82 | 83 | Riot-cpp is not developed by or affiliated with Riot Games. 84 | -------------------------------------------------------------------------------- /src/riot-cpp/types/args.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef DEBUG_MODE 8 | #define rcp_assert(x, msg) if (!x) {std::cerr << "ASSERTION FAILED: " << msg << std::endl;} 9 | #else 10 | #define rcp_assert(x, msg) 11 | #endif 12 | 13 | #define NUM_CHAMPIONS 166 14 | 15 | #define NUM_PLATFORMS 16 16 | #define NUM_VAL_PLATFORMS 5 17 | #define NUM_REGIONS 4 18 | 19 | #define REGIONAL_INDICATOR 0 20 | #define PLATFORM_INDICATOR 1 21 | #define VAL_PLATFORM_INDICATOR 2 22 | 23 | /* 24 | * Header file for type definitions used as input arguments. 25 | * Helps with input sanitisation, allows the client to abort a request before sending if the arguments are known to be invalid 26 | * 27 | * [SECTION] Platform and Routing 28 | */ 29 | 30 | namespace riotcpp { 31 | namespace args { 32 | 33 | /* 34 | * [SECTION] Platform and Routing 35 | */ 36 | 37 | enum class platform : int { 38 | BR1 = 0, 39 | EUN1, 40 | EUW1, 41 | JP1, 42 | KR, 43 | LA1, 44 | LA2, 45 | NA1, 46 | OC1, 47 | TR1, 48 | RU, 49 | PH2, 50 | SG2, 51 | TH2, 52 | TW2, 53 | VN2 54 | }; 55 | 56 | enum class val_platform : int { 57 | AP = 0, 58 | BR, 59 | EU, 60 | KR, 61 | LATAM, 62 | NA, 63 | ESPORTS // 6 64 | }; 65 | 66 | enum class regional : int { 67 | AMERICAS = 0, 68 | ASIA, 69 | EUROPE, 70 | SEA 71 | }; 72 | 73 | typedef union routing_union { 74 | regional reg; 75 | platform pltform; 76 | val_platform vpltform; 77 | }routing_union; 78 | 79 | typedef struct routing { 80 | routing_union routng; 81 | int indicator; 82 | constexpr routing(regional r) { routng.reg = r; indicator = 0;} 83 | constexpr routing(platform p) { routng.pltform = p; indicator = 1;} 84 | constexpr routing(val_platform v) { routng.vpltform = v; indicator = 2;} 85 | } routing; 86 | 87 | routing str_to_routing(const std::string&); 88 | 89 | regional platform_to_regional(platform) noexcept; 90 | 91 | std::string platform_to_str(const platform) noexcept; 92 | std::optional str_to_platform(const std::string& platfrm) noexcept; 93 | bool valid_platform(const std::string&) noexcept; 94 | 95 | std::string regional_to_str(const regional) noexcept; 96 | std::optional str_to_regional(const std::string&) noexcept; 97 | bool valid_regional(const std::string&) noexcept; 98 | 99 | std::string val_platform_to_str(const val_platform) noexcept; 100 | std::optional str_to_val_platform(const std::string&) noexcept; 101 | bool valid_val_platform(const std::string&) noexcept; 102 | 103 | enum class division : int { 104 | I = 0, 105 | II, 106 | III, 107 | IV 108 | }; 109 | 110 | std::string division_to_str(const division) noexcept; 111 | std::optional str_to_division(const std::string&) noexcept; 112 | bool valid_division(const std::string&) noexcept; 113 | 114 | enum class tier : int { 115 | IRON = 0, 116 | BRONZE, 117 | SILVER, 118 | GOLD, 119 | PLATINUM, 120 | EMERALD, 121 | DIAMOND 122 | }; 123 | 124 | std::string tier_to_str(const tier) noexcept; 125 | std::optional str_to_tier(const std::string&) noexcept; 126 | bool valid_tier(const std::string&) noexcept; 127 | 128 | enum class queue : int { 129 | RANKED_SOLO_5x5 = 0, 130 | RANKED_FLEX_SR, 131 | RANKED_FLEX_TT 132 | }; 133 | 134 | std::string queue_to_str(const queue) noexcept; 135 | std::optional str_to_queue(const std::string&) noexcept; 136 | bool valid_queue(const std::string&) noexcept; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/scope_count.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef DEBUG_MODE 11 | #define rcp_assert(x, msg) if (!x) {std::cerr << "ASSERTION FAILED: " << msg << std::endl;} 12 | #else 13 | #define rcp_assert(x, msg) 14 | #endif 15 | namespace riotcpp { 16 | namespace rate { 17 | 18 | /** 19 | * ScopeCount attempts to track the state of Riot's rate limiter to avoid breaching limits 20 | * A ScopeCount tracks only one limit of either an application or method limit 21 | * Any request made to a given endpoint within a certain region must call get_wait_time() before sending 22 | */ 23 | class ScopeCount { 24 | 25 | private: 26 | 27 | int duration_ = 0; 28 | int limit_ = 0; 29 | int count_ = 0; 30 | unsigned next_reset_ = 0; 31 | 32 | unsigned update_count(bool update_reset = false); 33 | 34 | public: 35 | 36 | ScopeCount(int duration, int limit, int count); 37 | ~ScopeCount() = default; 38 | 39 | inline void correct_count(int server_counter, int server_limit, int server_duration); 40 | inline void insert_request(unsigned server_time); 41 | inline int get_wait_time(); 42 | inline int n_available(); 43 | 44 | std::string to_string() const; 45 | 46 | }; 47 | 48 | /** 49 | * @param update flag, if we haven't made a new request we do not want to update the reset time as this will possibly desync with 50 | * riot's timer 51 | * @return current time to avoid multiple time requests 52 | */ 53 | inline unsigned ScopeCount::update_count(bool update_reset) { 54 | unsigned current_time = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); 55 | 56 | // if we have not passed the next reset there is nothing to reset 57 | if (current_time <= this->next_reset_) { 58 | return current_time; 59 | } 60 | 61 | this->count_ = 0; 62 | 63 | // If we have made a request and want to upate the count we must also update the new reset time 64 | if (update_reset) { 65 | this->next_reset_ = current_time + this->duration_; 66 | } 67 | return current_time; 68 | } 69 | 70 | inline ScopeCount::ScopeCount(int duration, int limit, int count) { 71 | rcp_assert(duration > 0, "Negative duration given"); 72 | rcp_assert(limit > 0, "Negative limit given"); 73 | rcp_assert(limit >= 0, "Negative count given"); 74 | this->duration_ = duration; 75 | this->limit_ = limit; 76 | this->count_ = count; 77 | } 78 | 79 | inline void ScopeCount::insert_request(unsigned server_time) { 80 | this->update_count(server_time >= this->next_reset_); // conservative approach update if equals 81 | this->count_++; 82 | } 83 | 84 | inline int ScopeCount::n_available() { 85 | this->update_count(); 86 | return this->limit_ - this->count_; 87 | } 88 | 89 | inline int ScopeCount::get_wait_time() { 90 | unsigned current_time = this->update_count(); 91 | return 0 + (this->next_reset_ - current_time) * (this->count_ >= this->limit_) * (current_time < this->next_reset_); 92 | } 93 | 94 | inline void ScopeCount::correct_count(int server_count, int server_limit, int server_duration) { 95 | this->duration_ = server_duration; 96 | this->limit_ = server_limit; 97 | this->count_ = server_count; 98 | } 99 | 100 | inline std::string ScopeCount::to_string() const { 101 | std::stringstream ss; 102 | ss << "Next Reset: " << this->next_reset_ << " Duration: " << this->duration_ << " Limit: " << this->limit_ << " Count: " << this->count_; 103 | return ss.str(); 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/riot-cpp/handling/rate_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "rate_handler.h" 4 | 5 | namespace riotcpp { 6 | namespace rate { 7 | 8 | // STRING PARSING HELPERS 9 | 10 | static inline int chars_to_int(char nums[10], int num_digits) { 11 | int to_return = 0; 12 | for (int i = 0; i < num_digits; i++) { 13 | to_return += ((int) (nums[i] - '0')) * (10^(num_digits - i - 1)); 14 | } 15 | return to_return; 16 | } 17 | 18 | static std::pair, std::vector> extract_duration_limits_counts(const std::string& description) { 19 | // this function assumes valid format of descriptor "20:1, 100:120"... 20 | int num_digits = 0; 21 | char nums[10]; 22 | bool limit_add = true; 23 | std::vector limit; 24 | std::vector duration; 25 | 26 | for (const char& pos_digit : description) { 27 | if (std::isdigit(pos_digit)) { 28 | nums[num_digits] = pos_digit; 29 | } else { 30 | if (limit_add) { 31 | limit.push_back(chars_to_int(nums, num_digits)); 32 | } else { 33 | duration.push_back(chars_to_int(nums, num_digits)); 34 | } 35 | limit_add = !limit_add; 36 | num_digits = 0; 37 | } 38 | } 39 | 40 | return {duration, limit}; 41 | } 42 | 43 | // CLASS DEFINITION 44 | 45 | bool RateHandler::initialise_counts(const query::RiotHeader& response_header) { 46 | std::pair, std::vector> duration_limits = extract_duration_limits_counts(response_header.app_limit); 47 | std::pair, std::vector> duration_counts = extract_duration_limits_counts(response_header.app_limit_count); 48 | std::vector default_counts (duration_limits.first.size()); 49 | for (int i = 0; i < default_counts.size(); i++) { 50 | default_counts[i] = 0; 51 | } 52 | for (int i = 0; i < NUM_PLATFORMS; i++) { 53 | this->platform_counts[i].init_limits(duration_limits.first, duration_limits.second, default_counts); 54 | } 55 | for (int i = 0; i < NUM_REGIONS; i++) { 56 | this->region_counts[i].init_limits(duration_limits.first, duration_limits.second, default_counts); 57 | } 58 | for (int i = 0; i < NUM_VAL_PLATFORMS; i++) { 59 | this->val_plaform_counts[i].init_limits(duration_limits.first, duration_limits.second, default_counts); 60 | } 61 | return true; 62 | } 63 | 64 | bool RateHandler::check_rate_limits(std::shared_ptr request) { 65 | if (!this->initialised) { 66 | return true; // we can only get information about the rate limit by making requests 67 | } 68 | int wait_time = 0; 69 | switch (request->route.indicator) { 70 | case REGIONAL_INDICATOR: 71 | wait_time = this->region_counts[static_cast(request->route.routng.reg)].get_wait_time(request->method_key); break; 72 | case PLATFORM_INDICATOR: 73 | wait_time = this->platform_counts[static_cast(request->route.routng.pltform)].get_wait_time(request->method_key); break; 74 | case VAL_PLATFORM_INDICATOR: 75 | wait_time = this->val_plaform_counts[static_cast(request->route.routng.vpltform)].get_wait_time(request->method_key); break; 76 | default: 77 | rcp_assert(false, "Invalid routing indicator given: " << request->route.indicator << ", should be 0, 1, 2"); 78 | } 79 | if (wait_time == 0) { 80 | return true; // OK to send 81 | } 82 | 83 | // update time to send and return false; 84 | request->send_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() + wait_time; 85 | return false; 86 | } 87 | 88 | void RateHandler::insert_request(std::shared_ptr request) { 89 | if (!this->initialised) { 90 | this->initialised = this->initialise_counts(request->response_header); 91 | } 92 | return; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/Rate_Tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "../src/riot-cpp/client/client.h" 10 | namespace riotcpp { 11 | namespace rate { 12 | 13 | static inline unsigned current_time_secs() { 14 | return std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); 15 | } 16 | 17 | //TEST_CASE("Scope Count - No Requests") { 18 | // ScopeCount scope_count = ScopeCount(120, 100, 0); 19 | // REQUIRE(0 == scope_count.get_wait_time()); 20 | // REQUIRE(100 == scope_count.n_available()); 21 | //} 22 | 23 | //TEST_CASE("Scope Count - Insertions") { 24 | // int duration = 120; 25 | // int limit = 100; 26 | // int init_count = 0; 27 | 28 | // ScopeCount scope_count = ScopeCount(duration, limit, init_count); 29 | 30 | // scope_count.insert_request(current_time_secs()); 31 | // REQUIRE(limit-1 == scope_count.n_available()); 32 | // 33 | // int n_insertions = 20; 34 | // for (int i = 0; i < n_insertions; i++) { 35 | // scope_count.insert_request(current_time_secs()); 36 | // } 37 | // REQUIRE(limit - 1 - n_insertions == scope_count.n_available()); 38 | //} 39 | 40 | //TEST_CASE("Scope Count - Wait time") { 41 | // int duration = 120; 42 | // int limit = 100; 43 | // int init_count = 0; 44 | 45 | // ScopeCount scope_count = ScopeCount(duration, limit, init_count); 46 | // 47 | // unsigned first_request_time = current_time_secs(); 48 | // for (int i = 0; i < limit; i++) { 49 | // scope_count.insert_request(first_request_time); 50 | // } 51 | 52 | // unsigned current_time = current_time_secs(); 53 | // REQUIRE(duration - (current_time - first_request_time) == scope_count.get_wait_time()); 54 | 55 | // std::this_thread::sleep_for(std::chrono::seconds(10)); 56 | 57 | // current_time = current_time_secs(); 58 | // REQUIRE(duration - (current_time - first_request_time) == scope_count.get_wait_time()); 59 | //} 60 | 61 | //TEST_CASE("Scope Count - Availability after wait") { 62 | // int duration = 120; 63 | // int limit = 100; 64 | // int init_count = 0; 65 | 66 | // ScopeCount scope_count = ScopeCount(duration, limit, init_count); 67 | // 68 | // unsigned first_request_time = current_time_secs(); 69 | // for (int i = 0; i < limit; i++) { 70 | // scope_count.insert_request(first_request_time); 71 | // } 72 | 73 | // unsigned current_time = current_time_secs(); 74 | // REQUIRE(duration - (current_time - first_request_time) == scope_count.get_wait_time()); 75 | 76 | // std::this_thread::sleep_for(std::chrono::seconds(scope_count.get_wait_time())); 77 | 78 | // REQUIRE(0 == scope_count.get_wait_time()); 79 | //} 80 | 81 | //TEST_CASE("Rate Hierachy - Construction") { 82 | // constexpr int durations[3] = {1, 10, 120}; 83 | // constexpr int limits[3] = {10, 50, 100}; 84 | // constexpr int counts[3] = {1, 1, 1}; 85 | // constexpr int size = 3; 86 | // std::string header_repr = "10:1, 50:10, 100:120"; 87 | 88 | // RateHierachy explicit_construction(durations, limits, counts, size); 89 | // RateHierachy string_construction(header_repr); 90 | 91 | // REQUIRE(string_construction.to_string() == explicit_construction.to_string()); 92 | //} 93 | 94 | TEST_CASE("Rate Hierachy - Wait time") { 95 | constexpr int size = 2; 96 | constexpr int durations[size] = {10, 120}; 97 | constexpr int limits[size] = {50, 75}; 98 | constexpr int counts[size] = {1, 1}; 99 | 100 | RateHierachy rate_hierachy(durations, limits, counts, size); 101 | // No wait time when rate limit is not exceeded 102 | REQUIRE(rate_hierachy.get_wait_time() == 0); 103 | 104 | unsigned current_time = current_time_secs(); 105 | unsigned reset_times[size] = { 106 | current_time + durations[0], current_time + durations[1] 107 | }; 108 | rate_hierachy.insert_request(current_time); 109 | 110 | REQUIRE(rate_hierachy.get_wait_time() == 0); 111 | for (int i = 0; i < 49; i++) { 112 | rate_hierachy.insert_request(current_time); 113 | } 114 | current_time = current_time_secs(); 115 | REQUIRE(reset_times[0] - current_time == rate_hierachy.get_wait_time()); 116 | 117 | std::this_thread::sleep_for(std::chrono::seconds(rate_hierachy.get_wait_time())); 118 | REQUIRE(rate_hierachy.get_wait_time() == 0); 119 | 120 | current_time = current_time_secs(); 121 | for (int i = 0; i < 25; i++) { 122 | rate_hierachy.insert_request(current_time); 123 | } 124 | current_time = current_time_secs(); 125 | REQUIRE(reset_times[1] - current_time == rate_hierachy.get_wait_time()); 126 | } 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/riot-cpp/logging/logger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "logger.h" 4 | 5 | namespace riotcpp { 6 | namespace logging { 7 | static std::string Err_Codes(const int code) { // official message 8 | switch (code) { 9 | case 200: 10 | return std::string("Successful request"); 11 | case 400: 12 | return std::string("Bad request"); 13 | case 401: 14 | return std::string("Unauthorized"); 15 | case 403: 16 | return std::string("Forbidden"); 17 | case 404: 18 | return std::string("Data not found"); 19 | case 405: 20 | return std::string("Method not found"); 21 | case 415: 22 | return std::string("Unsupported media type"); 23 | case 429: 24 | return std::string("Rate limit exceeded"); 25 | case 500: 26 | return std::string("Internal service error"); 27 | case 502: 28 | return std::string("Bad gateway"); 29 | case 503: 30 | return std::string("Service unavailable"); 31 | case 504: 32 | return std::string("Gateway timeout"); 33 | case -1: 34 | return std::string("CURL error. Please check your internet connection or view logs for more information"); 35 | case -2: 36 | return std::string("Unexpected header response format. Unable to parse header to Json object"); 37 | default: 38 | throw std::invalid_argument("Invalide error code passed" + std::to_string(code)); 39 | } 40 | } 41 | 42 | static std::string Code_Meaning(const int code) { // more informative (ty shieldbow riot api) 43 | switch (code) { 44 | case 200: 45 | return std::string("Successful request"); 46 | case 400: 47 | return std::string("Verify the request URL, headers, and body parameters."); 48 | case 401: 49 | return std::string("No API key was found, please make sure you're providing one."); 50 | case 403: 51 | return std::string("Either your API key is invalid/blacklisted or you don't have access to the requested resource."); 52 | case 404: 53 | return std::string("The requested resource was not found."); 54 | case 415: 55 | return std::string("The provided body is of an invalid type."); 56 | case 429: 57 | return std::string("You have reached the rate limit."); 58 | case 500: 59 | return std::string("The server encountered an error. Please try again later."); 60 | case 502: 61 | return std::string("Bad gateway"); 62 | case 503: 63 | return std::string("The server is down. Please try again later."); 64 | case 504: 65 | return std::string("Gateway timeout"); 66 | case -1: 67 | return std::string("CURL error. Please check your internet connection or view logs for more information"); 68 | case -2: 69 | return std::string("Unexpected header response format. Unable to parse header to Json object"); 70 | default: 71 | throw std::invalid_argument("Invalid error code passed: " + std::to_string(code)); 72 | } 73 | }; 74 | 75 | static std::string level_string(const LEVEL &log_level) { 76 | switch (log_level) { 77 | case LEVEL::DEBUG: 78 | return std::string("DEBUG"); 79 | case LEVEL::INFO: 80 | return std::string("INFO"); 81 | case LEVEL::WARNING: 82 | return std::string("WARNING"); 83 | case LEVEL::ERRORS: 84 | return std::string("ERRORS"); 85 | case LEVEL::CRITICAL: 86 | return std::string("CRITICAL"); 87 | default: 88 | throw std::invalid_argument("Invalid log level passed"); // somehow 89 | } 90 | } 91 | 92 | static const std::string get_current_time() { // riot format 93 | const std::time_t cur_time = std::time(NULL); 94 | std::tm *gmt_time = std::gmtime(&cur_time); 95 | char time_buf[sizeof("Sun, 05 Feb 2023 08:36:28 GMT") + 2]; 96 | strftime(time_buf, sizeof time_buf, "[%a, %d %b %y %H:%M:%S GMT]", gmt_time); 97 | std::string log_msg{time_buf}; 98 | return log_msg; 99 | }; 100 | 101 | Logger& Logger::operator<<(const LEVEL& log_level) { 102 | if (this->_log_level <= log_level) { 103 | std::string log_msg = get_current_time(); 104 | log_msg = log_msg + " " + level_string(log_level) + ":"; 105 | this->_log_file << log_msg; 106 | this->_incoming = true; 107 | } 108 | return *this; 109 | }; 110 | 111 | Logger& Logger::operator<<(const char* message) { 112 | if (this->_incoming) { 113 | this->_log_file << "\n " << message; 114 | } 115 | return *this; 116 | } 117 | 118 | Logger& Logger::operator<<(const std::string& message) { 119 | if (this->_incoming) { 120 | this->_log_file << "\n " << message; 121 | } 122 | return *this; 123 | } 124 | 125 | Logger& Logger::operator<<(const int err_code) { 126 | if (this->_incoming && !err_code) { 127 | this->_log_file << std::endl; // use std::endl as we want to flush the buffer on every log message 128 | this->_incoming = false; 129 | return *this; 130 | } 131 | if (this->_incoming) { 132 | this->_log_file << "\n " << err_code << ": " << Err_Codes(err_code); 133 | if (this->_verbose){ 134 | this->_log_file << "\n " << Code_Meaning(err_code); 135 | } 136 | } 137 | return *this; 138 | } 139 | 140 | Logger& Logger::operator<<(const rate::RegionCount& region_count) { 141 | if (this->_incoming) { 142 | this->_log_file << "\n " << "Region Rate State"; 143 | this->_log_file << "\n " << region_count.to_string(); 144 | } 145 | return *this; 146 | } 147 | 148 | Logger& Logger::operator<<(const query::RiotHeader& response) { 149 | if (!this->_verbose) { 150 | return *this; 151 | } 152 | if (this->_incoming) { 153 | this->_log_file << "\n---- Response Header ----\n"; 154 | this->_log_file << " Date: " << response.date << "\n"; 155 | this->_log_file << " Application Rate Limit: " << response.app_limit << "\n"; 156 | this->_log_file << " Application Rate Count: " << response.app_limit_count << "\n"; 157 | this->_log_file << " Method Rate Limit: " << response.method_limit << "\n"; 158 | this->_log_file << " Method Rate Count: " << response.method_limit_count << "\n"; 159 | this->_log_file << " Retry After: " << response.retry_after << "\n"; 160 | } 161 | return *this; 162 | } 163 | 164 | Logger::Logger(std::string log_path, LEVEL log_level, bool verbose, bool log_q_time) { 165 | this->_log_path = log_path; 166 | this->_log_level = log_level; 167 | this->_verbose = verbose; 168 | this->_log_q_time = log_q_time; 169 | this->_log_file.open(log_path, std::ios::app); 170 | std::string init_time = get_current_time(); 171 | if (!this->_log_file.is_open()) { 172 | throw std::runtime_error("Unable to open log file"); 173 | } else { 174 | this->_log_file << init_time << " Log Initialisation:\n Log file successfully opened. \n"; 175 | } 176 | } 177 | 178 | Logger::~Logger() { 179 | this->_log_file.close(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/riot-cpp/client/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "client.h" 16 | namespace riotcpp { 17 | namespace client { 18 | 19 | using func_type = std::function(std::shared_ptr)>; 20 | 21 | static std::string extract_key(const std::string& path_to_config) { 22 | 23 | std::ifstream config(path_to_config); 24 | 25 | if (!config.is_open()) { 26 | throw std::runtime_error("Invalid path to file, or file does not exist"); 27 | } 28 | 29 | const std::regex key_reg("RGAPI[-0123456789abcdefABCDEF]{0,38}\""); 30 | 31 | std::stringstream file_cont; 32 | file_cont << config.rdbuf(); 33 | std::string filestr = file_cont.str(); 34 | std::smatch base_match; 35 | 36 | if (std::regex_search(filestr, base_match, key_reg)) { 37 | config.close(); 38 | std::string match = base_match[0].str(); 39 | return match.substr(0, match.size()-1); 40 | } else { 41 | config.close(); 42 | throw std::runtime_error("Configuration file does not exist or has unexpected format"); 43 | }; 44 | } 45 | 46 | RiotApiClient::RiotApiClient(std::string path_to_config, std::string path_to_log, logging::LEVEL report_level, bool verbose_logging) : 47 | logger(path_to_log, report_level, verbose_logging), 48 | request_handler(&(this->logger)), 49 | endpoint_call(std::bind_front(&RiotApiClient::query, this)), 50 | Account(&this->endpoint_call), 51 | Champion_Mastery(&this->endpoint_call), 52 | Champion(&this->endpoint_call), 53 | Clash(&this->endpoint_call), 54 | League_exp(&this->endpoint_call), 55 | League(&this->endpoint_call), 56 | Lol_Challenges(&this->endpoint_call), 57 | Lol_Status(&this->endpoint_call), 58 | Lor_Match(&this->endpoint_call), 59 | Lor_Ranked(&this->endpoint_call), 60 | Lor_Status(&this->endpoint_call), 61 | Match(&this->endpoint_call), 62 | Spectator_Tft(&this->endpoint_call), 63 | Spectator(&this->endpoint_call), 64 | Summoner(&this->endpoint_call), 65 | Tft_League(&this->endpoint_call), 66 | Tft_Match(&this->endpoint_call), 67 | Tft_Status(&this->endpoint_call), 68 | Tft_Summoner(&this->endpoint_call), 69 | Val_Content(&this->endpoint_call), 70 | Val_Match(&this->endpoint_call), 71 | Val_Ranked(&this->endpoint_call), 72 | Val_Status(&this->endpoint_call) { 73 | curl_global_init(CURL_GLOBAL_ALL); 74 | 75 | // initialised libcurl handle and header 76 | std::string api_key = extract_key(path_to_config); 77 | this->header = curl_slist_append(header, (std::string("X-RIOT-TOKEN: ") + api_key).c_str()); 78 | 79 | this->easy_handle = curl_easy_init(); 80 | } 81 | 82 | RiotApiClient::~RiotApiClient() { 83 | curl_slist_free_all(this->header); 84 | curl_easy_cleanup(this->easy_handle); 85 | curl_global_cleanup(); 86 | } 87 | 88 | static size_t WriteCallBack(void* contents, size_t size, size_t nmemb, void* buffer) { 89 | 90 | size_t real_size = size * nmemb; 91 | char *new_chars = (char *)contents; 92 | 93 | std::vector *new_buffer = static_cast*>(buffer); 94 | new_buffer->insert(new_buffer->end(), &new_chars[0], &new_chars[nmemb]); 95 | 96 | return real_size; 97 | } 98 | 99 | static size_t WriteCallBack_header(char* buffer, size_t size, size_t nitems, void* user_data) { 100 | std::size_t real_size = nitems * size; 101 | 102 | if (*buffer == 'H') { 103 | return real_size; 104 | } 105 | 106 | query::RiotHeader* new_header = static_cast(user_data); 107 | char* write_field; 108 | 109 | switch (buffer[0]) { 110 | case 'D': 111 | write_field = &new_header->date[0]; break; 112 | case 'X': 113 | switch (buffer[2]) { 114 | case 'A': 115 | write_field = buffer[17] == 'C' ? &new_header->app_limit_count[0] : &new_header->app_limit[0]; break; 116 | case 'M': 117 | write_field = buffer[20] == 'C' ? &new_header->method_limit_count[0] : &new_header->method_limit[0]; break; 118 | default: 119 | return real_size; 120 | } break; 121 | case 'R': 122 | write_field = &new_header->retry_after[0]; break; 123 | default: // can't be bother parsing headers we dont need/use 124 | return real_size; 125 | } 126 | 127 | char* colon = std::find(buffer, buffer + nitems, ':'); 128 | if (colon == buffer + nitems) { 129 | return real_size; 130 | } 131 | 132 | strncpy(write_field, colon+2, nitems - (std::size_t)(colon - buffer) - 4); 133 | write_field[nitems - (std::size_t)(colon - buffer) - 4] = 0; 134 | 135 | return real_size; 136 | } 137 | 138 | bool RiotApiClient::get(std::shared_ptr request) { 139 | 140 | request->response_content->clear(); 141 | 142 | curl_easy_setopt(this->easy_handle, CURLOPT_URL, request->url.get()); 143 | curl_easy_setopt(this->easy_handle, CURLOPT_HTTPGET, 1); 144 | curl_easy_setopt(this->easy_handle, CURLOPT_HTTPHEADER, this->header); 145 | 146 | curl_easy_setopt(this->easy_handle, CURLOPT_WRITEFUNCTION, WriteCallBack); 147 | curl_easy_setopt(this->easy_handle, CURLOPT_WRITEDATA, &(*request->response_content)); 148 | 149 | curl_easy_setopt(this->easy_handle, CURLOPT_HEADERFUNCTION, WriteCallBack_header); 150 | curl_easy_setopt(this->easy_handle, CURLOPT_HEADERDATA, &request->response_header); 151 | 152 | CURLcode res_ = curl_easy_perform(this->easy_handle); 153 | 154 | if (res_ != CURLE_OK) { 155 | this->logger << logging::LEVEL::CRITICAL << "CURL failed to send request" << 0; 156 | request->last_response = -1; // CURL ERRORS 157 | return false; 158 | } 159 | // null terminant buffer for json parsing 160 | request->response_content->push_back(0); 161 | 162 | this->logger << logging::LEVEL::DEBUG << request->response_header << 0; 163 | 164 | curl_easy_getinfo(this->easy_handle, CURLINFO_RESPONSE_CODE, &(request->last_response)); 165 | 166 | if (request->last_response == 200) { // only parse content to json if request was successful 167 | this->logger << logging::LEVEL::DEBUG << "Query Successful" << 0; 168 | } 169 | 170 | return true; 171 | } 172 | static inline void wait_until(std::time_t send_time) { 173 | const std::time_t c_time = std::time(NULL); 174 | std::time_t current_time = std::mktime(std::gmtime(&c_time)); 175 | if (current_time >= send_time) { 176 | return; 177 | } else { 178 | std::this_thread::sleep_for(std::chrono::seconds(send_time - current_time)); 179 | return; 180 | } 181 | } 182 | 183 | std::unique_ptr RiotApiClient::query(std::shared_ptr request) { 184 | 185 | this->logger << logging::LEVEL::DEBUG << "--Query Call--" << std::string(request->url.get()) << 0; 186 | 187 | while (this->request_handler.review_request(request)) { 188 | if (request->last_response == 200) { 189 | return std::move(request->response_content); 190 | } 191 | if (!this->request_handler.validate_request(request)) { 192 | this->logger << logging::LEVEL::WARNING << "Request sent was invalid or the server is unavailable" << 0; 193 | throw std::runtime_error("Request sent was invalid or the server is unavailable"); 194 | } 195 | this->logger << logging::LEVEL::DEBUG << "Request Validated" << 0; 196 | wait_until(request->send_time); 197 | this->get(request); 198 | } 199 | 200 | this->logger << logging::LEVEL::ERRORS << "Failed request" << request->method_key << request->last_response << 0; 201 | return std::move(request->response_content); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /test/type_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../src/riot-cpp/types/args.h" 4 | 5 | namespace riotcpp { 6 | namespace args { 7 | 8 | TEST_CASE("PLATFORM SANITISATION") { 9 | /* 10 | * User may enter the routing as "BR1" or "br1", we should accept both 11 | */ 12 | REQUIRE(valid_platform("BR1")); 13 | REQUIRE(valid_platform("EUN1")); 14 | REQUIRE(valid_platform("EUW1")); 15 | REQUIRE(valid_platform("JP1")); 16 | REQUIRE(valid_platform("KR")); 17 | REQUIRE(valid_platform("LA")); 18 | REQUIRE(valid_platform("LA")); 19 | REQUIRE(valid_platform("NA")); 20 | REQUIRE(valid_platform("OC")); 21 | REQUIRE(valid_platform("TR")); 22 | REQUIRE(valid_platform("RU")); 23 | REQUIRE(valid_platform("PH")); 24 | REQUIRE(valid_platform("SG")); 25 | REQUIRE(valid_platform("TH")); 26 | REQUIRE(valid_platform("TW")); 27 | REQUIRE(valid_platform("VN")); 28 | 29 | REQUIRE(valid_platform("br1")); 30 | REQUIRE(valid_platform("euN1")); 31 | REQUIRE(valid_platform("euW1")); 32 | REQUIRE(valid_platform("jp1")); 33 | REQUIRE(valid_platform("kr")); 34 | REQUIRE(valid_platform("la")); 35 | REQUIRE(valid_platform("la")); 36 | REQUIRE(valid_platform("na")); 37 | REQUIRE(valid_platform("oc")); 38 | REQUIRE(valid_platform("tr")); 39 | REQUIRE(valid_platform("ru")); 40 | REQUIRE(valid_platform("ph")); 41 | REQUIRE(valid_platform("sg")); 42 | REQUIRE(valid_platform("th")); 43 | REQUIRE(valid_platform("tw")); 44 | REQUIRE(valid_platform("vn")); 45 | } 46 | 47 | TEST_CASE("REGIONAL SANITISATION") { 48 | /* 49 | * User may enter the routing as "americas" or "AMERICAS", we should accept both 50 | */ 51 | REQUIRE(valid_regional("AMERICAS")); 52 | REQUIRE(valid_regional("americas")); 53 | REQUIRE(valid_regional("AMERicas")); 54 | REQUIRE(valid_regional("amerICAS")); 55 | 56 | REQUIRE(valid_regional("ASIA")); 57 | REQUIRE(valid_regional("asia")); 58 | REQUIRE(valid_regional("asIA")); 59 | REQUIRE(valid_regional("AsiA")); 60 | 61 | REQUIRE(valid_regional("EUROPE")); 62 | REQUIRE(valid_regional("europe")); 63 | REQUIRE(valid_regional("eurOPE")); 64 | REQUIRE(valid_regional("EUROpe")); 65 | 66 | REQUIRE(valid_regional("SEA")); 67 | REQUIRE(valid_regional("sea")); 68 | REQUIRE(valid_regional("sEA")); 69 | REQUIRE(valid_regional("Sea")); 70 | } 71 | 72 | TEST_CASE("PLATFORM TO STRING") { 73 | REQUIRE("BR1" == platform_to_str(platform::BR1)); 74 | REQUIRE("EUN1" == platform_to_str(platform::EUN1)); 75 | REQUIRE("EUW1" == platform_to_str(platform::EUW1)); 76 | REQUIRE("JP1" == platform_to_str(platform::JP1)); 77 | REQUIRE("KR" == platform_to_str(platform::KR)); 78 | REQUIRE("LA1" == platform_to_str(platform::LA1)); 79 | REQUIRE("LA2" == platform_to_str(platform::LA2)); 80 | REQUIRE("NA1" == platform_to_str(platform::NA1)); 81 | REQUIRE("OC1" == platform_to_str(platform::OC1)); 82 | REQUIRE("TR1" == platform_to_str(platform::TR1)); 83 | REQUIRE("RU" == platform_to_str(platform::RU)); 84 | REQUIRE("PH2" == platform_to_str(platform::PH2)); 85 | REQUIRE("SG2" == platform_to_str(platform::SG2)); 86 | REQUIRE("TH2" == platform_to_str(platform::TH2)); 87 | REQUIRE("TW2" == platform_to_str(platform::TW2)); 88 | REQUIRE("VN2" == platform_to_str(platform::VN2)); 89 | } 90 | 91 | TEST_CASE("STRING TO PLATFORM") { 92 | REQUIRE(platform::BR1 == str_to_platform("BR1")); 93 | REQUIRE(platform::EUN1 == str_to_platform("EUN1")); 94 | REQUIRE(platform::EUW1 == str_to_platform("EUW1")); 95 | REQUIRE(platform::JP1 == str_to_platform("JP1")); 96 | REQUIRE(platform::KR == str_to_platform("KR")); 97 | REQUIRE(platform::LA1 == str_to_platform("LA1")); 98 | REQUIRE(platform::LA2 == str_to_platform("LA2")); 99 | REQUIRE(platform::NA1 == str_to_platform("NA1")); 100 | REQUIRE(platform::OC1 == str_to_platform("OC1")); 101 | REQUIRE(platform::TR1 == str_to_platform("TR1")); 102 | REQUIRE(platform::RU == str_to_platform("RU")); 103 | REQUIRE(platform::PH2 == str_to_platform("PH2")); 104 | REQUIRE(platform::SG2 == str_to_platform("SG2")); 105 | REQUIRE(platform::TH2 == str_to_platform("TH2")); 106 | REQUIRE(platform::TW2 == str_to_platform("TW2")); 107 | REQUIRE(platform::VN2 == str_to_platform("VN2")); 108 | } 109 | 110 | TEST_CASE("REGIONAL TO STRING") { 111 | REQUIRE("AMERICAS" == regional_to_str(regional::AMERICAS)); 112 | REQUIRE("ASIA" == regional_to_str(regional::ASIA)); 113 | REQUIRE("EUROPE" == regional_to_str(regional::EUROPE)); 114 | REQUIRE("SEA" == regional_to_str(regional::SEA)); 115 | } 116 | 117 | TEST_CASE("STRING TO REGIONAL") { 118 | REQUIRE(regional::AMERICAS == str_to_regional("AMERICAS")); 119 | REQUIRE(regional::ASIA == str_to_regional("ASIA")); 120 | REQUIRE(regional::EUROPE == str_to_regional("EUROPE")); 121 | REQUIRE(regional::SEA == str_to_regional("SEA")); 122 | } 123 | 124 | TEST_CASE("PLATFORM TO REGIONAL") { 125 | REQUIRE(regional::AMERICAS == platform_to_regional(platform::NA1)); 126 | REQUIRE(regional::AMERICAS == platform_to_regional(platform::BR1)); 127 | REQUIRE(regional::AMERICAS == platform_to_regional(platform::LA1)); 128 | REQUIRE(regional::AMERICAS == platform_to_regional(platform::LA2)); 129 | 130 | REQUIRE(regional::ASIA == platform_to_regional(platform::KR)); 131 | REQUIRE(regional::ASIA == platform_to_regional(platform::JP1)); 132 | 133 | REQUIRE(regional::EUROPE == platform_to_regional(platform::EUN1)); 134 | REQUIRE(regional::EUROPE == platform_to_regional(platform::EUW1)); 135 | REQUIRE(regional::EUROPE == platform_to_regional(platform::TR1)); 136 | REQUIRE(regional::EUROPE == platform_to_regional(platform::RU)); 137 | 138 | REQUIRE(regional::SEA == platform_to_regional(platform::OC1)); 139 | REQUIRE(regional::SEA == platform_to_regional(platform::PH2)); 140 | REQUIRE(regional::SEA == platform_to_regional(platform::SG2)); 141 | REQUIRE(regional::SEA == platform_to_regional(platform::TH2)); 142 | REQUIRE(regional::SEA == platform_to_regional(platform::TW2)); 143 | REQUIRE(regional::SEA == platform_to_regional(platform::VN2)); 144 | } 145 | 146 | TEST_CASE("VALORANT PLATFORM TO STRING") { 147 | REQUIRE("AP" == val_platform_to_str(val_platform::AP)); 148 | REQUIRE("BR" == val_platform_to_str(val_platform::BR)); 149 | REQUIRE("EU" == val_platform_to_str(val_platform::EU)); 150 | REQUIRE("KR" == val_platform_to_str(val_platform::KR)); 151 | REQUIRE("LATAM" == val_platform_to_str(val_platform::LATAM)); 152 | REQUIRE("NA" == val_platform_to_str(val_platform::NA)); 153 | REQUIRE("ESPORTS" == val_platform_to_str(val_platform::ESPORTS)); 154 | } 155 | 156 | TEST_CASE("STRING TO VALORANT PLATFORM") { 157 | REQUIRE(val_platform::AP == str_to_val_platform("AP")); 158 | REQUIRE(val_platform::BR == str_to_val_platform("BR")); 159 | REQUIRE(val_platform::EU == str_to_val_platform("EU")); 160 | REQUIRE(val_platform::KR == str_to_val_platform("KR")); 161 | REQUIRE(val_platform::LATAM == str_to_val_platform("LATAM")); 162 | REQUIRE(val_platform::NA == str_to_val_platform("NA")); 163 | REQUIRE(val_platform::ESPORTS == str_to_val_platform("ESPORTS")); 164 | REQUIRE(std::nullopt == str_to_val_platform("blah blahblah")); 165 | } 166 | 167 | TEST_CASE("DIVISION TO STRING") { 168 | REQUIRE("I" == division_to_str(division::I)); 169 | REQUIRE("II" == division_to_str(division::II)); 170 | REQUIRE("III" == division_to_str(division::III)); 171 | REQUIRE("IV" == division_to_str(division::IV)); 172 | } 173 | 174 | TEST_CASE("STRING TO DIVISION") { 175 | REQUIRE(division::I == str_to_division("I")); 176 | REQUIRE(division::II == str_to_division("II")); 177 | REQUIRE(division::III == str_to_division("III")); 178 | REQUIRE(division::IV == str_to_division("IV")); 179 | REQUIRE(std::nullopt == str_to_division("invalid string")); 180 | } 181 | 182 | TEST_CASE("TIER TO STRING") { 183 | REQUIRE("IRON" == tier_to_str(tier::IRON)); 184 | REQUIRE("BRONZE" == tier_to_str(tier::BRONZE)); 185 | REQUIRE("SILVER" == tier_to_str(tier::SILVER)); 186 | REQUIRE("GOLD" == tier_to_str(tier::GOLD)); 187 | REQUIRE("PLATINUM" == tier_to_str(tier::PLATINUM)); 188 | REQUIRE("EMERALD" == tier_to_str(tier::EMERALD)); 189 | REQUIRE("DIAMOND" == tier_to_str(tier::DIAMOND)); 190 | } 191 | 192 | TEST_CASE("STRING TO TIER") { 193 | REQUIRE(tier::IRON == str_to_tier("IRON")); 194 | REQUIRE(tier::BRONZE == str_to_tier("BRONZE")); 195 | REQUIRE(tier::SILVER == str_to_tier("SILVER")); 196 | REQUIRE(tier::GOLD == str_to_tier("GOLD")); 197 | REQUIRE(tier::PLATINUM == str_to_tier("PLATINUM")); 198 | REQUIRE(tier::EMERALD == str_to_tier("EMERALD")); 199 | REQUIRE(tier::DIAMOND == str_to_tier("DIAMOND")); 200 | REQUIRE(std::nullopt == str_to_tier("invalid blah blah")); 201 | } 202 | 203 | TEST_CASE("QUEUE TO STRING") { 204 | REQUIRE("RANKED_SOLO_5x5" == queue_to_str(queue::RANKED_SOLO_5x5)); 205 | REQUIRE("RANKED_FLEX_SR" == queue_to_str(queue::RANKED_FLEX_SR)); 206 | REQUIRE("RANKED_FLEX_TT" == queue_to_str(queue::RANKED_FLEX_TT)); 207 | } 208 | 209 | TEST_CASE("STRING TO QUEUE") { 210 | REQUIRE(queue::RANKED_SOLO_5x5 == str_to_queue("RANKED_SOLO_5x5")); 211 | REQUIRE(queue::RANKED_FLEX_SR == str_to_queue("RANKED_FLEX_SR")); 212 | REQUIRE(queue::RANKED_FLEX_TT == str_to_queue("RANKED_FLEX_TT")); 213 | REQUIRE(std::nullopt == str_to_queue("invalid blhlhlh")); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/riot-cpp/types/region_enums.cpp: -------------------------------------------------------------------------------- 1 | #include "args.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace riotcpp { 7 | namespace args { 8 | 9 | regional platform_to_regional(platform pform) noexcept { 10 | switch (pform) { 11 | case platform::NA1: 12 | case platform::BR1: 13 | case platform::LA1: 14 | case platform::LA2: 15 | return regional::AMERICAS; 16 | case platform::KR: 17 | case platform::JP1: 18 | return regional::ASIA; 19 | case platform::EUN1: 20 | case platform::EUW1: 21 | case platform::TR1: 22 | case platform::RU: 23 | return regional::EUROPE; 24 | case platform::OC1: 25 | case platform::PH2: 26 | case platform::SG2: 27 | case platform::TH2: 28 | case platform::TW2: 29 | case platform::VN2: 30 | return regional::SEA; 31 | } 32 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class platform"); 33 | return regional::AMERICAS; 34 | } 35 | 36 | /** 37 | * String equality but case insensitive for first string. For example 38 | * "string" == "STRING" true 39 | * "STRING" == "string" false 40 | * sTrING == STRING true 41 | */ 42 | static bool str_equals_upper(const std::string& str1, const char* str2) noexcept { 43 | const int len = str1.length(); 44 | for (int i = 0; i < len; i++) { 45 | if (str2[i] == '\0') { 46 | return false; 47 | } 48 | if (str1[i] == str2[i]) { 49 | continue; 50 | } 51 | if (str1[i] != str2[i] && !(str1[i] >= 97 && str2[i] <= 122 && str1[i] - 32 == str2[i])) { 52 | return false; 53 | } 54 | } 55 | return true; 56 | } 57 | 58 | std::string platform_to_str(platform platfrm) noexcept { 59 | switch (platfrm) { 60 | case platform::BR1: 61 | return "BR1"; 62 | case platform::EUN1: 63 | return "EUN1"; 64 | case platform::EUW1: 65 | return "EUW1"; 66 | case platform::JP1: 67 | return "JP1"; 68 | case platform::KR: 69 | return "KR"; 70 | case platform::LA1: 71 | return "LA1"; 72 | case platform::LA2: 73 | return "LA2"; 74 | case platform::NA1: 75 | return "NA1"; 76 | case platform::OC1: 77 | return "OC1"; 78 | case platform::TR1: 79 | return "TR1"; 80 | case platform::RU: 81 | return "RU"; 82 | case platform::PH2: 83 | return "PH2"; 84 | case platform::SG2: 85 | return "SG2"; 86 | case platform::TH2: 87 | return "TH2"; 88 | case platform::TW2: 89 | return "TW2"; 90 | case platform::VN2: 91 | return "VN2"; 92 | } 93 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class platform"); 94 | return NULL; 95 | } 96 | 97 | std::optional str_to_platform(const std::string& platfrm) noexcept { 98 | constexpr std::array platforms {"BR1","EUN1","EUW1","JP1","KR","LA1","LA2","NA1","OC1","TR1","RU","PH2","SG2","TH2","TW2","VN2"}; 99 | int platform_index= 0; 100 | for (auto& pform : platforms) { 101 | if (str_equals_upper(platfrm, pform)) { 102 | return std::optional{static_cast(platform_index)}; 103 | } 104 | platform_index++; 105 | } 106 | return std::nullopt; 107 | } 108 | 109 | /** 110 | * wrapper of str_to_platform 111 | * @param string to check if valid platform 112 | * @return true if valid platform string false otherwise 113 | */ 114 | bool valid_platform(const std::string& platfrm) noexcept { 115 | std::optional pform = str_to_platform(platfrm); 116 | return pform.has_value(); 117 | } 118 | 119 | std::string regional_to_str(const regional regionl) noexcept { 120 | switch (regionl) { 121 | case regional::AMERICAS: 122 | return "AMERICAS"; 123 | case regional::ASIA: 124 | return "ASIA"; 125 | case regional::EUROPE: 126 | return "EUROPE"; 127 | case regional::SEA: 128 | return "SEA"; 129 | } 130 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class regional"); 131 | return NULL; // not possible 132 | } 133 | 134 | std::optional str_to_regional(const std::string& regionl) noexcept { 135 | constexpr std::array regionals {"AMERICAS", "ASIA", "EUROPE", "SEA"}; 136 | int reg_index = 0; 137 | for (auto& reg : regionals) { 138 | if (str_equals_upper(regionl, reg)) { 139 | return std::optional{static_cast(reg_index)}; 140 | } 141 | reg_index++; 142 | } 143 | return std::nullopt; 144 | } 145 | 146 | /** 147 | * wrapper of str_to_regional 148 | * @param string to check if valid region 149 | * @return true if valid region string false otherwise 150 | */ 151 | bool valid_regional(const std::string& regionl) noexcept { 152 | std::optional reg = str_to_regional(regionl); 153 | return reg.has_value(); 154 | } 155 | 156 | std::string val_platform_to_str(const val_platform vplatform) noexcept { 157 | switch (vplatform) { 158 | case val_platform::AP: 159 | return "AP"; 160 | case val_platform::BR: 161 | return "BR"; 162 | case val_platform::EU: 163 | return "EU"; 164 | case val_platform::KR: 165 | return "KR"; 166 | case val_platform::LATAM: 167 | return "LATAM"; 168 | case val_platform::NA: 169 | return "NA"; 170 | case val_platform::ESPORTS: 171 | return "ESPORTS"; 172 | } 173 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class val_platform"); 174 | return NULL; 175 | } 176 | 177 | std::optional str_to_val_platform(const std::string& vpform) noexcept { 178 | constexpr std::array val_platforms {"AP", "BR", "EU", "KR", "LATAM", "NA", "ESPORTS"}; 179 | int pform_index = 0; 180 | for (auto& vform : val_platforms) { 181 | if (str_equals_upper(vpform, vform)) { 182 | return std::optional(static_cast(pform_index)); 183 | } 184 | pform_index++; 185 | } 186 | return std::nullopt; 187 | } 188 | 189 | /** 190 | * wrapper of str_to_val_platform 191 | * @param string to check if valid val platform 192 | * @return true if valid val platform string false otherwise 193 | */ 194 | bool valid_val_platform(const std::string& vform) noexcept { 195 | std::optional pform = str_to_val_platform(vform); 196 | return pform.has_value(); 197 | } 198 | 199 | std::string division_to_str(const division divi) noexcept { 200 | switch (divi) { 201 | case division::I: 202 | return "I"; 203 | case division::II: 204 | return "II"; 205 | case division::III: 206 | return "III"; 207 | case division::IV: 208 | return "IV"; 209 | } 210 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class division"); 211 | return NULL; 212 | } 213 | 214 | std::optional str_to_division(const std::string& divi) noexcept { 215 | constexpr std::array divisions {"I", "II", "III", "IV"}; 216 | int division_index = 0; 217 | for (auto& divisin : divisions) { 218 | if (str_equals_upper(divi, divisin)) { 219 | return std::optional{static_cast(division_index)}; 220 | } 221 | division_index++; 222 | } 223 | return std::nullopt; 224 | 225 | } 226 | 227 | /** 228 | * wrapper of str_to_division 229 | * @param string to check if valid division 230 | * @return true if valid division string false otherwise 231 | */ 232 | bool valid_division(const std::string& divi) noexcept { 233 | std::optional div = str_to_division(divi); 234 | return div.has_value(); 235 | } 236 | 237 | std::string tier_to_str(const tier tir) noexcept { 238 | switch (tir) { 239 | case tier::IRON: 240 | return "IRON"; 241 | case tier::BRONZE: 242 | return "BRONZE"; 243 | case tier::SILVER: 244 | return "SILVER"; 245 | case tier::GOLD: 246 | return "GOLD"; 247 | case tier::PLATINUM: 248 | return "PLATINUM"; 249 | case tier::EMERALD: 250 | return "EMERALD"; 251 | case tier::DIAMOND: 252 | return "DIAMOND"; 253 | } 254 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class tier"); 255 | return NULL; 256 | } 257 | 258 | std::optional str_to_tier(const std::string& tir) noexcept { 259 | constexpr std::array tiers {"IRON", "BRONZE", "SILVER", "GOLD", "PLATINUM", "EMERALD", "DIAMOND"}; 260 | int tier_index = 0; 261 | for (auto& tirs : tiers) { 262 | if (str_equals_upper(tir, tirs)) { 263 | return std::optional{static_cast(tier_index)}; 264 | } 265 | tier_index++; 266 | } 267 | return std::nullopt; 268 | } 269 | 270 | /** 271 | * wrapper of str_to_tier 272 | * @param string to check if valid tier 273 | * @return true if valid tier string false otherwise 274 | */ 275 | bool valid_tier(const std::string& tir) noexcept { 276 | std::optional ti = str_to_division(tir); 277 | return ti.has_value(); 278 | } 279 | 280 | std::string queue_to_str(const queue que) noexcept { 281 | switch (que) { 282 | case queue::RANKED_SOLO_5x5: 283 | return "RANKED_SOLO_5x5"; 284 | case queue::RANKED_FLEX_SR: 285 | return "RANKED_FLEX_SR"; 286 | case queue::RANKED_FLEX_TT: 287 | return "RANKED_FLEX_TT"; 288 | } 289 | rcp_assert(false, "Non-exhaustive switch. Check if function if upto date with enum class queue"); 290 | return NULL; 291 | } 292 | 293 | std::optional str_to_queue(const std::string& que) noexcept { 294 | constexpr std::array queues {"RANKED_SOLO_5x5", "RANKED_FLEX_SR", "RANKED_FLEX_TT"}; 295 | int queue_index = 0; 296 | for (auto& ques : queues) { 297 | if (str_equals_upper(que, ques)) { 298 | return std::optional{static_cast(queue_index)}; 299 | } 300 | queue_index++; 301 | } 302 | return std::nullopt; 303 | } 304 | 305 | /** 306 | * wrapper of str_to_tier 307 | * @param string to check if valid tier 308 | * @return true if valid tier string false otherwise 309 | */ 310 | bool valid_queue(const std::string& queu) noexcept { 311 | std::optional que = str_to_queue(queu); 312 | return que.has_value(); 313 | } 314 | 315 | /** 316 | * Converts a string to a routing struct. 317 | * Importantly this function throws an invalid arguement error if the arguement is invalid. 318 | * Users should never be able to send a request to an invalid routing value as this could compromise 319 | * their api key. 320 | * @param string to convert to toute. 321 | * @returns routing 322 | */ 323 | routing str_to_routing(const std::string& route) { 324 | std::optional pform = str_to_platform(route); 325 | if (pform.has_value()) { 326 | return routing(pform.value()); 327 | } 328 | std::optional region = str_to_regional(route); 329 | if (region.has_value()) { 330 | return routing(region.value()); 331 | } 332 | std::optional vform = str_to_val_platform(route); 333 | if (vform.has_value()) { 334 | return routing(vform.value()); 335 | } 336 | // Sending get requests to an unexpected routing address is dangerous. 337 | throw std::invalid_argument("Invalid routing value given: " + route); 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /test/url_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/riot-cpp/query/url.h" 4 | 5 | namespace riotcpp { 6 | namespace url { 7 | 8 | TEST_CASE("Percent Encode Check") { 9 | // Techincally all character can be percent encoded 10 | // however we should at least leave Alpha-numerics 11 | // plain. 12 | 13 | SECTION("Alpha-numeric") { 14 | REQUIRE_FALSE(need_percent_encode('a')); 15 | REQUIRE_FALSE(need_percent_encode('A')); 16 | REQUIRE_FALSE(need_percent_encode('z')); 17 | REQUIRE_FALSE(need_percent_encode('Z')); 18 | REQUIRE_FALSE(need_percent_encode('0')); 19 | REQUIRE_FALSE(need_percent_encode('9')); 20 | REQUIRE_FALSE(need_percent_encode('k')); 21 | REQUIRE_FALSE(need_percent_encode('l')); 22 | REQUIRE_FALSE(need_percent_encode('s')); 23 | 24 | REQUIRE_FALSE(need_percent_encode('1')); 25 | REQUIRE_FALSE(need_percent_encode('2')); 26 | REQUIRE_FALSE(need_percent_encode('3')); 27 | REQUIRE_FALSE(need_percent_encode('4')); 28 | REQUIRE_FALSE(need_percent_encode('5')); 29 | REQUIRE_FALSE(need_percent_encode('6')); 30 | REQUIRE_FALSE(need_percent_encode('7')); 31 | REQUIRE_FALSE(need_percent_encode('8')); 32 | } 33 | 34 | SECTION("RESERVED") { 35 | // * ' ( ) ; : @ & = + $ , / ? % # [ ] 36 | REQUIRE(need_percent_encode('*')); 37 | REQUIRE(need_percent_encode('\'')); 38 | REQUIRE(need_percent_encode('(')); 39 | REQUIRE(need_percent_encode(')')); 40 | REQUIRE(need_percent_encode(';')); 41 | REQUIRE(need_percent_encode(':')); 42 | REQUIRE(need_percent_encode('@')); 43 | REQUIRE(need_percent_encode('&')); 44 | REQUIRE(need_percent_encode('=')); 45 | REQUIRE(need_percent_encode('+')); 46 | REQUIRE(need_percent_encode('$')); 47 | REQUIRE(need_percent_encode(',')); 48 | REQUIRE(need_percent_encode('/')); 49 | REQUIRE(need_percent_encode('?')); 50 | REQUIRE(need_percent_encode('%')); 51 | REQUIRE(need_percent_encode('#')); 52 | REQUIRE(need_percent_encode('[')); 53 | REQUIRE(need_percent_encode(']')); 54 | REQUIRE(need_percent_encode(' ')); 55 | } 56 | 57 | SECTION("UTF-8 Unit") { 58 | REQUIRE(need_percent_encode((char) 0x8A)); 59 | REQUIRE(need_percent_encode((char) 0xAA)); 60 | REQUIRE(need_percent_encode((char) 0x9A)); 61 | REQUIRE(need_percent_encode((char) 0xB3)); 62 | REQUIRE(need_percent_encode((char) 0xD8)); 63 | REQUIRE(need_percent_encode((char) 0xBE)); 64 | REQUIRE(need_percent_encode((char) 0x91)); 65 | REQUIRE(need_percent_encode((char) 0xC4)); 66 | REQUIRE(need_percent_encode((char) 0xA8)); 67 | REQUIRE(need_percent_encode((char) 0x8A)); 68 | REQUIRE(need_percent_encode((char) 0xD9)); 69 | REQUIRE(need_percent_encode((char) 0x93)); 70 | } 71 | } 72 | 73 | TEST_CASE("Length of URL Fragment") { 74 | 75 | SECTION("NO RESERVED") { 76 | REQUIRE(length_url_frag("1234") == 4); 77 | REQUIRE(length_url_frag("123456789") == 9); 78 | REQUIRE(length_url_frag("HelloWorld") == 10); 79 | REQUIRE(length_url_frag("Testing") == 7); 80 | REQUIRE(length_url_frag("namesd3456") == 10); 81 | } 82 | 83 | SECTION("CONTAINS RESERVED") { 84 | REQUIRE(length_url_frag("Hello World") == 13); 85 | REQUIRE(length_url_frag("He%lo Faker") == 15); 86 | REQUIRE(length_url_frag("Hello )[]ld") == 19); 87 | REQUIRE(length_url_frag("%") == 3); 88 | REQUIRE(length_url_frag("[()]") == 12); 89 | REQUIRE(length_url_frag("//%%a%%//") == 25); 90 | } 91 | 92 | SECTION("UTF-8") { 93 | // Hello World! 94 | REQUIRE(length_url_frag("你好世界") == 36); 95 | REQUIRE(length_url_frag("안녕하세요 세계") == 66); 96 | REQUIRE(length_url_frag("£¥§©") == 24); 97 | 98 | REQUIRE(length_url_frag("£¥§©안녕하세요 세계") == 66 + 24); 99 | REQUIRE(length_url_frag("£¥§©你好世界") == 24 + 36); 100 | REQUIRE(length_url_frag("£¥§©안녕하세요 세계你好世界") == 24 + 66 + 36); 101 | } 102 | } 103 | 104 | static inline bool percent_encode_equal(const char expected[3], const char received[3]) { 105 | return expected[0] == received[0] 106 | && expected[1] == received[1] 107 | && expected[2] == received[2]; 108 | } 109 | 110 | TEST_CASE("Char to Percent Hex") { 111 | char temp_buff[4]; temp_buff[3] = '\0'; 112 | char expected[4]; expected[3] = '\0'; expected[0] = '%'; 113 | REQUIRE(percent_encode_equal); 114 | expected[1] = 'A'; expected[2] = '4'; 115 | to_hex((char) 0xA4, temp_buff); 116 | REQUIRE(percent_encode_equal(expected, temp_buff)); 117 | expected[1] = 'D'; expected[2] = '1'; 118 | to_hex((char) 0xD1, temp_buff); 119 | REQUIRE(percent_encode_equal(expected, temp_buff)); 120 | expected[1] = '8'; expected[2] = '4'; 121 | to_hex((char) 0x84, temp_buff); 122 | REQUIRE(percent_encode_equal(expected, temp_buff)); 123 | expected[1] = '1'; expected[2] = 'B'; 124 | to_hex((char) 0x1B, temp_buff); 125 | REQUIRE(percent_encode_equal(expected, temp_buff)); 126 | expected[1] = '5'; expected[2] = 'E'; 127 | to_hex((char) 0x5E, temp_buff); 128 | REQUIRE(percent_encode_equal(expected, temp_buff)); 129 | expected[1] = '9'; expected[2] = 'F'; 130 | to_hex((char) 0x9F, temp_buff); 131 | REQUIRE(percent_encode_equal(expected, temp_buff)); 132 | } 133 | 134 | bool equal_char_buff(const u_int buff_size, const char* expected, const char* received) { 135 | bool eq = true; 136 | for (int i = 0; i < buff_size; i++) { 137 | eq &= expected[i] == received[i]; 138 | } 139 | return eq; 140 | } 141 | 142 | TEST_CASE("Encode Write") { 143 | // Testing for encode_write 144 | 145 | SECTION("NO ENCODE") { 146 | std::vector words = {"hello", "world", "no", "encode"}; 147 | const std::vector write_len = {5, 10, 12, 18}; 148 | char buff[18]; 149 | const char expected[19] = "helloworldnoencode"; 150 | 151 | char* next_write = encode_write(words[0], buff); 152 | REQUIRE(equal_char_buff(write_len[0], expected, buff)); 153 | next_write = encode_write(words[1], next_write); 154 | REQUIRE(equal_char_buff(write_len[1], expected, buff)); 155 | next_write = encode_write(words[2], next_write); 156 | REQUIRE(equal_char_buff(write_len[2], expected, buff)); 157 | next_write = encode_write(words[3], next_write); 158 | REQUIRE(equal_char_buff(write_len[3], expected, buff)); 159 | } 160 | 161 | SECTION("WITH ENCODED") { 162 | std::vector words = {"hello ", "world ", "no ", "encode"}; 163 | const std::vector write_len = {8, 16, 21, 27}; 164 | char buff[27]; 165 | const char expected[28] = "hello%20world%20no%20encode"; 166 | 167 | char* next_write = encode_write(words[0], buff); 168 | REQUIRE(equal_char_buff(write_len[0], expected, buff)); 169 | next_write = encode_write(words[1], next_write); 170 | REQUIRE(equal_char_buff(write_len[1], expected, buff)); 171 | next_write = encode_write(words[2], next_write); 172 | REQUIRE(equal_char_buff(write_len[2], expected, buff)); 173 | next_write = encode_write(words[3], next_write); 174 | REQUIRE(equal_char_buff(write_len[3], expected, buff)); 175 | } 176 | } 177 | 178 | TEST_CASE("Number of Digits") { 179 | // Testing for ndigits 180 | 181 | REQUIRE(ndigits(12345) == 5); 182 | REQUIRE(ndigits(9375) == 4); 183 | REQUIRE(ndigits(1) == 1); 184 | REQUIRE(ndigits(98284765) == 8); 185 | REQUIRE(ndigits(2301) == 4); 186 | REQUIRE(ndigits(8762345) == 7); 187 | } 188 | 189 | TEST_CASE("Optional Arguments Length Tests") { 190 | // Test getSize for optional argument structs 191 | 192 | SECTION("Optional Count") { 193 | // Expected string &count=1 or ?count=1 194 | opt_count opt_arg = {.count = 1}; 195 | REQUIRE(getSize(opt_arg) == 8); 196 | opt_arg = {.count = 13}; 197 | REQUIRE(getSize(opt_arg) == 9); 198 | opt_arg = {.count = 1234}; 199 | REQUIRE(getSize(opt_arg) == 11); 200 | opt_arg = {.count = 12345}; 201 | REQUIRE(getSize(opt_arg) == 12); 202 | opt_arg = {}; 203 | REQUIRE(getSize(opt_arg) == 0); 204 | } 205 | SECTION("Optional Page") { 206 | // Expected string &page=1 207 | opt_page opt_arg = {.page = 1}; 208 | REQUIRE(getSize(opt_arg) == 7); 209 | opt_arg = {.page = 13}; 210 | REQUIRE(getSize(opt_arg) == 8); 211 | opt_arg = {.page = 1234}; 212 | REQUIRE(getSize(opt_arg) == 10); 213 | opt_arg = {.page = 9876543}; 214 | REQUIRE(getSize(opt_arg) == 13); 215 | opt_arg = {.page = 132}; 216 | REQUIRE(getSize(opt_arg) == 9); 217 | opt_arg = {}; 218 | REQUIRE(getSize(opt_arg) == 0); 219 | } 220 | SECTION("Optional Limit") { 221 | // Expected string &limit=1 222 | opt_limit opt_arg = {.limit = 2}; 223 | REQUIRE(getSize(opt_arg) == 8); 224 | opt_arg = {.limit = 23}; 225 | REQUIRE(getSize(opt_arg) == 9); 226 | opt_arg = {.limit = 234}; 227 | REQUIRE(getSize(opt_arg) == 10); 228 | opt_arg = {.limit = 12345}; 229 | REQUIRE(getSize(opt_arg) == 12); 230 | opt_arg = {}; 231 | REQUIRE(getSize(opt_arg) == 0); 232 | } 233 | SECTION("Optional Match History") { 234 | // Expected string ?startTime=1&endTime=1&queue="string"&type="string"&start=1&count=1 235 | opt_match_history opt_arg = {}; 236 | REQUIRE(getSize(opt_arg) == 0); 237 | opt_arg = {.startTime = 1}; 238 | REQUIRE(getSize(opt_arg) == 12); 239 | opt_arg = {.endTime = 1}; 240 | REQUIRE(getSize(opt_arg) == 10); 241 | opt_arg = {.queue = "Ranked_Solo_5x5"}; 242 | REQUIRE(getSize(opt_arg) == 26); 243 | opt_arg = {.type = "type"}; 244 | REQUIRE(getSize(opt_arg) == 10); 245 | opt_arg = {.start = 1}; 246 | REQUIRE(getSize(opt_arg) == 8); 247 | opt_arg = {.count = 12}; 248 | REQUIRE(getSize(opt_arg) == 9); 249 | opt_arg = {.startTime = 1, .endTime = 12}; 250 | REQUIRE(getSize(opt_arg) == 23); 251 | opt_arg = {.endTime = 23, .queue = "Ranked_Solo_5x5", .count = 12}; 252 | REQUIRE(getSize(opt_arg) == 46); 253 | opt_arg = {.endTime = 23, .queue = "Ranked_Solo_5x5", .start = 1, .count = 12}; 254 | REQUIRE(getSize(opt_arg) == 54); 255 | } 256 | SECTION("Optional Queue Page") { 257 | // Expected string ?queue="string"&page=1 258 | opt_queue_page opt_arg = {}; 259 | REQUIRE(getSize(opt_arg) == 0); 260 | opt_arg = {.queue = "Ranked_Solo_5x5"}; 261 | REQUIRE(getSize(opt_arg) == 26); 262 | opt_arg = {.page = 1}; 263 | REQUIRE(getSize(opt_arg) == 7); 264 | opt_arg = { .queue = "Ranked_Solo_5x5", .page = 1}; 265 | REQUIRE(getSize(opt_arg) == 33); 266 | } 267 | SECTION("Optional Start Size") { 268 | // Expected string ?size=1&startIndex=1 269 | opt_start_size opt_arg = {}; 270 | REQUIRE(getSize(opt_arg) == 0); 271 | opt_arg = {.size = 1}; 272 | REQUIRE(getSize(opt_arg) == 7); 273 | opt_arg = {.startIndex = 1}; 274 | REQUIRE(getSize(opt_arg) == 13); 275 | opt_arg = {.size = 12, .startIndex = 13}; 276 | REQUIRE(getSize(opt_arg) == 22); 277 | } 278 | SECTION("Optional Locale") { 279 | // Expected string ?locale="string" 280 | opt_locale opt_arg = {}; 281 | REQUIRE(getSize(opt_arg) == 0); 282 | opt_arg = {.locale = "locale"}; 283 | REQUIRE(getSize(opt_arg) == 14); 284 | opt_arg = {.locale = "loc ale"}; 285 | REQUIRE(getSize(opt_arg) == 17); 286 | } 287 | } 288 | 289 | TEST_CASE("Variadic Url Fragments") { 290 | // Testing length function 291 | REQUIRE(length("par1", "par2", "par3") == 12); 292 | REQUIRE(length("par1", "par3", 1, 12) == 11); 293 | REQUIRE(length("%encode", 1234, "*encode") == 22); 294 | REQUIRE(length(1, 12, 123, 1234) == 10); 295 | } 296 | 297 | TEST_CASE("Copy int to buffer") { 298 | // equal_char_buff 299 | char expected[] = "123456789"; 300 | char buffer[10]; 301 | char* next_write = write_int(buffer, 1); 302 | REQUIRE(equal_char_buff(1, expected, buffer)); 303 | next_write = write_int(next_write, 23); 304 | REQUIRE(equal_char_buff(1, expected, buffer)); 305 | next_write = write_int(next_write, 456); 306 | REQUIRE(equal_char_buff(1, expected, buffer)); 307 | next_write = write_int(next_write, 789); 308 | REQUIRE(equal_char_buff(1, expected, buffer)); 309 | } 310 | 311 | TEST_CASE("URL Construction") { 312 | std::string routing = "americas"; 313 | std::string base_url = "/baseurlhere"; 314 | std::array url_frags = {"/frag1/", "/frag2/", "/frag3/"}; 315 | std::string expected_url = "https://americas.api.riotgames.com/baseurlhere/frag1/par%201/frag2/pa%20r2/frag3/p%20ar3"; 316 | std::unique_ptr url = construct_url(routing, base_url, 21, url_frags, "par 1", "pa r2", "p ar3"); 317 | std::string received = std::string(url.get()); 318 | REQUIRE(received == expected_url); 319 | expected_url += "/trailing"; 320 | url = construct_url(routing, base_url, "/trailing", 21, url_frags, "par 1", "pa r2", "p ar3"); 321 | received = std::string(url.get()); 322 | REQUIRE(received == expected_url); 323 | expected_url = std::string("https://americas.api.riotgames.com/baseurlhere/frag1/12/frag2/123/frag3/1234/trailing"); 324 | url = construct_url(routing, base_url, "/trailing", 21, url_frags, 12, 123, 1234); 325 | received = std::string(url.get()); 326 | REQUIRE(received == expected_url); 327 | opt_match_history opt_arg = {.startTime = 1, .endTime = 12, .queue = "Ranked_Solo_5x5", .type = "type", .count = 10}; 328 | expected_url = std::string("https://americas.api.riotgames.com/baseurlhere/frag1/12/frag2/123/frag3/1234/trailing?startTime=1&endTime=12&queue=Ranked%5FSolo%5F5x5&type=type&count=10"); 329 | url = construct_url(routing, base_url, opt_arg, "/trailing", 21, url_frags, 12, 123, 1234); 330 | received = std::string(url.get()); 331 | REQUIRE(received == expected_url); 332 | opt_arg = {.startTime = 1, .endTime = 12, .queue = "Ranked_Solo_5x5", .type = "type", .count = 10}; 333 | expected_url = std::string("https://americas.api.riotgames.com/baseurlhere/frag1/12/frag2/123/frag3/1234?startTime=1&endTime=12&queue=Ranked%5FSolo%5F5x5&type=type&count=10"); 334 | url = construct_url(routing, base_url, opt_arg, 21, url_frags, 12, 123, 1234); 335 | received = std::string(url.get()); 336 | REQUIRE(received == expected_url); 337 | }; 338 | 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/riot-cpp/query/endpoints.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "../types/args.h" 18 | #include "url.h" 19 | 20 | namespace riotcpp { 21 | namespace query { 22 | 23 | typedef struct RiotHeader { // default to extremely slow rate limit successful requests will overwrite these 24 | char date[32]; // users with invalid api keys will only be able to send a request every 2 minutes 25 | char app_limit[64] = "1:120"; 26 | char app_limit_count[64] = "1:120"; 27 | char method_limit[64] = "1:120"; 28 | char method_limit_count[64] = "1:120"; 29 | char retry_after[4]; 30 | } RiotHeader; 31 | 32 | typedef struct query { 33 | std::string method_key; 34 | args::routing route; 35 | std::unique_ptr url; 36 | std::time_t send_time = 0; 37 | std::unique_ptr> response_content; 38 | RiotHeader response_header; 39 | int last_response = -2; 40 | int server_error_count; 41 | } query; 42 | 43 | using json_text = std::vector; 44 | using query_fp = std::function(std::shared_ptr)>*; 45 | 46 | template 47 | inline int len_url_frags(const std::array& url_frags) { 48 | return std::accumulate( 49 | url_frags.begin(), 50 | url_frags.end(), 51 | 0, 52 | [](const int acc, const std::string& str){return acc + str.size();}); 53 | } 54 | 55 | 56 | template 57 | class EndpointMethod { 58 | private: 59 | // String will be held by the endpoint. 60 | const std::string& url_base_; 61 | const std::array url_fragments_; 62 | const int frag_len_; 63 | const std::string additional_frag_; 64 | 65 | const std::string method_key_; 66 | const query_fp get_; 67 | 68 | bool validate_keywords(const std::pair& opt_args...); 69 | 70 | std::unique_ptr send_(const std::string& routing, std::unique_ptr url) const; 71 | 72 | public: 73 | EndpointMethod( 74 | const query_fp get, 75 | const std::string& url_base, 76 | const std::string& method_key, 77 | const std::array url_fragments, 78 | const std::string& additional_frag="" 79 | ) : get_(get), 80 | url_base_(url_base), 81 | method_key_(method_key), 82 | url_fragments_(url_fragments), 83 | additional_frag_(additional_frag), 84 | frag_len_(len_url_frags(url_fragments)) {}; 85 | 86 | std::unique_ptr operator()(const std::string& routing, const Args&...args, const T& opt_arg={}) const { 87 | std::unique_ptr url; 88 | if constexpr (std::is_same::value) { 89 | url = url::construct_url(routing, this->url_base_, this->additional_frag_, this->frag_len_, this->url_fragments_, args...); 90 | } else { 91 | url = url::construct_url(routing, this->url_base_, opt_arg, this->additional_frag_, this->frag_len_, this->url_fragments_, args...); 92 | } 93 | return this->send_(routing, std::move(url)); 94 | } 95 | }; 96 | 97 | template 98 | std::unique_ptr EndpointMethod::send_(const std::string& routing, std::unique_ptr url) const { 99 | std::shared_ptr new_request = std::make_shared(this->method_key_,args::str_to_routing(routing), std::move(url)); 100 | new_request->response_content = std::make_unique(); 101 | return (*this->get_)(new_request); 102 | } 103 | 104 | typedef struct Endpoint { 105 | const std::string url_base_; 106 | } Endpoint; 107 | 108 | typedef struct Account_v1 : public Endpoint { 109 | Account_v1(query_fp get) 110 | : Endpoint("/riot/account/v1"), 111 | by_puuid (get, url_base_, "Account-v1-by-puuid", {"/accounts/by-puuid/"}), 112 | by_riot_id (get, url_base_, "Acccount-v1-by-riot-id", {"/accounts/by-riot-id/", "/"}), 113 | by_game_by_puuid(get, url_base_, "Account-v1-by-game-by-puuid", {"/active-shards/by-game/", "/by-puuid/"}) {}; 114 | 115 | const EndpointMethod by_puuid; 116 | const EndpointMethod by_riot_id; 117 | const EndpointMethod by_game_by_puuid; 118 | } Account_v1; 119 | 120 | typedef struct Champion_Mastery_v4 : public Endpoint { 121 | Champion_Mastery_v4(query_fp get) 122 | : Endpoint("/lol/champion-mastery/v4"), 123 | by_puuid (get, url_base_, "Champion-Mastery-v4-by-puuid", {"/champion-masteries/by-puuid/"}), 124 | by_puuid_by_champion(get, url_base_, "Champion-Mastery-v4-by-puuid-by-champion", {"/champion-masteries/by-puuid/", "/by-champion/"}), 125 | by_puuid_top (get, url_base_, "Champion-Mastery-v4-by-puuid-top", {"/champion-masteries/by-puuid/"}, "/top"), 126 | scores_by_puuid (get, url_base_, "Champion-Mastery-v4-scores-by-puuid", {"/scores/by-puuid/"}) {} 127 | 128 | const EndpointMethod by_puuid; 129 | const EndpointMethod by_puuid_by_champion; 130 | const EndpointMethod by_puuid_top; 131 | const EndpointMethod scores_by_puuid; 132 | } Champion_Mastery_v4; 133 | 134 | typedef struct Champion_v3 : public Endpoint { 135 | Champion_v3(query_fp get) 136 | : Endpoint("/lol/platform/v3"), 137 | champion_rotations(get, url_base_, "Champion-v3=champion-rotations", {}, "/champion-rotations") {}; 138 | 139 | const EndpointMethod champion_rotations; 140 | } Champion_v3; 141 | 142 | typedef struct Clash_v1 : public Endpoint{ 143 | Clash_v1(query_fp get) 144 | : Endpoint("/lol/clash/v1"), 145 | by_summoner_id (get, url_base_, "Clash-v1-by-summoner-id", {"/players/by-summoner/"}), 146 | teams (get, url_base_, "Clash-v1-teams", {"/teams/"}), 147 | tournaments (get, url_base_, "Clash-v1-tournaments" , {}, "/tournaments"), 148 | tournaments_by_team(get, url_base_, "Clash-v1-by-team" , {"/tournaments/by-team/"}), 149 | tournaments_by_id (get, url_base_, "Clash-v1-tournament-by-id", {"/tournaments/"}) {} 150 | 151 | const EndpointMethod by_summoner_id; 152 | const EndpointMethod teams; 153 | const EndpointMethod tournaments; 154 | const EndpointMethod tournaments_by_team; 155 | const EndpointMethod tournaments_by_id; 156 | } Clash_v1; 157 | 158 | typedef struct League_exp_v4 : public Endpoint { 159 | League_exp_v4(query_fp get) 160 | : Endpoint("/lol/league-exp/v4"), 161 | entries(get, url_base_, "League-exp-v4-entries", {"/entries/", "/", "/"}) {}; 162 | 163 | const EndpointMethod entries; 164 | } League_exp_v4; 165 | 166 | typedef struct League_v4 : public Endpoint { 167 | League_v4(query_fp get) 168 | : Endpoint("/lol/league/v4"), 169 | challenger (get, url_base_, "League-v4-challenger", {"/challengerleagues/by-queue/"}), 170 | by_summoner_id(get, url_base_, "League-v4-by-summoner-id", {"/entries/by-summoner/"}), 171 | entries (get, url_base_, "League-v4-entries", {"/entries/", "/", "/"}), 172 | grandmaster (get, url_base_, "League-v4-grandmaster", {"/grandmasterleagues/by-queue/"}), 173 | by_league_id (get, url_base_, "League-v4-by-league-id", {"/leagues/"}), 174 | master (get, url_base_, "League-v4-master", {"/masterleagues/by-queue/"}) {} 175 | 176 | const EndpointMethod challenger; 177 | const EndpointMethod by_summoner_id; 178 | const EndpointMethod entries; 179 | const EndpointMethod grandmaster; 180 | const EndpointMethod by_league_id; 181 | const EndpointMethod master; 182 | } League_v4; 183 | 184 | typedef struct Lol_Challenges_v1 : public Endpoint { 185 | Lol_Challenges_v1(query_fp get) 186 | : Endpoint("/lol/challenges/v1"), 187 | config (get, url_base_, "Lol-Challenges-config", {}, "/challenges/config"), 188 | percentiles (get, url_base_, "Lol-Challenges-percentilejs", {}, "/challenges/percentiles"), 189 | config_by_id (get, url_base_, "Lol-Challenges-config-by-id", {"/challenges/"}, "/config"), 190 | leaderboards_by_id_by_level(get, url_base_, "Lol-Challenges-leaderboards-by-id-by-level", {"/challenges/", "/leaderboards/by-level/"}), 191 | percentiles_by_id (get, url_base_, "Lol-Challenges-percentiles-by-id", {"/challenges/"}, "/percentiles"), 192 | player_data_by_puuid (get, url_base_, "Lol-Challenges-player-data-by-puuid", {"/player-data/"}) {} 193 | 194 | const EndpointMethod config; 195 | const EndpointMethod percentiles; 196 | const EndpointMethod config_by_id; 197 | const EndpointMethod leaderboards_by_id_by_level; 198 | const EndpointMethod percentiles_by_id; 199 | const EndpointMethod player_data_by_puuid; 200 | } Lol_Challenges_v1; 201 | 202 | typedef struct Lol_Status_v4 : public Endpoint { 203 | Lol_Status_v4(query_fp get) 204 | : Endpoint("/lol/status/v4"), 205 | status(get, url_base_, "Lol-Status-v4-status", {}, "/platform-data") {}; 206 | 207 | const EndpointMethod status; 208 | } Lol_Status_v4; 209 | 210 | typedef struct Lor_Match_v1 : public Endpoint { 211 | Lor_Match_v1(query_fp get) 212 | : Endpoint("/lor/match/v1"), 213 | by_puuid (get, url_base_, "Lor-Match-v1-by-puuid", {"/matches/by-puuid/"}, "/ids"), 214 | by_match_id(get, url_base_, "Lor-Match-v1-by-match-id", {"/matches/"}) {}; 215 | 216 | const EndpointMethod by_puuid; 217 | const EndpointMethod by_match_id; 218 | } Lor_Match_v1; 219 | 220 | typedef struct Lor_Ranked_v1 : public Endpoint { 221 | Lor_Ranked_v1(query_fp get) 222 | : Endpoint("/lor/ranked/v1"), 223 | leaderboards(get, url_base_, "Lor-Ranked-v1-leaderboards", {}, "/leaderboards") {}; 224 | 225 | const EndpointMethod leaderboards; 226 | } Lor_Ranked_v1; 227 | 228 | typedef struct Lor_Status_v1 : public Endpoint { 229 | Lor_Status_v1(query_fp get) 230 | : Endpoint("/lor/status/v1"), 231 | status(get, url_base_, "Lor-Status-v1", {}, "/platform-data"){}; 232 | 233 | const EndpointMethod status; 234 | } Lor_Status_v1; 235 | 236 | typedef struct Match_v5 : public Endpoint { 237 | Match_v5(query_fp get) 238 | : Endpoint("/lol/match/v5"), 239 | by_puuid (get, url_base_, "Match-v5-by-puuid", {"/matches/by-puuid/"}, "/ids"), 240 | by_match_id (get, url_base_, "Match-v5-by-match-id", {"/matches/"}), 241 | timeline_by_match_id(get, url_base_, "Match-v5-timeline-by-match-id", {"/matches/"}, "/timeline") {}; 242 | 243 | const EndpointMethod by_puuid; 244 | const EndpointMethod by_match_id; 245 | const EndpointMethod timeline_by_match_id; 246 | } Match_v5; 247 | 248 | typedef struct Spectator_Tft_v5 : public Endpoint { 249 | Spectator_Tft_v5(query_fp get) 250 | : Endpoint("/lol/spectator/tft/v5"), 251 | by_puuid(get, url_base_, "Spectator-Tft-v5-by-puuid", {"/active-games/by-puuid/"}), 252 | featured(get, url_base_, "Spectator-Tft-v5=featured", {}, "/featured-games") {}; 253 | 254 | const EndpointMethod by_puuid; 255 | const EndpointMethod featured; 256 | } Spectator_Tft_v5; 257 | 258 | typedef struct Spectator_v5 : public Endpoint { 259 | Spectator_v5(query_fp get) 260 | : Endpoint("/lol/spectator/v5"), 261 | by_summoner(get, url_base_, "Spectator-v5-by-summoner-id", {"/active-games/by-summoner/"}), 262 | featured (get, url_base_, "Spectator-v5-featured", {}, "/featured-games") {}; 263 | 264 | const EndpointMethod by_summoner; 265 | const EndpointMethod featured; 266 | } Spectator_v5; 267 | 268 | typedef struct Summoner_v4 : public Endpoint { 269 | Summoner_v4(query_fp get) 270 | : Endpoint("/lol/summoner/v4"), 271 | by_account_id (get, url_base_, "Summoner-v4-by-account-id", {"/summoners/by-account/"}), 272 | by_puuid (get, url_base_, "Summoner-v4-by-puuid", {"/summoners/by-puuid/"}), 273 | by_summoner_id(get, url_base_, "Summoner-v4-by-summoner-id", {"/summoners/"}) {}; 274 | 275 | const EndpointMethod by_account_id; 276 | const EndpointMethod by_puuid; 277 | const EndpointMethod by_summoner_id; 278 | } Summoner_v4; 279 | 280 | typedef struct Tft_League_v1 : public Endpoint { 281 | Tft_League_v1(query_fp get) 282 | : Endpoint("/tft/league/v1"), 283 | challenger (get, url_base_, "Tft-League-v1-challenger", {}, "/challenger"), 284 | by_summoner (get, url_base_, "Tft-League-v1-by-summoner", {"/entries/by-summoner/"}), 285 | entries (get, url_base_, "Tft-League-v1-entries", {"/entries/", "/"}), 286 | grandmaster (get, url_base_, "Tft-League-v1-grandmaster", {}, "/grandmaster"), 287 | by_league_id(get, url_base_, "Tft-League-v1-by-league-id", {"/leagues/"}), 288 | master (get, url_base_, "Tft-League-v1-master", {}, "/master"), 289 | top_by_queue(get, url_base_, "Tft-League-v1-top-by-queue", {"/rated-ladders/"}, "/top") {} 290 | 291 | const EndpointMethod challenger; 292 | const EndpointMethod by_summoner; 293 | const EndpointMethod entries; 294 | const EndpointMethod grandmaster; 295 | const EndpointMethod by_league_id; 296 | const EndpointMethod master; 297 | const EndpointMethod top_by_queue; 298 | } Tft_League_v1; 299 | 300 | typedef struct Tft_Match_v1 : public Endpoint { 301 | Tft_Match_v1(query_fp get) 302 | : Endpoint("/tft/match/v1"), 303 | by_puuid( get, url_base_, "Tft-Match-v1-by-puuid", {"/matches/by-puuid/"}, "/ids"), 304 | by_match_id(get, url_base_, "Tft-Match-v1-by-match-id", {"/matches/"}) {}; 305 | 306 | const EndpointMethod by_puuid; 307 | const EndpointMethod by_match_id; 308 | } Tft_Match_v1; 309 | 310 | typedef struct Tft_Status_v1 : public Endpoint { 311 | Tft_Status_v1(query_fp get) 312 | : Endpoint("/tft/status/v1"), 313 | status(get, url_base_, "Tft-Status-v1-status", {}, "/platform-data") {}; 314 | 315 | const EndpointMethod status; 316 | } Tft_Status_v1; 317 | 318 | typedef struct Tft_Summoner_v1 : public Endpoint { 319 | Tft_Summoner_v1(query_fp get) 320 | : Endpoint("/tft/summoner/v1"), 321 | by_account_id (get, url_base_, "Tft-Summoner-v1-by-account-id", {"/summoners/by-account/"}), 322 | by_puuid (get, url_base_, "Tft-Summoner-v1-by-puuid", {"/summoners/by-puuid/"}), 323 | by_summoner_id(get, url_base_, "Tft-Summoner-v1-by-summoner-id", {"/summoners/"}) {}; 324 | 325 | const EndpointMethod by_account_id; 326 | const EndpointMethod by_puuid; 327 | const EndpointMethod by_summoner_id; 328 | } Tft_Summoner_v1; 329 | 330 | typedef struct Val_Content_v1 : public Endpoint { 331 | Val_Content_v1(query_fp get) 332 | : Endpoint("/val/content/v1"), 333 | contents(get, url_base_, "Val-Content-v1-contents", {}, "/contents") {}; 334 | 335 | const EndpointMethod contents; 336 | } Val_Content_v1; 337 | 338 | typedef struct Val_Match_v1 : public Endpoint { 339 | Val_Match_v1(query_fp get) 340 | : Endpoint("/val/match/v1"), 341 | by_match_id (get, url_base_, "Val-Match-v1-by-match-id", {"/matches/"}), 342 | by_puuid (get, url_base_, "Val-Match-v1-by-puuid", {"/matches/by-puuid/"}), 343 | recent_by_queue(get, url_base_, "Val-Match-v1-recent-by-queue", {"/recent-matches/by-queue/"}) {}; 344 | 345 | const EndpointMethod by_match_id; 346 | const EndpointMethod by_puuid; 347 | const EndpointMethod recent_by_queue; 348 | } Val_Match_v1; 349 | 350 | typedef struct Val_Ranked_v1 : public Endpoint { 351 | Val_Ranked_v1(query_fp get) 352 | : Endpoint("/val/ranked/v1"), 353 | leaderboards_by_act(get, url_base_, "Val-Ranked-v1-leaderbords-by-act", {"/leaderboards/by-act/"}) {}; 354 | 355 | const EndpointMethod leaderboards_by_act; 356 | } Val_Ranked_v1; 357 | 358 | typedef struct Val_Status_v1 : public Endpoint { 359 | Val_Status_v1(query_fp get) 360 | : Endpoint("/val/status/v1"), 361 | status(get, url_base_, "Val-Status-v1-status", {}, "/platform-data") {}; 362 | 363 | const EndpointMethod status; 364 | } Val_Status_v1; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /test/client_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../src/riot-cpp/client/client.h" 8 | #include "json.hpp" 9 | 10 | #define CONFIG "../../.api_keys/riot_config.json", "../test/log_file.txt", logging::LEVEL::DEBUG, true 11 | namespace riotcpp { 12 | using namespace client; 13 | 14 | static std::string ROUTING = "KR"; 15 | static std::string SUMMONER_ID; 16 | static std::string PUUID; 17 | static std::string ACCOUNT_ID; 18 | 19 | using json_ptr = std::unique_ptr>; 20 | using json = nlohmann::json; 21 | 22 | TEST_CASE( "ACCOUNT_V1 QUERIES") { 23 | std::cout << "TESTING ACCOUNT_V1 QUERIES" << '\n'; 24 | RiotApiClient test_client(CONFIG); 25 | 26 | std::vector region = {"AMERICAS", "ASIA", "EUROPE"}; 27 | std::string puuid; // puuid is key specific 28 | std::string gamename = "Hide on bush"; std::string tagline = "KR1"; 29 | std::string game = "val"; 30 | std::vector end_types = {"by-riot-id", "by-puuid", "by-game"}; 31 | 32 | std::string endpoint = "ACCOUNT-V1"; 33 | json doc; 34 | 35 | 36 | json_ptr result; 37 | 38 | for (int i = 0; i < 1; i++) { 39 | result = test_client.Account.by_riot_id(region[i], gamename, tagline); 40 | doc = json::parse(result->data()); 41 | REQUIRE(doc["gameName"] == gamename); 42 | REQUIRE(doc["tagLine"] == tagline); 43 | 44 | PUUID = doc["puuid"]; 45 | 46 | puuid = doc["puuid"]; 47 | 48 | result = test_client.Account.by_puuid(region[i], puuid); 49 | doc = json::parse(result->data()); 50 | REQUIRE(doc["puuid"] == puuid); 51 | 52 | result = test_client.Account.by_game_by_puuid(region[i], game, puuid); 53 | doc = json::parse(result->data()); 54 | REQUIRE(doc["puuid"] == puuid); 55 | } 56 | } 57 | 58 | // League of Legend Queries 59 | 60 | TEST_CASE( "LEAGUE_V4 QUERIES") { 61 | std::cout << "TESTING LEAGUE_V4 QUERIES" << '\n'; 62 | RiotApiClient test_client(CONFIG); 63 | 64 | std::string region = "na1"; 65 | std::vector queue = {"RANKED_SOLO_5x5", "RANKED_FLEX_SR"}; 66 | std::vector division = {"I", "II", "III", "IV"}; 67 | std::vector tier = {"DIAMOND", "PLATINUM", "GOLD", "SILVER", "BRONZE", "IRON"}; 68 | std::string summoner_id; 69 | std::string league_id; 70 | 71 | std::string endpoint = "LEAGUE-V4"; 72 | 73 | json_ptr result; 74 | json doc; 75 | 76 | SECTION("CHALLENGER, GRANDMASTER AND MASTER QUEUE") { 77 | std::cout << " CHALLENGER, GRANDMASTER AND MASTER" << '\n'; 78 | for (int i = 0; i < queue.size(); i++) { 79 | 80 | result = test_client.League.challenger(region, queue.at(i)); 81 | doc = json::parse(result->data()); 82 | REQUIRE(doc["tier"] == "CHALLENGER"); 83 | REQUIRE(doc["queue"] == queue[i]); 84 | 85 | result = test_client.League.grandmaster(region, queue.at(i)); 86 | doc = json::parse(result->data()); 87 | REQUIRE(doc["tier"] == "GRANDMASTER"); 88 | REQUIRE(doc["queue"] == queue[i]); 89 | 90 | result = test_client.League.master(region, queue.at(i)); 91 | doc = json::parse(result->data()); 92 | REQUIRE(doc["tier"] == "MASTER"); 93 | REQUIRE(doc["queue"] == queue[i]); 94 | } 95 | } 96 | 97 | result = test_client.League.master(region, queue.at(0)); 98 | doc = json::parse(result->data()); 99 | summoner_id = doc["entries"].at(0)["summonerId"]; 100 | league_id = doc["leagueId"]; 101 | 102 | SECTION("SPECIFIC QUEUE ") { 103 | std::cout << " SPECIFIC QUEUE " << '\n'; 104 | for (int qu = 0; qu < queue.size(); qu++) { 105 | for (int ti = 0; ti < tier.size(); ti++) { 106 | for (int div = 0; div < division.size(); div++) { 107 | 108 | result = test_client.League.entries(region, queue.at(qu), tier.at(ti), division.at(div)); 109 | doc = json::parse(result->data()); 110 | auto ref = doc.at(0); 111 | REQUIRE(ref["queueType"] == queue[qu]); 112 | REQUIRE(ref["tier"] == tier[ti]); 113 | REQUIRE(ref["rank"] == division[div]); 114 | } 115 | } 116 | } 117 | } 118 | 119 | SECTION("Testing Summoner ID and League ID") { 120 | std::cout << " SUMMONER ID AND LEAGUE ID " << '\n'; 121 | 122 | result = test_client.League.by_summoner_id(region, summoner_id); 123 | doc = json::parse(result->data()); 124 | REQUIRE(doc.at(0)["summonerId"] == summoner_id); 125 | 126 | result = test_client.League.by_league_id(region, league_id); 127 | doc = json::parse(result->data()); 128 | REQUIRE(doc["leagueId"] == league_id); 129 | } 130 | } 131 | 132 | TEST_CASE(" SUMMONER QUERIES ") { 133 | std::cout << "TESTING SUMMONER QUERIES " << '\n'; 134 | RiotApiClient test_client(CONFIG); 135 | 136 | std::string region = "kr"; 137 | std::string summoner_name = "Hide on bush"; 138 | 139 | json_ptr result; 140 | json doc; 141 | 142 | std::string endpoint = "SUMMONER-V4"; 143 | 144 | result = test_client.Summoner.by_puuid(region, PUUID); 145 | doc = json::parse(result->data()); 146 | REQUIRE(doc["puuid"] == PUUID); 147 | 148 | ACCOUNT_ID = doc["accountId"]; 149 | SUMMONER_ID = doc["id"]; 150 | 151 | result = test_client.Summoner.by_account_id(region, ACCOUNT_ID); 152 | doc = json::parse(result->data()); 153 | REQUIRE(doc["accountId"] == ACCOUNT_ID); 154 | 155 | result = test_client.Summoner.by_summoner_id(region, SUMMONER_ID); 156 | doc = json::parse(result->data()); 157 | REQUIRE(doc["id"] == SUMMONER_ID); 158 | 159 | } 160 | 161 | TEST_CASE( "MATCH QUERIES" ) { 162 | std::cout << "TESTING MATCH QUERIES" << '\n'; 163 | RiotApiClient test_client(CONFIG); 164 | 165 | std::string region = "ASIA"; 166 | std::string match_id = "KR_6279823690"; 167 | json_ptr result; 168 | json doc; 169 | 170 | std::string endpoint = "MATCH-V5"; 171 | result = test_client.Match.by_puuid(region, PUUID, {.type = "ranked"}); 172 | doc = json::parse(result->data()); 173 | match_id = doc.at(0); 174 | result = test_client.Match.by_match_id(region, match_id); 175 | doc = json::parse(result->data()); 176 | REQUIRE(doc["metadata"]["matchId"] == match_id); 177 | result = test_client.Match.timeline_by_match_id(region, match_id); 178 | doc = json::parse(result->data()); 179 | REQUIRE(doc["metadata"]["matchId"] == match_id); 180 | } 181 | 182 | TEST_CASE("CHAMPION-MASTERY-V4 QUERIES") { 183 | std::cout << "TESTING CHAMPION-MASTERY-V4 QUERIES" << '\n'; 184 | RiotApiClient test_client(CONFIG); 185 | 186 | const int champion_id = 1; 187 | std::string endpoint = "CHAMPION-MASTERY-V4"; 188 | std::vector> optional_args = {{"count", "1"}}; 189 | 190 | json_ptr result; 191 | json doc; 192 | 193 | result = test_client.League.entries("KR", "RANKED_SOLO_5x5", "DIAMOND", "I"); 194 | doc = json::parse(result->data()); 195 | std::string summonerId; summonerId = doc.at(0)["summonerId"]; 196 | 197 | result = test_client.Summoner.by_summoner_id("KR", summonerId); 198 | doc = json::parse(result->data()); 199 | std::string puuid; 200 | puuid = doc["puuid"]; 201 | 202 | result = test_client.Champion_Mastery.by_puuid(ROUTING, puuid); 203 | doc = json::parse(result->data()); 204 | INFO("puuid Used: " + puuid); 205 | std::string res = doc.at(0)["puuid"]; 206 | REQUIRE(res == puuid); 207 | 208 | result = test_client.Champion_Mastery.by_puuid_by_champion(ROUTING, puuid, champion_id); 209 | doc = json::parse(result->data()); 210 | INFO(result->data()); 211 | REQUIRE(doc["championId"] == champion_id); 212 | INFO(result->data()); 213 | res = doc["puuid"]; 214 | REQUIRE(res == puuid); 215 | result = test_client.Champion_Mastery.scores_by_puuid(ROUTING, puuid); 216 | } 217 | 218 | TEST_CASE("CHAMPION-V3") { 219 | std::cout << "TESTING CHAMPION-V3" << '\n'; 220 | RiotApiClient test_client(CONFIG); 221 | 222 | json_ptr result; 223 | json doc; 224 | 225 | result = test_client.Champion.champion_rotations(ROUTING); 226 | doc = json::parse(result->data()); 227 | REQUIRE_NOTHROW(doc.at("freeChampionIds")); 228 | REQUIRE_NOTHROW(doc.at("freeChampionIdsForNewPlayers")); 229 | } 230 | 231 | TEST_CASE("LOL-CHALLENGES-V1") { 232 | std::cout << "TESTING LOL-CHALLENGES-V1" << '\n'; 233 | RiotApiClient test_client(CONFIG); 234 | 235 | const int challenge_id = 1; 236 | std::string level = "HIGHEST"; 237 | json_ptr result; 238 | json doc; 239 | 240 | result = test_client.Lol_Challenges.config(ROUTING); 241 | doc = json::parse(result->data()); 242 | REQUIRE_NOTHROW(doc.at(0).at("id")); 243 | REQUIRE_NOTHROW(doc.at(0).at("localizedNames")); 244 | 245 | result = test_client.Lol_Challenges.percentiles(ROUTING); 246 | doc = json::parse(result->data()); 247 | REQUIRE_NOTHROW(doc.at("0")); 248 | REQUIRE_NOTHROW(doc.at("1")); 249 | 250 | result = test_client.Lol_Challenges.config_by_id(ROUTING, challenge_id); 251 | doc = json::parse(result->data()); 252 | REQUIRE_NOTHROW(doc.at("id")); 253 | REQUIRE_NOTHROW(doc.at("localizedNames")); 254 | // result = test_client.Lol_Challenges.challenge_leaderboard(ROUTING, challenge_id, level); failing on riotswebstire as well?! 255 | // REQUIRE(result.isMember("BRONZE")); 256 | // REQUIRE(result.isMember("CHALLENGER")); 257 | result = test_client.Lol_Challenges.player_data_by_puuid(ROUTING, PUUID); 258 | doc = json::parse(result->data()); 259 | REQUIRE_NOTHROW(doc.at("challenges")); 260 | REQUIRE_NOTHROW(doc.at("preferences")); 261 | REQUIRE_NOTHROW(doc.at("totalPoints")); 262 | REQUIRE_NOTHROW(doc.at("categoryPoints")); 263 | } 264 | TEST_CASE("LOL-STATUS") { 265 | std::cout << "TESTING LOL-STATUS" << '\n'; 266 | RiotApiClient test_client(CONFIG); 267 | 268 | json_ptr result; 269 | json doc; 270 | 271 | // not available for my development key 272 | // result = test_client.query(endpoint, std::string("v3"), std::vector{ROUTING}); 273 | result = test_client.Lol_Status.status(ROUTING); 274 | doc = json::parse(result->data()); 275 | REQUIRE(doc["id"] == "KR"); 276 | REQUIRE_NOTHROW(doc.at("maintenances")); 277 | REQUIRE_NOTHROW(doc.at("incidents")); 278 | REQUIRE_NOTHROW(doc.at("locales")); 279 | } 280 | TEST_CASE("LOR-MATCH-V1") { 281 | std::cout << "TESTING LOR-MATCH-V1" << '\n'; 282 | RiotApiClient test_client(CONFIG); 283 | 284 | json_ptr result; 285 | std::string endpoint = "LOR-MATCH-V1"; 286 | std::string match_id; 287 | json doc; 288 | 289 | result = test_client.Account.by_riot_id("AMERICAS", "Monkeys R Us", "fresn"); 290 | doc = json::parse(result->data()); 291 | std::string puuid; puuid = doc["puuid"]; 292 | 293 | result = test_client.Lor_Match.by_puuid("AMERICAS", puuid); 294 | doc = json::parse(result->data()); 295 | REQUIRE_NOTHROW(doc.at(0)); 296 | match_id = doc.at(1); 297 | result = test_client.Lor_Match.by_match_id("AMERICAS", match_id); 298 | // Haven't played enough recently 299 | // REQUIRE(result["metadata"]["match_id"] == match_id); 300 | } 301 | TEST_CASE("LOR-RANKED-V1") { 302 | std::cout << "TESTING LOR-RANKED-V1" << '\n'; 303 | RiotApiClient test_client(CONFIG); 304 | 305 | json_ptr result; 306 | json doc; 307 | 308 | result = test_client.Lor_Ranked.leaderboards("AMERICAS"); 309 | doc = json::parse(result->data()); 310 | REQUIRE_NOTHROW(doc.at("players")); 311 | REQUIRE(doc["players"].is_array()); 312 | } 313 | TEST_CASE("LOR-STATUS-V1") { 314 | std::cout << "TESTING LOR-STATUS-V1" << '\n'; 315 | RiotApiClient test_client(CONFIG); 316 | 317 | json_ptr result; 318 | json doc; 319 | 320 | result = test_client.Lor_Status.status("AMERICAS"); 321 | doc = json::parse(result->data()); 322 | REQUIRE(doc["id"] == "Americas"); 323 | REQUIRE(doc["name"] == "Americas"); 324 | REQUIRE(doc["locales"].is_array()); 325 | REQUIRE_NOTHROW(doc.at("maintenances")); 326 | REQUIRE_NOTHROW(doc.at("incidents")); 327 | } 328 | TEST_CASE("SPECTATOR-V4") { 329 | std::cout << "TESTING SPECTATOR-V4" << '\n'; 330 | RiotApiClient test_client(CONFIG); 331 | 332 | json_ptr result; 333 | std::string endpoint = "SPECTATOR-V4"; 334 | json doc; 335 | 336 | result = test_client.Spectator.featured(ROUTING); 337 | doc = json::parse(result->data()); 338 | INFO("JSON EXISTENCE CHECK: \"gameList\""); 339 | REQUIRE_NOTHROW(doc.at("gameList")); 340 | INFO("JSON ARRAY CHECK"); 341 | REQUIRE(doc["gameList"].is_array()); 342 | auto ref = doc["gameList"].at(0); 343 | INFO("JSON EXISTENCE CHECK: \"participants\""); 344 | REQUIRE_NOTHROW(ref.at("participants")); 345 | std::string puuid = ref["participants"].at(0)["puuid"]; 346 | result = test_client.Summoner.by_puuid(ROUTING, puuid); 347 | doc = json::parse(result->data()); 348 | puuid = doc["puuid"]; 349 | result = test_client.Spectator.by_summoner(ROUTING, puuid); 350 | doc = json::parse(result->data()); 351 | INFO("JSON EXISTENCE CHECK: \"participants\""); 352 | REQUIRE_NOTHROW(doc.at("participants")); 353 | INFO("JSON ARRAY CHECK"); 354 | REQUIRE(doc["participants"].is_array()); 355 | auto foriter = doc["participants"]; 356 | { // find participant in the game to see if the correct game was found 357 | bool participant_found = false; 358 | bool temp; 359 | for (auto& objjs : foriter) { 360 | temp = objjs["puuid"] == puuid; 361 | participant_found = participant_found || temp; 362 | } 363 | REQUIRE(participant_found); 364 | } 365 | } 366 | TEST_CASE("TFT-LEAGUE-V1") { 367 | std::cout << "TESTING TFT-LEAGUE-V1" << '\n'; 368 | RiotApiClient test_client(CONFIG); 369 | 370 | json_ptr result; 371 | std::string endpoint = "TFT-LEAGUE-V1"; 372 | json doc; 373 | 374 | std::string summonerid; 375 | 376 | result = test_client.Tft_League.challenger(ROUTING); 377 | doc = json::parse(result->data()); 378 | REQUIRE(doc["tier"] == "CHALLENGER"); 379 | summonerid = doc["entries"].at(0)["summonerId"]; 380 | 381 | result = test_client.Tft_League.grandmaster(ROUTING); 382 | doc = json::parse(result->data()); 383 | REQUIRE(doc["tier"] == "GRANDMASTER"); 384 | 385 | result = test_client.Tft_League.master(ROUTING); 386 | doc = json::parse(result->data()); 387 | REQUIRE(doc["tier"] == "MASTER"); 388 | 389 | result = test_client.Tft_League.by_summoner(ROUTING, summonerid); 390 | doc = json::parse(result->data()); 391 | 392 | auto arrobj = doc.at(0); 393 | INFO("JSON KEY EXISTENCE: \"summonerId\""); 394 | REQUIRE_NOTHROW(arrobj.at("summonerId")); 395 | INFO("JSON KEY EXISTENCE: \"leagueId\""); 396 | REQUIRE_NOTHROW(arrobj.at("leagueId")); 397 | INFO("CHECKING CORRECT QUERY"); 398 | REQUIRE(arrobj["summonerId"] == summonerid); 399 | std::string league_id; 400 | league_id = arrobj["leagueId"]; 401 | 402 | result = test_client.Tft_League.by_league_id(ROUTING, league_id); 403 | doc = json::parse(result->data()); 404 | INFO("JSON KEY EXISTENCE: \"leagueId\""); 405 | REQUIRE_NOTHROW(doc.at("leagueId")); 406 | REQUIRE(doc["leagueId"] == league_id); 407 | 408 | result = test_client.Tft_League.top_by_queue(ROUTING, "RANKED_TFT_TURBO"); 409 | doc = json::parse(result->data()); 410 | INFO("ASSERTING JSON RESPONSE IS ARRAY"); 411 | REQUIRE(doc.is_array()); 412 | 413 | result = test_client.Tft_League.entries(ROUTING, "DIAMOND", "II", {.queue = "RANKED_TFT", .page = 2}); 414 | doc = json::parse(result->data()); 415 | auto jsonref = doc.at(0); 416 | INFO("JSON KEY EXISTENCE: \"tier\""); 417 | REQUIRE_NOTHROW(jsonref.at("tier")); 418 | INFO("JSON KEY EXISTENCE: \"rank\""); 419 | REQUIRE_NOTHROW(jsonref.at("rank")); 420 | INFO("CHECKING CORRECT QUERY"); 421 | REQUIRE(jsonref["tier"] == "DIAMOND"); 422 | REQUIRE(jsonref["rank"] == "II"); 423 | } 424 | TEST_CASE("TFT-MATCH-V1") { 425 | std::cout << "TESTING TFT-MATCH-V1" << '\n'; 426 | RiotApiClient test_client(CONFIG); 427 | 428 | json_ptr result; 429 | json doc; 430 | 431 | std::string endpoint = "TFT-MATCH-V1"; 432 | 433 | result = test_client.Account.by_riot_id("AMERICAS", "Monkeys R Us", "fresn"); 434 | doc = json::parse(result->data()); 435 | std::string puuid; 436 | puuid = doc["puuid"]; 437 | 438 | result = test_client.Tft_Match.by_puuid("AMERICAS", puuid, {.startTime = 0, .start = 5, .count = 20}); 439 | doc = json::parse(result->data()); 440 | REQUIRE(doc.is_array()); 441 | REQUIRE(doc.size() == 20); 442 | std::string match_id = doc.at(0); 443 | 444 | result = test_client.Tft_Match.by_match_id("AMERICAS", match_id); 445 | doc = json::parse(result->data()); 446 | if (doc.contains("status") && doc["status"]["status_code"] == 404) { 447 | INFO("404: Data not found error. Possible known problem with RIOT API."); 448 | REQUIRE(false); 449 | } else { 450 | INFO("JSON KEY EXISTENCE: \"metadata\""); 451 | REQUIRE_NOTHROW(doc.at("metadata")); 452 | INFO("JSON KEY EXISTENCE: \"info\""); 453 | REQUIRE_NOTHROW(doc.at("info")); 454 | INFO("JSON KEY EXISTENCE: \"metadata|match_id\""); 455 | REQUIRE_NOTHROW(doc.at("metadata").at("match_id")); 456 | INFO("CHECKING CORRECR QUERY"); 457 | REQUIRE(doc["metadata"]["match_id"] == match_id); 458 | } 459 | } 460 | TEST_CASE("TFT-STATUS-V1") { 461 | std::cout << "TESTING TFT-STATUS-V1" << '\n'; 462 | RiotApiClient test_client(CONFIG); 463 | 464 | json_ptr result; 465 | json doc; 466 | 467 | result = test_client.Tft_Status.status(ROUTING); 468 | doc = json::parse(result->data()); 469 | REQUIRE_NOTHROW(doc.at("id")); 470 | REQUIRE(doc["id"] == "KR"); 471 | 472 | } 473 | TEST_CASE("TFT-SUMMONER-V1") { 474 | std::cout << "TESTING TFT-SUMMONER-V1" << '\n'; 475 | RiotApiClient test_client(CONFIG); 476 | 477 | json_ptr result; 478 | json doc; 479 | 480 | std::string summoner_name = "Monkeys R US"; 481 | std::string reg = "oc1"; 482 | 483 | result = test_client.Account.by_riot_id("AMERICAS", "Monkeys R Us", "fresn"); 484 | doc = json::parse(result->data()); 485 | std::string puuid; puuid = doc["puuid"]; 486 | result = test_client.Tft_Summoner.by_puuid("oc1", puuid); 487 | doc = json::parse(result->data()); 488 | REQUIRE_NOTHROW(doc.at("id")); 489 | REQUIRE_NOTHROW(doc.at("puuid")); 490 | REQUIRE_NOTHROW(doc.at("accountId")); 491 | REQUIRE(doc["puuid"] == puuid); 492 | puuid = doc["puuid"]; 493 | std::string summoner_id = doc["id"]; 494 | std::string account_id = doc["accountId"]; 495 | 496 | result = test_client.Tft_Summoner.by_account_id(reg, account_id); 497 | doc = json::parse(result->data()); 498 | REQUIRE(doc["accountId"] == account_id); 499 | 500 | result = test_client.Tft_Summoner.by_summoner_id(reg, summoner_id); 501 | doc = json::parse(result->data()); 502 | REQUIRE(doc["id"] == summoner_id); 503 | } 504 | TEST_CASE("VAL-CONTENT-V1") { 505 | std::cout << "TESTING VAL-CONTENT-V1" << '\n'; 506 | RiotApiClient test_client(CONFIG); 507 | 508 | json_ptr result; 509 | json doc; 510 | 511 | result = test_client.Val_Content.contents("AP"); 512 | doc = json::parse(result->data()); 513 | REQUIRE_NOTHROW(doc.at("version")); 514 | REQUIRE_NOTHROW(doc.at("characters")); 515 | } 516 | } 517 | // not available with my development key 518 | //TEST_CASE("VAL-MATCH-V1") { 519 | //} 520 | //TEST_CASE("VAL-RANKED-V1") { 521 | // 522 | // std::string act_id = "0df5adb9-4dcb-6899-1306-3e9860661dd3"; 523 | //} 524 | //TEST_CASE("VAL-STATUS-V1") { 525 | //} 526 | -------------------------------------------------------------------------------- /src/riot-cpp/query/url.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef DEBUG_MODE 17 | #ifndef rcp_assert 18 | #define rcp_assert(x, msg) if (!x) {std::cerr << "ASSERTION FAILED: " << msg << std::endl;} 19 | #endif 20 | #else 21 | #ifndef rcp_assert 22 | #define rcp_assert(x, msg) 23 | #endif 24 | #endif 25 | 26 | 27 | 28 | namespace riotcpp { 29 | namespace url { 30 | 31 | // Emulate kwargs for optional arguments 32 | template 33 | concept OptInt = std::is_same::value || 34 | std::is_same::value || 35 | std::is_same::value || 36 | std::is_same::value; 37 | 38 | template 39 | class Maybe { 40 | private: 41 | T value_; 42 | bool initialised_ = false; 43 | public: 44 | Maybe() {}; 45 | Maybe(const T& value) { 46 | value_ = value; 47 | initialised_ = true; 48 | } 49 | inline T value() const {return value_;}; 50 | inline bool initialised() const {return initialised_;}; 51 | }; 52 | 53 | inline u_int early_exit_strlen(const char* buff, u_int max_len) { 54 | int i = 0; 55 | while (i < max_len) { 56 | if (buff[i] == '\0') { 57 | break; 58 | } 59 | ++i; 60 | } 61 | return i; 62 | } 63 | 64 | template<> 65 | class Maybe { 66 | private: 67 | std::string value_; 68 | bool initialised_ = false; 69 | public: 70 | Maybe() {}; 71 | Maybe(const std::string& value) : value_(value) { 72 | initialised_ = true; 73 | } 74 | Maybe(const char* value) { 75 | // Assert maximum size of 32. 76 | value_ = std::string(value, early_exit_strlen(value, 32)); 77 | initialised_ = true; 78 | } 79 | inline std::string value() const {return value_;}; 80 | inline bool initialised() const {return initialised_;}; 81 | }; 82 | 83 | inline bool need_percent_encode(const char chr) { 84 | if ('@' < chr && chr < '[') { 85 | return false; 86 | } 87 | if ('`' < chr && chr < '{') { 88 | return false; 89 | } 90 | if ('/' < chr && chr < ':') { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | inline int length_url_frag(const std::string& param) { 97 | int n = param.size(); 98 | for (const char& chr : param) { 99 | n += need_percent_encode(chr) * 2; 100 | } 101 | return n; 102 | } 103 | 104 | inline void to_hex(const char chr, char* encoded) { 105 | static constexpr char tab[17] = "0123456789ABCDEF"; 106 | encoded[0] = '%'; 107 | encoded[1] = tab[(unsigned char) chr >> 4]; 108 | encoded[2] = tab[(unsigned char) chr & 0b00001111]; 109 | return; 110 | } 111 | 112 | inline int ndigits(u_int n) { 113 | int n_digits = 1; 114 | int comp = 10; 115 | while (n >= comp) { 116 | comp *= 10; 117 | ++n_digits; 118 | } 119 | return n_digits; 120 | } 121 | 122 | inline char* write_int(char* buff, u_int n) { 123 | int len_n = ndigits(n); 124 | int tmp; 125 | for (int i = len_n-1; i >= 0; i--) { 126 | tmp = n % 10; 127 | buff[i] = tmp + '0'; 128 | n = (n - tmp) / 10; 129 | } 130 | return buff + len_n; 131 | } 132 | 133 | 134 | inline char* encode_write(const std::string& to_encode, char* to_write) { 135 | int ind = 0; 136 | const char* srt = to_encode.data(); 137 | const std::size_t l = to_encode.size(); 138 | for (int i = 0; i < l; i++) { 139 | if (need_percent_encode(srt[i])) { 140 | to_hex(srt[i], to_write + ind); 141 | ind += 3; 142 | } 143 | else { 144 | to_write[ind] = srt[i]; 145 | ind += 1; 146 | } 147 | } 148 | return to_write + ind; 149 | } 150 | 151 | typedef struct opt_count { 152 | Maybe count; 153 | } opt_count; 154 | 155 | typedef struct opt_page { 156 | Maybe page; 157 | } opt_page; 158 | 159 | typedef struct opt_limit { 160 | Maybe limit; 161 | } opt_limit; 162 | 163 | 164 | typedef struct opt_match_history { 165 | Maybe startTime; 166 | Maybe endTime; 167 | Maybe queue; 168 | Maybe type; 169 | Maybe start; 170 | Maybe count; 171 | } opt_match_history; 172 | 173 | typedef struct opt_tft_match_history { 174 | Maybe startTime; 175 | Maybe endTime; 176 | Maybe start; 177 | Maybe count; 178 | } opt_tft_match_history; 179 | 180 | typedef struct opt_queue { 181 | Maybe queue; 182 | } opt_queue; 183 | 184 | typedef struct opt_queue_page { 185 | Maybe queue; 186 | Maybe page; 187 | } opt_queue_page; 188 | 189 | typedef struct opt_start_size { 190 | Maybe size; 191 | Maybe startIndex; 192 | } opt_start_size; 193 | 194 | typedef struct opt_locale { 195 | Maybe locale; 196 | } opt_locale; 197 | 198 | typedef struct no_opt { 199 | } no_opt; 200 | 201 | template 202 | concept OptArg = std::is_same::value || 203 | std::is_same::value || 204 | std::is_same::value || 205 | std::is_same::value || 206 | std::is_same::value || 207 | std::is_same::value || 208 | std::is_same::value || 209 | std::is_same::value || 210 | std::is_same::value || 211 | std::is_same::value; 212 | 213 | template 214 | inline void write_opt_arg(char* buff, const T& opt_arg); 215 | 216 | template<> 217 | inline void write_opt_arg(char* buff, const opt_count& opt_arg) { 218 | if (opt_arg.count.initialised()) { 219 | buff[0] = '?'; ++buff; 220 | memcpy(buff, "count=", 6); buff = buff + 6; 221 | write_int(buff, opt_arg.count.value()); 222 | } 223 | } 224 | template<> 225 | inline void write_opt_arg(char* buff, const opt_page& opt_arg) { 226 | if (opt_arg.page.initialised()) { 227 | buff[0] = '?'; ++buff; 228 | memcpy(buff, "page=", 5); buff = buff + 5; 229 | write_int(buff, opt_arg.page.value()); 230 | } 231 | } 232 | template<> 233 | inline void write_opt_arg(char* buff, const opt_limit& opt_arg) { 234 | if (opt_arg.limit.initialised()) { 235 | buff[0] = '?'; ++buff; 236 | memcpy(buff, "limit=", 6); buff = buff + 6; 237 | write_int(buff, opt_arg.limit.value()); 238 | } 239 | } 240 | template<> 241 | inline void write_opt_arg(char* buff, const opt_tft_match_history& opt_arg) { 242 | char sep = '?'; 243 | if (opt_arg.startTime.initialised()) { 244 | buff[0] = sep; ++buff; 245 | memcpy(buff, "startTime=", 10); buff = buff + 10; 246 | buff = write_int(buff, opt_arg.startTime.value()); 247 | sep = '&'; 248 | } 249 | if (opt_arg.endTime.initialised()) { 250 | buff[0] = sep; ++buff; 251 | memcpy(buff, "endTime=", 8); buff = buff + 8; 252 | buff = write_int(buff, opt_arg.endTime.value()); 253 | sep = '&'; 254 | } 255 | if (opt_arg.start.initialised()) { 256 | buff[0] = sep; ++buff; 257 | memcpy(buff, "start=", 6); buff = buff + 6; 258 | buff = write_int(buff, opt_arg.start.value()); 259 | sep = '&'; 260 | } 261 | if (opt_arg.count.initialised()) { 262 | buff[0] = sep; ++buff; 263 | memcpy(buff, "count=", 6); buff = buff + 6; 264 | buff = write_int(buff, opt_arg.count.value()); 265 | sep = '&'; 266 | } 267 | } 268 | template<> 269 | inline void write_opt_arg(char* buff, const opt_match_history& opt_arg) { 270 | char sep = '?'; 271 | if (opt_arg.startTime.initialised()) { 272 | buff[0] = sep; ++buff; 273 | memcpy(buff, "startTime=", 10); buff = buff + 10; 274 | buff = write_int(buff, opt_arg.startTime.value()); 275 | sep = '&'; 276 | } 277 | if (opt_arg.endTime.initialised()) { 278 | buff[0] = sep; ++buff; 279 | memcpy(buff, "endTime=", 8); buff = buff + 8; 280 | buff = write_int(buff, opt_arg.endTime.value()); 281 | sep = '&'; 282 | } 283 | if (opt_arg.queue.initialised()) { 284 | buff[0] = sep; ++buff; 285 | memcpy(buff, "queue=", 6); buff = buff + 6; 286 | buff = encode_write(opt_arg.queue.value(), buff); 287 | sep = '&'; 288 | } 289 | if (opt_arg.type.initialised()) { 290 | buff[0] = sep; ++buff; 291 | memcpy(buff, "type=", 5); buff = buff + 5; 292 | buff = encode_write(opt_arg.type.value(), buff); 293 | sep = '&'; 294 | } 295 | if (opt_arg.start.initialised()) { 296 | buff[0] = sep; ++buff; 297 | memcpy(buff, "start=", 6); buff = buff + 6; 298 | buff = write_int(buff, opt_arg.start.value()); 299 | sep = '&'; 300 | } 301 | if (opt_arg.count.initialised()) { 302 | buff[0] = sep; ++buff; 303 | memcpy(buff, "count=", 6); buff = buff + 6; 304 | buff = write_int(buff, opt_arg.count.value()); 305 | sep = '&'; 306 | } 307 | } 308 | template<> 309 | inline void write_opt_arg(char* buff, const opt_queue& opt_arg) { 310 | if (opt_arg.queue.initialised()) { 311 | buff[0] = '?'; ++buff; 312 | memcpy(buff, "queue=", 6); buff = buff + 6; 313 | buff = encode_write(opt_arg.queue.value(), buff); 314 | } 315 | } 316 | template<> 317 | inline void write_opt_arg(char* buff, const opt_queue_page& opt_arg) { 318 | char sep = '?'; 319 | if (opt_arg.queue.initialised()) { 320 | buff[0] = sep; ++buff; 321 | memcpy(buff, "queue=", 6); buff = buff + 6; 322 | buff = encode_write(opt_arg.queue.value(), buff); 323 | sep = '&'; 324 | } 325 | if (opt_arg.page.initialised()) { 326 | buff[0] = sep; ++buff; 327 | memcpy(buff, "page=", 5); buff = buff + 5; 328 | buff = write_int(buff, opt_arg.page.value()); 329 | sep = '&'; 330 | } 331 | } 332 | template<> 333 | inline void write_opt_arg(char* buff, const opt_start_size& opt_arg) { 334 | char sep = '?'; 335 | if (opt_arg.size.initialised()) { 336 | buff[0] = sep; ++buff; 337 | memcpy(buff, "size=", 5); buff = buff + 5; 338 | buff = write_int(buff, opt_arg.size.value()); 339 | sep = '&'; 340 | } 341 | if (opt_arg.startIndex.initialised()) { 342 | buff[0] = sep; ++buff; 343 | memcpy(buff, "startIndex=", 11); buff = buff + 11; 344 | buff = write_int(buff, opt_arg.startIndex.value()); 345 | sep = '&'; 346 | } 347 | } 348 | template<> 349 | inline void write_opt_arg(char* buff, const opt_locale& opt_arg) { 350 | if (opt_arg.locale.initialised()) { 351 | buff[0] = '?'; ++buff; 352 | memcpy(buff, "locale=", 7); buff = buff + 7; 353 | encode_write(opt_arg.locale.value(), buff); 354 | } 355 | } 356 | // Get length of string ?=&=... 357 | template 358 | inline u_int getSize(const T& opt_arg); 359 | 360 | template<> 361 | inline u_int getSize(const opt_count& opt_arg) { 362 | if (opt_arg.count.initialised()) { 363 | return 7 + ndigits(opt_arg.count.value()); 364 | } 365 | return 0; 366 | }; 367 | template<> 368 | inline u_int getSize(const opt_page& opt_arg) { 369 | if (opt_arg.page.initialised()) { 370 | return 6 + ndigits(opt_arg.page.value()); 371 | } 372 | return 0; 373 | }; 374 | template<> 375 | inline u_int getSize(const opt_limit& opt_arg) { 376 | if (opt_arg.limit.initialised()) { 377 | return 7 + ndigits(opt_arg.limit.value()); 378 | } 379 | return 0; 380 | }; 381 | template<> 382 | inline u_int getSize(const opt_tft_match_history& opt_arg) { 383 | int size = 0; 384 | if (opt_arg.startTime.initialised()) { 385 | size += 11 + ndigits(opt_arg.startTime.value()); 386 | } 387 | if (opt_arg.endTime.initialised()) { 388 | size += 9 + ndigits(opt_arg.endTime.value()); 389 | } 390 | if (opt_arg.start.initialised()) { 391 | size += 7 + ndigits(opt_arg.start.value()); 392 | } 393 | if (opt_arg.count.initialised()) { 394 | size += 7 + ndigits(opt_arg.count.value()); 395 | } 396 | return size; 397 | }; 398 | template<> 399 | inline u_int getSize(const opt_match_history& opt_arg) { 400 | int size = 0; 401 | if (opt_arg.startTime.initialised()) { 402 | size += 11 + ndigits(opt_arg.startTime.value()); 403 | } 404 | if (opt_arg.endTime.initialised()) { 405 | size += 9 + ndigits(opt_arg.endTime.value()); 406 | } 407 | if (opt_arg.queue.initialised()) { 408 | size += 7 + length_url_frag(opt_arg.queue.value()); 409 | } 410 | if (opt_arg.type.initialised()) { 411 | size += 6 + length_url_frag(opt_arg.type.value()); 412 | } 413 | if (opt_arg.start.initialised()) { 414 | size += 7 + ndigits(opt_arg.start.value()); 415 | } 416 | if (opt_arg.count.initialised()) { 417 | size += 7 + ndigits(opt_arg.count.value()); 418 | } 419 | return size; 420 | }; 421 | template<> 422 | inline u_int getSize(const opt_queue& opt_arg) { 423 | int count = 0; 424 | if (opt_arg.queue.initialised()) { 425 | count += 7 + length_url_frag(opt_arg.queue.value()); 426 | } 427 | return count; 428 | } 429 | template<> 430 | inline u_int getSize(const opt_queue_page& opt_arg) { 431 | int count = 0; 432 | if (opt_arg.queue.initialised()) { 433 | count += 7 + length_url_frag(opt_arg.queue.value()); 434 | } 435 | if (opt_arg.page.initialised()) { 436 | count += 6 + ndigits(opt_arg.page.value()); 437 | } 438 | return count; 439 | } 440 | template<> 441 | inline u_int getSize(const opt_start_size& opt_arg) { 442 | int count = 0; 443 | if (opt_arg.size.initialised()) { 444 | count += 6 + ndigits(opt_arg.size.value()); 445 | } 446 | if (opt_arg.startIndex.initialised()) { 447 | count += 12 + ndigits(opt_arg.startIndex.value()); 448 | } 449 | return count; 450 | } 451 | template<> 452 | inline u_int getSize(const opt_locale& opt_arg) { 453 | if (opt_arg.locale.initialised()) { 454 | return 8 + length_url_frag(opt_arg.locale.value()); 455 | } 456 | return 0; 457 | } 458 | 459 | template || std::is_convertible_v>::type> 460 | inline u_int length_(const Arg& arg) { 461 | if constexpr (std::is_arithmetic_v) { 462 | return ndigits(arg); 463 | } 464 | if constexpr (std::is_convertible_v) { 465 | return length_url_frag(arg); 466 | } 467 | rcp_assert(false, "length_ given invalid type"); 468 | return 0; 469 | } 470 | 471 | inline u_int length() { 472 | return 0; 473 | } 474 | 475 | template 476 | inline u_int length(const Arg& arg) { 477 | return length_(arg); 478 | } 479 | 480 | template 481 | inline u_int length(const Arg& arg, const Args&...args) { 482 | return length_(arg) + length(args...); 483 | } 484 | 485 | // Replace with an implementation to avoid allocations 486 | inline const std::string encode_params(const std::string& to_encode) { 487 | char* encoded = curl_easy_escape(NULL, to_encode.c_str(), to_encode.length()); 488 | const std::string encoded_str(encoded); 489 | curl_free(encoded); 490 | return encoded_str; 491 | } 492 | 493 | template 494 | inline void write_url_frag(char* url, const std::size_t ind, const std::array& frags, const Arg& arg) { 495 | if constexpr (N == 0) { 496 | return; 497 | } 498 | memcpy(url, frags[ind].data(), frags[ind].size()); 499 | url = url + frags[ind].size(); 500 | if constexpr (std::is_convertible_v) { 501 | url = write_int(url, arg); 502 | } 503 | else if constexpr (std::is_convertible_v) { 504 | url = encode_write(arg, url); 505 | } 506 | } 507 | 508 | template 509 | inline void write_url_frag(char* url, const std::size_t ind, const std::array& frags, const Arg& arg, const Args&... args) { 510 | if constexpr (N == 0) { 511 | return; 512 | } 513 | memcpy(url, frags[ind].data(), frags[ind].size()); 514 | url = url + frags[ind].size(); 515 | if constexpr (std::is_convertible_v) { 516 | url = write_int(url, arg); 517 | } 518 | else if constexpr (std::is_convertible_v) { 519 | url = encode_write(arg, url); 520 | } 521 | if (ind < N-1) { 522 | write_url_frag(url, ind+1, frags, args...); 523 | } 524 | } 525 | 526 | template 527 | std::unique_ptr construct_url(const std::string& routing, const std::string& url_base, const std::size_t len, const std::array& url_frags, const Args&...args) { 528 | const u_int arg_len = length(args...) + 8 + 18 + routing.size() + url_base.size() + len; 529 | std::unique_ptr url(new char[arg_len+1]); 530 | url.get()[arg_len] = '\0'; 531 | int ind = 0; 532 | memcpy(url.get(), "https://", 8); 533 | ind += 8; 534 | memcpy(url.get() + ind, routing.data(), routing.size()); 535 | ind += routing.size(); 536 | memcpy(url.get() + ind, ".api.riotgames.com", 18); 537 | ind += 18; 538 | memcpy(url.get() + ind, url_base.data(), url_base.size()); 539 | ind += url_base.size(); 540 | if constexpr (N != 0 | sizeof...(Args) == 0) { 541 | write_url_frag(url.get()+ind, 0, url_frags, args...); 542 | } 543 | return url; 544 | }; 545 | 546 | template 547 | std::unique_ptr construct_url(const std::string& routing, const std::string& url_base, const std::string& trailing, const std::size_t len, const std::array& url_frags, const Args&...args) { 548 | const u_int arg_len = length(args...) + 8 + 18 + routing.size() + url_base.size() + len + trailing.size(); 549 | std::unique_ptr url(new char[arg_len+1]); 550 | url.get()[arg_len] = '\0'; 551 | int ind = 0; 552 | memcpy(url.get(), "https://", 8); 553 | ind += 8; 554 | memcpy(url.get() + ind, routing.data(), routing.size()); 555 | ind += routing.size(); 556 | memcpy(url.get() + ind, ".api.riotgames.com", 18); 557 | ind += 18; 558 | memcpy(url.get() + ind, url_base.data(), url_base.size()); 559 | ind += url_base.size(); 560 | if constexpr (N != 0 || sizeof...(Args) != 0) { 561 | write_url_frag(url.get()+ind, 0, url_frags, args...); 562 | } 563 | memcpy(url.get() + arg_len - trailing.size(), trailing.data(), trailing.size()); 564 | return url; 565 | }; 566 | 567 | template 568 | std::unique_ptr construct_url(const std::string& routing, const std::string url_base, const T& opt_arg, const std::string& trailing, const std::size_t len, const std::array& url_frags, const Args&...args) { 569 | const int optsize = getSize(opt_arg); 570 | const u_int arg_len = length(args...) + 8 + 18 + routing.size() + url_base.size() + len + trailing.size() + optsize; 571 | std::unique_ptr url(new char[arg_len+1]); 572 | url.get()[arg_len] = '\0'; 573 | int ind = 0; 574 | memcpy(url.get(), "https://", 8); 575 | ind += 8; 576 | memcpy(url.get() + ind, routing.data(), routing.size()); 577 | ind += routing.size(); 578 | memcpy(url.get() + ind, ".api.riotgames.com", 18); 579 | ind += 18; 580 | memcpy(url.get() + ind, url_base.data(), url_base.size()); 581 | ind += url_base.size(); 582 | if constexpr (N != 0 || sizeof...(Args) != 0) { 583 | write_url_frag(url.get()+ind, 0, url_frags, args...); 584 | } 585 | memcpy(url.get() + arg_len - trailing.size() - optsize, trailing.data(), trailing.size()); 586 | write_opt_arg(url.get() + arg_len - optsize, opt_arg); 587 | return url; 588 | } 589 | template 590 | std::unique_ptr construct_url(const std::string& routing, const std::string url_base, const T& opt_arg, const std::size_t len, const std::array& url_frags, const Args&...args) { 591 | return construct_url(routing, url_base, opt_arg, "", len, url_frags, args...); 592 | } 593 | } 594 | } 595 | --------------------------------------------------------------------------------