From 5c594b6301ae5f61d25ab746aefae005fa7d3054 Mon Sep 17 00:00:00 2001 From: blankie Date: Fri, 7 Apr 2023 22:06:31 +0700 Subject: [PATCH] Add /users/:id/illustrations --- CMakeLists.txt | 2 +- blankie/serializer.cpp | 2 +- main.cpp | 15 +---- routes/css.cpp | 4 ++ routes/routes.h | 1 + routes/users/illustrations.cpp | 113 +++++++++++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 routes/users/illustrations.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3caef15..f53f97e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ add_link_options(${FLAGS}) add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp pixivclient.cpp blankie/serializer.cpp blankie/escape.cpp blankie/murl.cpp - routes/home.cpp routes/css.cpp routes/users/common.cpp routes/users/users.cpp) + routes/home.cpp routes/css.cpp routes/users/common.cpp routes/users/users.cpp routes/users/illustrations.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 diff --git a/blankie/serializer.cpp b/blankie/serializer.cpp index 611aace..7a6a951 100644 --- a/blankie/serializer.cpp +++ b/blankie/serializer.cpp @@ -52,5 +52,5 @@ std::string Element::serialize() const { }; // namespace blankie static inline bool is_autoclosing_tag(const char* tag) { - return !strncmp(tag, "link", 5) || !strncmp(tag, "meta", 5) || !strncmp(tag, "img", 4); + return !strncmp(tag, "link", 5) || !strncmp(tag, "meta", 5) || !strncmp(tag, "img", 4) || !strncmp(tag, "br", 3); } diff --git a/main.cpp b/main.cpp index 66ff979..b67e80f 100644 --- a/main.cpp +++ b/main.cpp @@ -31,6 +31,9 @@ int main(int argc, char** argv) { server.Get("/users/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) { users_route(req, res, config, pixiv_client); }); + server.Get("/users/(\\d+)/illustrations", [&](const httplib::Request& req, httplib::Response& res) { + user_illustrations_route(req, res, config, pixiv_client); + }); server.Get("/member\\.php", [&](const httplib::Request& req, httplib::Response& res) { std::string id = req.get_param_value("id"); @@ -52,18 +55,6 @@ int main(int argc, char** argv) { }); #ifndef NDEBUG - // TODO remove - server.Get("/debug/userillusts", [&](const httplib::Request& req, httplib::Response& res) { - std::vector illusts = pixiv_client.get_illusts(2583663); - std::string output; - - for (uint64_t i : illusts) { - output += std::to_string(i); - output += '\n'; - } - - res.set_content(std::move(output), "text/plain"); - }); server.Get("/debug/exception/known", [](const httplib::Request& req, httplib::Response& res) { throw std::runtime_error("awoo"); }); diff --git a/routes/css.cpp b/routes/css.cpp index 37978f5..b5dcafc 100644 --- a/routes/css.cpp +++ b/routes/css.cpp @@ -51,6 +51,10 @@ void css_route(const httplib::Request& req, httplib::Response& res) { margin-left: .5em; } + .center { + text-align: center; + } + .error { text-align: center; background-color: var(--error-background-color); diff --git a/routes/routes.h b/routes/routes.h index 6041d63..d93cc35 100644 --- a/routes/routes.h +++ b/routes/routes.h @@ -8,3 +8,4 @@ class PixivClient; // forward declaration from ../pixivclient.h void home_route(const httplib::Request& req, httplib::Response& res, const Config& config); void css_route(const httplib::Request& req, httplib::Response& res); void users_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client); +void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client); diff --git a/routes/users/illustrations.cpp b/routes/users/illustrations.cpp new file mode 100644 index 0000000..991da1e --- /dev/null +++ b/routes/users/illustrations.cpp @@ -0,0 +1,113 @@ +#include + +#include "../routes.h" +#include "../../servehelper.h" +#include "../../pixivclient.h" +#include "common.h" + +static inline uint64_t to_ull(const std::string& str); +static Element generate_pager(size_t page, size_t items, size_t items_per_page); +static inline Element generate_content(const std::vector& illust_ids, size_t page, size_t items_per_page); + +static inline size_t page_count(size_t items, size_t items_per_page); +static inline std::pair page_to_offsets(size_t page, size_t items, size_t items_per_page); + +void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) { + uint64_t user_id = to_ull(req.matches[1].str()); + uint64_t page = req.has_param("p") ? to_ull(req.get_param_value("p")) - 1 : 0; + User user; + std::vector illust_ids; + + try { + user = pixiv_client.get_user(user_id); + illust_ids = pixiv_client.get_illusts(user_id); + } catch (const PixivException& e) { + if (e.status == 404) { + res.status = 404; + serve_error(req, res, config, "404: User not found", e.what()); + } else { + res.status = 500; + serve_error(req, res, config, "500: Internal server error", "Failed to fetch user information", e.what()); + } + return; + } catch (const std::exception& e) { + res.status = 500; + serve_error(req, res, config, "500: Internal server error", "Failed to fetch user information", e.what()); + return; + } + + const constexpr size_t items_per_page = 18; // on pixiv touch + Element body("body", { + generate_user_header(std::move(user), config), + generate_pager(page, illust_ids.size(), items_per_page), + Element("br"), + generate_content(std::move(illust_ids), page, items_per_page), + generate_pager(page, illust_ids.size(), items_per_page) + }); + (void)page_to_offsets; + serve(req, res, config, user.display_name + " illustrations", std::move(body)); +} + +static inline uint64_t to_ull(const std::string& str) { + char* endptr; + + errno = 0; + uint64_t res = strtoull(str.c_str(), &endptr, 10); + if (res == ULLONG_MAX && errno == ERANGE) { + throw std::overflow_error(str + " is too big"); + } else if (endptr[0] != '\0') { + throw std::invalid_argument(str + " contains trailing text or is not an integer"); + } + return res; +} + +static Element generate_pager(size_t page, size_t items, size_t items_per_page) { + using namespace std::string_literals; + + size_t total_pages = page_count(items, items_per_page); + auto link = [](std::string href, const char* text, bool add_link) { + Element b("b"); + if (add_link) { + b.nodes.push_back(Element("a", {{"href", std::move(href)}}, {text})); + } else { + b.nodes.push_back(text); + } + return b; + }; + return Element("div", {{"class", "center"}}, { + link("?p=1", "First", page != 0), " ", + link("?p="s + std::to_string(page), "Prev", page != 0), " ", + std::to_string(page + 1), "/", std::to_string(total_pages), " ", + link("?p="s + std::to_string(page + 2), "Next", page + 1 < total_pages), " ", + link("?p="s + std::to_string(total_pages), "Last", page + 1 < total_pages) + }); +} + +static inline Element generate_content(const std::vector& illust_ids, size_t page, size_t items_per_page) { + // TODO be real + Element ul("ul"); + std::pair illust_ids_offsets = page_to_offsets(page, illust_ids.size(), items_per_page); + + ul.nodes.reserve(items_per_page); + for (size_t i = illust_ids_offsets.first; i < illust_ids_offsets.second; i++) { + ul.nodes.push_back(Element("li", { + std::to_string(illust_ids[i]) + })); + } + + return ul; +} + +static inline size_t page_count(size_t items, size_t items_per_page) { + size_t ret = items / items_per_page; + if (items % items_per_page != 0) { + ret++; + } + return ret; +} + +static inline std::pair page_to_offsets(size_t page, size_t items, size_t items_per_page) { + size_t start_offset = page * items_per_page; + size_t end_offset = start_offset + items_per_page; + return {items > start_offset ? start_offset : items, items > end_offset ? end_offset : items}; +}