#include #include "config.h" #include "pixivmodels.h" #include "servehelper.h" static Element generate_pager(const Illusts& illusts, size_t page, const char* id); static inline Element generate_illusts_grid(const httplib::Request& req, const Config& config, const Illusts& illusts); static inline Element generate_illusts_grid_item(const httplib::Request& req, const Config& config, const Illust& illust); static inline Element generate_illust_badge(const Illust& illust, const std::string& illust_url); void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element) { using namespace std::string_literals; std::string css_url = get_origin(req, config) + "/style.css"; res.set_header("Content-Security-Policy", "default-src 'none'; style-src "s + css_url + "; img-src https://s.pximg.net " + config.image_proxy_url.get_origin()); Element html("html", { Element("head", { Element("meta", {{"charset", "utf-8"}}, {}), Element("title", {std::move(title)}), Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url)}}, {}), Element("meta", {{"name", "viewport"}, {"content", "width=device-width,initial-scale=1"}}, {}) }), std::move(element) }); res.set_content(""s + html.serialize(), "text/html"); } void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, std::optional subtitle, std::optional info) { Element error_div("div", {{"class", "error"}}, { Element("h2", {title}) }); if (subtitle) { error_div.nodes.push_back(Element("p", { std::move(*subtitle) })); } if (info) { error_div.nodes.push_back(Element("pre", { Element("code", {std::move(*info)}) })); } Element body("body", {std::move(error_div)}); serve(req, res, config, std::move(title), std::move(body)); } void serve_redirect(const httplib::Request& req, httplib::Response& res, const Config& config, std::string url) { using namespace std::string_literals; Element body("body", { "Redirecting to ", Element("a", {{"href", url}}, {url}), "…" }); res.set_redirect(url); serve(req, res, config, "Redirecting to "s + std::move(url) + "…", std::move(body)); } std::string get_origin(const httplib::Request& req, const Config& config) { if (req.has_header("X-Canonical-Origin")) { return req.get_header_value("X-Canonical-Origin"); } std::string origin = "http://"; if (req.has_header("Host")) { origin += req.get_header_value("Host"); } else { origin += config.bind_host; if (config.bind_port != 80) { origin += ':' + std::to_string(config.bind_port); } } return origin; } std::string proxy_url(blankie::murl::Url base, blankie::murl::Url url) { if (!url.path.empty() && url.path[0] != '/') { base.path += '/'; } base.path += blankie::murl::normalize_path(std::move(url.path)); if (!base.query.empty() && !url.query.empty()) { base.query += '&'; } base.query += std::move(url.query); base.fragment = std::move(url.fragment); return base.to_string(); } std::string proxy_image_url(const Config& config, blankie::murl::Url url) { if (url.is_host_equal("s.pximg.net")) { return url.to_string(); } return proxy_url(config.image_proxy_url, std::move(url)); } Element generate_illusts_pager(const httplib::Request& req, const Config& config, const Illusts& illusts, size_t page, const char* id) { return Element("div", {{"id", id}}, { generate_pager(illusts, page, id), Element("br"), generate_illusts_grid(req, config, illusts), generate_pager(illusts, page, id) }); } static Element generate_pager(const Illusts& illusts, size_t page, const char* id) { auto link = [&](size_t new_page, const char* text, bool add_link) { using namespace std::string_literals; Element b("b"); if (add_link) { std::string href = "?p="s + std::to_string(new_page) + '#' + id; 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(1, "First", page != 0), " ", link(page, "Prev", page != 0), " ", std::to_string(page + 1), "/", std::to_string(illusts.total_pages), " ", link(page + 2, "Next", page + 1 < illusts.total_pages), " ", link(illusts.total_pages, "Last", page + 1 < illusts.total_pages) }); } static inline Element generate_illusts_grid(const httplib::Request& req, const Config& config, const Illusts& illusts) { Element div("div", {{"class", "grid"}}, {}); div.nodes.reserve(illusts.illusts.size()); for (const Illust& i : illusts.illusts) { div.nodes.push_back(generate_illusts_grid_item(req, config, i)); } return div; } static inline Element generate_illusts_grid_item(const httplib::Request& req, const Config& config, const Illust& illust) { std::string illust_url = get_origin(req, config) + "/artworks/" + std::to_string(illust.illust_id); std::string image_url = proxy_image_url(config, illust.images[0].thumbnail_or_original(1).url); Element div("div", {{"class", "illustsgriditem"}}, { Element("a", {{"href", illust_url}}, { Element("img", {{"loading", "lazy"}, {"src", std::move(image_url)}}, {}), Element("p", {illust.title}) }) }); if (illust.page_count > 1 || illust.ai_generated) { div.nodes.push_back(generate_illust_badge(illust, std::move(illust_url))); } return div; } static inline Element generate_illust_badge(const Illust& illust, const std::string& illust_url) { const char* css_class = !illust.ai_generated ? "illustbadge" : "illustbadge ai"; if (illust.page_count > 1) { return Element("a", {{"class", css_class}, {"href", illust_url + "?preview=1"}}, { std::to_string(illust.page_count), " pages" }); } else { return Element("span", {{"class", css_class}}, { "AI" }); } }