#include #include "blankie/murl.h" #include "hiredis_wrapper.h" #include "numberhelper.h" #include "pixivclient.h" using namespace std::string_literals; static const constexpr char* touch_user_agent = "Mozilla/5.0 (Android 12; Mobile; rv:97.0) Gecko/97.0 Firefox/97.0"; static const constexpr char* desktop_user_agent = "Mozilla/5.0 (Windows NT 10.0; rv:111.0) Gecko/20100101 Firefox/111.0"; PixivClient::PixivClient(Redis* redis) : _redis(redis) { this->_www_pixiv_net_client.set_keep_alive(true); this->_www_pixiv_net_client.set_default_headers({ {"Cookie", "webp_available=1"} }); this->_i_pximg_net_client.set_keep_alive(true); this->_i_pximg_net_client.set_default_headers({ {"Referer", "https://www.pixiv.net/"} }); } User PixivClient::get_user(uint64_t user_id) { nlohmann::json j = this->_call_api("pixwhile:user:"s + std::to_string(user_id), std::nullopt, 24 * 60 * 60, "/touch/ajax/user/details", {{"lang", "en"}, {"id", std::to_string(user_id)}}, {{"User-Agent", touch_user_agent}}); return j.at("user_details").get(); } Illusts PixivClient::get_illusts(uint64_t user_id, size_t page) { httplib::Params params = {{"lang", "en"}, {"id", std::to_string(user_id)}}; if (page != 0) { params.insert({"p", std::to_string(page + 1)}); } nlohmann::json j = this->_call_api("pixwhile:illusts:"s + std::to_string(user_id), std::to_string(page), 60 * 60, "/touch/ajax/user/illusts", std::move(params), {{"User-Agent", touch_user_agent}}); return j.get(); } Illust PixivClient::get_illust(uint64_t illust_id) { nlohmann::json j = this->_call_api("pixwhile:illust:"s + std::to_string(illust_id), std::nullopt, 24 * 60 * 60, "/touch/ajax/illust/details", {{"lang", "en"}, {"illust_id", std::to_string(illust_id)}}, {{"User-Agent", touch_user_agent}}); return j.get(); } SearchResults PixivClient::search_illusts(std::string query, size_t page, const std::string& order) { httplib::Params params = { {"lang", "en"}, {"mode", "all"}, {"p", std::to_string(page + 1)}, {"s_mode", "s_tag_full"}, {"type", "illust_and_ugoira"}, {"order", order}, {"word", query} }; std::string cache_key = "pixwhile:search:"s + order + ':' + std::to_string(FastHash(query.data(), query.size(), 0)); nlohmann::json j = this->_call_api(std::move(cache_key), std::to_string(page), 60 * 60, "/ajax/search/illustrations/"s + blankie::murl::escape(std::move(query)), std::move(params), {{"User-Agent", desktop_user_agent}}); return j.get(); } std::vector PixivClient::get_search_suggestions(std::string query) { std::string body = [&]() { std::string cache_key = "pixwhile:searchsugg:"s + std::to_string(FastHash(query.data(), query.size(), 0)); if (this->_redis) { std::optional cached_body = this->_redis->get(cache_key); if (cached_body) { return std::move(*cached_body); } } httplib::Result res = this->_www_pixiv_net_client.Get("/rpc/cps.php", { {"lang", "en"}, {"keyword", query} }, {{"User-Agent", desktop_user_agent}, {"Referer", "https://www.pixiv.net/"}}); if (!res) { throw HTTPLibException(res.error()); } if (this->_redis) { this->_redis->set(std::move(cache_key), res->body, 24 * 60 * 60); } return std::move(res->body); }(); nlohmann::json j = nlohmann::json::parse(std::move(body)).at("candidates"); std::vector search_suggestions; search_suggestions.reserve(j.size()); for (const nlohmann::json& i : j) { SearchSuggestion search_suggestion = i.get(); if (search_suggestion.tag != query) { search_suggestions.push_back(std::move(search_suggestion)); } } return search_suggestions; } nlohmann::json PixivClient::_call_api(std::string cache_key, std::optional cache_field, time_t expiry, std::string path, httplib::Params params, httplib::Headers headers) { if (this->_redis) { std::optional success_body = !cache_field ? this->_redis->get(cache_key) : this->_redis->hget(cache_key, *cache_field); if (success_body) { return nlohmann::json::parse(std::move(*success_body)); } } httplib::Result res = this->_www_pixiv_net_client.Get(std::move(path), std::move(params), std::move(headers)); if (!res) { throw HTTPLibException(res.error()); } nlohmann::json j = nlohmann::json::parse(std::move(res->body)); if (j.at("error")) { throw PixivException(res->status, j.at("message").get()); } j = j.at("body"); // trim data sent to redis without making our own serialization of our objects j.erase("ads"); if (this->_redis) { if (!cache_field) { this->_redis->set(std::move(cache_key), j.dump(), expiry); } else { this->_redis->hset(cache_key, std::move(*cache_field), j.dump()); this->_redis->expire_nx(std::move(cache_key), expiry); } } return j; } bool PixivClient::i_pximg_url_valid(const std::string& path) { httplib::Result res = this->_i_pximg_net_client.Head(path, {{"User-Agent", touch_user_agent}}); if (!res) { throw HTTPLibException(res.error()); } return res->status >= 200 && res->status <= 200; }