255 lines
8 KiB
C++
255 lines
8 KiB
C++
/* Copyright 2020, Michele Santullo
|
|
* This file is part of orotool.
|
|
*
|
|
* Orotool is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Orotool is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Orotool. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "private/api_nap.hpp"
|
|
#include "private/v1_endpoints.hpp"
|
|
#include "private/dateconv.hpp"
|
|
#include "duckhandy/int_conv.hpp"
|
|
#include "api_nap_exception.hpp"
|
|
#include <optional>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
|
|
namespace sjd = simdjson::dom;
|
|
namespace sj = simdjson;
|
|
|
|
namespace oro {
|
|
|
|
namespace {
|
|
bool equal (std::string_view a, std::string_view b) {
|
|
return a.size() == b.size() and std::equal(
|
|
a.begin(), a.end(),
|
|
b.begin(),
|
|
[](auto c1, auto c2) {return std::tolower(c1) == std::tolower(c2); }
|
|
);
|
|
}
|
|
|
|
Header to_header (const nap::HttpResponse& resp) {
|
|
using dhandy::int_conv;
|
|
|
|
Header ret;
|
|
for (const auto& entry : resp.header_list) {
|
|
if (equal(entry.first, "Date"))
|
|
ret.date = from_header_timestamp(std::string(entry.second));
|
|
else if (equal(entry.first, "X-RateLimit-Limit"))
|
|
ret.rate_limit = int_conv<decltype(ret.rate_limit)>(entry.second);
|
|
else if (equal(entry.first, "X-RateLimit-Remaining"))
|
|
ret.rate_limit_remaining = int_conv<decltype(ret.rate_limit_remaining)>(entry.second);
|
|
else if (equal(entry.first, "X-RateLimit-Reset"))
|
|
ret.rate_limit_reset = int_conv<decltype(ret.rate_limit_reset)>(entry.second);
|
|
else if (equal(entry.first, "Retry-After"))
|
|
ret.retry_after = int_conv<decltype(ret.retry_after)>(entry.second);
|
|
else if (equal(entry.first, "Server"))
|
|
ret.server = std::string(entry.second);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void fill_base_json (const simdjson::dom::element& doc, BaseJsonReply& out) {
|
|
out.generation_timestamp = std::string(doc["generation_timestamp"]);
|
|
out.version = static_cast<int>(doc["version"].get_int64());
|
|
}
|
|
|
|
template <typename R, typename T>
|
|
std::optional<R> get_optional (const sj::simdjson_result<sjd::element>& elem) {
|
|
typedef std::optional<R> RetType;
|
|
|
|
T retval;
|
|
const simdjson::error_code error = elem.get(retval);
|
|
return (not error ? RetType{static_cast<R>(retval)} : RetType{});
|
|
}
|
|
|
|
template <typename R, typename T>
|
|
R get_or_default (const sj::simdjson_result<sjd::element>& elem, R def) {
|
|
T retval;
|
|
const simdjson::error_code error = elem.get(retval);
|
|
return (not error ? static_cast<R>(retval) : def);
|
|
}
|
|
|
|
void fill_creator_list (const sjd::element& elem, std::vector<Creator>& out) {
|
|
if (elem.is_array()) {
|
|
auto creators = elem.get_array();
|
|
out.reserve(creators.size());
|
|
for (auto creator : creators) {
|
|
Creator new_entry;
|
|
new_entry.char_id = static_cast<unsigned int>(creator["char_id"].get_uint64());
|
|
new_entry.name = creator["name"];
|
|
new_entry.points = static_cast<unsigned int>(creator["points"].get_uint64());
|
|
out.push_back(std::move(new_entry));
|
|
}
|
|
}
|
|
}
|
|
|
|
} //unnamed namespace
|
|
|
|
ApiNap::ApiNap (
|
|
std::string&& root_address,
|
|
std::string&& api_key,
|
|
std::string&& client_name,
|
|
std::string&& client_purpose
|
|
) :
|
|
Api(std::move(root_address), std::move(api_key), std::move(client_name), std::move(client_purpose)),
|
|
m_qrest(simdjson::SIMDJSON_PADDING)
|
|
{
|
|
m_qrest.add_headers({
|
|
{"X-Client", m_client_name},
|
|
{"X-Client-Purpose", m_client_purpose},
|
|
{"x-api-key", m_api_key}
|
|
});
|
|
m_qrest.set_user_agent(std::string(m_client_name));
|
|
}
|
|
|
|
ApiNap::~ApiNap() noexcept = default;
|
|
|
|
std::pair<Header, Ping> ApiNap::ping() {
|
|
return fetch_and_parse<Ping>(
|
|
g_endpoint_ping,
|
|
[](const simdjson::dom::element& doc, Ping& out) {
|
|
out.message = doc["message"];
|
|
}
|
|
);
|
|
}
|
|
|
|
std::pair<Header, WhoAmI> ApiNap::who_am_i() {
|
|
return fetch_and_parse<WhoAmI>(
|
|
g_endpoint_whoami,
|
|
[](const simdjson::dom::element& doc, WhoAmI& out) {
|
|
out.master_id = static_cast<unsigned int>(doc["master_id"].get_uint64());
|
|
}
|
|
);
|
|
}
|
|
|
|
std::pair<Header, Items> ApiNap::items_list() {
|
|
return fetch_and_parse<Items>(
|
|
g_endpoint_items_list,
|
|
[](const simdjson::dom::element& doc, Items& out) {
|
|
auto items = doc["items"].get_array();
|
|
out.items.reserve(items.size());
|
|
for (auto item : items) {
|
|
Item new_entry;
|
|
new_entry.item_id = static_cast<unsigned int>(item["item_id"].get_uint64());
|
|
new_entry.unique_name = item["unique_name"];
|
|
new_entry.name = item["name"];
|
|
new_entry.type = item["type"].get_string();
|
|
new_entry.subtype = get_optional<ItemSubtypeWrapper, std::string_view>(item["subtype"]);
|
|
new_entry.npc_price = static_cast<unsigned int>(item["npc_price"].get_uint64());
|
|
new_entry.slots = get_optional<unsigned int, uint64_t>(item["slots"]);
|
|
out.items.push_back(std::move(new_entry));
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
std::pair<Header, Icons> ApiNap::items_icons() {
|
|
return fetch_and_parse<Icons>(
|
|
g_endpoint_items_icons,
|
|
[](const simdjson::dom::element& doc, Icons& out) {
|
|
auto icons = doc["icons"].get_array();
|
|
out.icons.reserve(icons.size());
|
|
for (auto icon : icons) {
|
|
Icon new_entry;
|
|
new_entry.item_id = static_cast<unsigned int>(icon["item_id"].get_uint64());
|
|
new_entry.icon = icon["icon"];
|
|
out.icons.push_back(std::move(new_entry));
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
std::pair<Header, Shops> ApiNap::market_list() {
|
|
return fetch_and_parse<Shops>(
|
|
g_endpoint_market_list,
|
|
[](const simdjson::dom::element& doc, Shops& out) {
|
|
auto shops = doc["shops"].get_array();
|
|
out.shops.reserve(shops.size());
|
|
for (auto shop : shops) {
|
|
Shop new_shop;
|
|
new_shop.title = shop["title"];
|
|
new_shop.owner = shop["owner"];
|
|
new_shop.creation_date = std::string(shop["creation_date"]);
|
|
new_shop.type = shop["type"].get_string();
|
|
{
|
|
auto loc = shop["location"];
|
|
new_shop.location.map = loc["map"];
|
|
new_shop.location.x = static_cast<int>(loc["x"].get_int64());
|
|
new_shop.location.y = static_cast<int>(loc["y"].get_int64());
|
|
}
|
|
{
|
|
auto items = shop["items"].get_array();
|
|
new_shop.items.reserve(items.size());
|
|
for (auto item : items) {
|
|
ShopItem new_item;
|
|
new_item.item_id = static_cast<unsigned int>(item["item_id"].get_uint64());
|
|
new_item.amount = static_cast<unsigned int>(item["amount"].get_uint64());
|
|
new_item.price = static_cast<unsigned int>(item["price"].get_uint64());
|
|
new_item.refine = get_or_default<unsigned int, uint64_t>(item["refine"], 0);
|
|
new_item.star_crumbs = get_or_default<unsigned int, uint64_t>(item["star_crumbs"], 0);
|
|
new_item.element = get_optional<std::string, std::string_view>(item["element"]);
|
|
new_item.creator = get_optional<unsigned int, uint64_t>(item["creator"]);
|
|
new_item.beloved = get_optional<bool, bool>(item["beloved"]);
|
|
|
|
auto item_cards = item["cards"];
|
|
if (item_cards.is_array()) {
|
|
auto cards = item_cards.get_array();
|
|
new_item.cards.reserve(cards.size());
|
|
for (uint64_t card : cards) {
|
|
new_item.cards.push_back(static_cast<unsigned int>(card));
|
|
}
|
|
}
|
|
|
|
new_shop.items.push_back(std::move(new_item));
|
|
}
|
|
}
|
|
out.shops.push_back(std::move(new_shop));
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
std::pair<Header, Creators> ApiNap::fame_list() {
|
|
return fetch_and_parse<Creators>(
|
|
g_endpoint_fame_list,
|
|
[](const simdjson::dom::element& doc, Creators& out) {
|
|
fill_creator_list(doc["brewers"], out.brewers);
|
|
fill_creator_list(doc["forgers"], out.brewers);
|
|
}
|
|
);
|
|
}
|
|
|
|
template <typename T, typename F>
|
|
std::pair<Header, T> ApiNap::fetch_and_parse (const char* endpoint, F&& data_fill) {
|
|
auto resp = m_qrest.fetch(m_prefix + endpoint);
|
|
|
|
if (200 != resp.code)
|
|
throw ServerError(resp);
|
|
|
|
T dataret;
|
|
{
|
|
std::unique_lock lock(m_json_mutex);
|
|
simdjson::dom::element doc = m_json.parse(
|
|
reinterpret_cast<const uint8_t*>(resp.body.data()),
|
|
resp.body.size()
|
|
);
|
|
|
|
fill_base_json(doc, dataret);
|
|
data_fill(doc, dataret);
|
|
}
|
|
|
|
return {to_header(resp), std::move(dataret)};
|
|
}
|
|
|
|
} //namespace oro
|