From b864b3d2900eb85f859484dac6eacb335ed332eb Mon Sep 17 00:00:00 2001 From: blankie Date: Wed, 5 Apr 2023 00:01:50 +0700 Subject: [PATCH] Add initial user fetching support --- CMakeLists.txt | 2 +- main.cpp | 13 ++++++ pixivclient.cpp | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ pixivclient.h | 64 +++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 pixivclient.cpp create mode 100644 pixivclient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ebd64..e83ece5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ list(APPEND FLAGS -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-s add_link_options(${FLAGS}) -add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp blankie/serializer.cpp blankie/escape.cpp +add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp pixivclient.cpp blankie/serializer.cpp blankie/escape.cpp routes/home.cpp routes/css.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES diff --git a/main.cpp b/main.cpp index 1528f66..1c340cb 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include #include "config.h" +#include "pixivclient.h" #include "servehelper.h" #include "routes/routes.h" @@ -20,6 +21,7 @@ int main(int argc, char** argv) { return 1; } + PixivClient pixiv_client; httplib::Server server; server.Get("/", [&](const httplib::Request& req, httplib::Response& res) { home_route(req, res, config); @@ -52,6 +54,17 @@ int main(int argc, char** argv) { server.Get("/debug/exception/unknown", [](const httplib::Request& req, httplib::Response& res) { throw "cope"; }); + // TODO stop it + server.Get("/debug/getuser/good", [&](const httplib::Request& req, httplib::Response& res) { + User user = pixiv_client.get_user(2583663); + printf("%s\n", user.username.c_str()); + res.set_content(user.username, "text/plain"); + }); + server.Get("/debug/getuser/bad", [&](const httplib::Request& req, httplib::Response& res) { + User user = pixiv_client.get_user(1); + printf("%s\n", user.username.c_str()); + res.set_content(user.username, "text/plain"); + }); #endif server.Get(".*", [&](const httplib::Request& req, httplib::Response& res) { res.status = 404; diff --git a/pixivclient.cpp b/pixivclient.cpp new file mode 100644 index 0000000..5d836aa --- /dev/null +++ b/pixivclient.cpp @@ -0,0 +1,115 @@ +#include + +#include "pixivclient.h" + +static std::regex cover_image_thumbnail_regex( + "((?:https?://)?(?:i\\.pximg\\.net)?)" // optional scheme and host + "/c/[0-9a-z_-]+(/.+)_master\\d+(\\.\\w{3,4})" +); +static inline std::optional get_original_cover_image(const std::string& thumbnail); +static std::regex profile_picture_thumbnail_regex( + "((?:https?://)?(?:i\\.pximg\\.net)?)" // optional scheme and host + "(/.+)_\\d+(\\.\\w{3,4})" +); +static inline std::optional get_original_profile_picture(const std::string& thumbnail); +static inline uint64_t to_ull(const std::string& str); + +PixivClient::PixivClient() { + this->_www_pixiv_net_client.set_keep_alive(true); + this->_www_pixiv_net_client.set_default_headers({ + {"User-Agent", "Mozilla/5.0 (Android 12; Mobile; rv:97.0) Gecko/97.0 Firefox/97.0"} + }); +} + +User PixivClient::get_user(uint64_t user_id) { + httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/user/details", { + {"lang", "en"}, {"id", std::to_string(user_id)} + }, httplib::Headers()); + return this->_handle_result(std::move(res)).at("user_details").get(); +} + +nlohmann::json PixivClient::_handle_result(httplib::Result res) { + 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()); + } + return j.at("body"); +} + +void from_json(const nlohmann::json& j, User& user) { + j.at("user_account").get_to(user.username); + j.at("user_name").get_to(user.display_name); + user.user_id = to_ull(j.at("user_id").get_ref()); + + if (j.contains("cover_image")) { + nlohmann::json cover_image = j["cover_image"]; + std::string c_720x360 = cover_image.at("profile_cover_image").at("720x360").get(); + std::optional original = get_original_cover_image(c_720x360); + + user.cover_images = {std::move(original), {std::move(c_720x360)}}; + } + + nlohmann::json profile_img = j.at("profile_img"); + if (profile_img.contains("main_s")) { + user.profile_pictures.thumbnails.push_back(profile_img["main_s"].get()); + } + user.profile_pictures.thumbnails.push_back(profile_img.at("main").get()); + user.profile_pictures.original = get_original_profile_picture(user.profile_pictures.thumbnails.back()); + + std::string user_webpage = j.at("user_webpage").get(); + if (!user_webpage.empty()) { + user.links.push_back({"webpage", std::move(user_webpage)}); + } + auto add_social_as_needed = [&](const char* key) { + nlohmann::json social = j.at("social"); + if (!social.is_object()) { + return; + } + if (!social.contains(key)) { + return; + } + + std::string url = social[key].at("url").get(); + user.links.push_back({key, std::move(url)}); + }; + add_social_as_needed("twitter"); + add_social_as_needed("instagram"); + add_social_as_needed("tumblr"); + add_social_as_needed("facebook"); + add_social_as_needed("circlems"); + add_social_as_needed("pawoo"); +} + +static inline std::optional get_original_cover_image(const std::string& thumbnail) { + std::smatch sm; + if (!std::regex_match(thumbnail, sm, cover_image_thumbnail_regex)) { + return std::nullopt; + } + return sm[1].str() + sm[2].str() + sm[3].str(); +} + +static inline std::optional get_original_profile_picture(const std::string& thumbnail) { + std::smatch sm; + if (!std::regex_match(thumbnail, sm, profile_picture_thumbnail_regex)) { + return std::nullopt; + } + return sm[1].str() + sm[2].str() + sm[3].str(); +} + +static inline uint64_t to_ull(const std::string& str) { + char* endptr; + + errno = 0; + unsigned long long res = strtoull(str.c_str(), &endptr, 10); + if (endptr[0] != '\0') { + throw std::invalid_argument(str + " contains trailing data"); + } else if (res == ULLONG_MAX && errno == ERANGE) { + throw std::overflow_error(str + " overflows uint64_t"); + } + + return res; +} diff --git a/pixivclient.h b/pixivclient.h new file mode 100644 index 0000000..7505939 --- /dev/null +++ b/pixivclient.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +#include +#include + +struct Images { + std::optional original; + std::vector thumbnails; +}; + +struct User { + std::string username; + std::string display_name; + uint64_t user_id; + + std::optional cover_images; + Images profile_pictures; + std::vector> links; +}; + +class PixivClient { +public: + PixivClient(); + + User get_user(uint64_t user_id); + +private: + nlohmann::json _handle_result(httplib::Result res); + httplib::Client _www_pixiv_net_client{"https://www.pixiv.net"}; +}; + +class HTTPLibException : public std::exception { +public: + HTTPLibException(httplib::Error error) { + this->_message = httplib::to_string(error); + } + + const constexpr char* what() const noexcept { + return this->_message.c_str(); + } + +private: + std::string _message; +}; + +class PixivException : public std::exception { +public: + PixivException(int status_, std::string message) : status(status_), _message(std::move(message)) {} + + const constexpr char* what() const noexcept { + return this->_message.c_str(); + } + + int status; + +private: + std::string _message; +}; + +void from_json(const nlohmann::json& j, User& user);