207 lines
7.4 KiB
C++
207 lines
7.4 KiB
C++
#include <regex>
|
|
#include <FastHash.h>
|
|
|
|
#include "config.h"
|
|
#include "pixivmodels.h"
|
|
#include "servehelper.h"
|
|
#include "routes/routes.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, Nodes extra_head) {
|
|
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 head("head", {
|
|
Element("meta", {{"charset", "utf-8"}}, {}),
|
|
Element("title", {std::move(title)}),
|
|
Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url) + "?v=" + std::to_string(css_hash)}}, {}),
|
|
Element("meta", {{"name", "viewport"}, {"content", "width=device-width,initial-scale=1"}}, {})
|
|
});
|
|
head.nodes.reserve(head.nodes.size() + extra_head.size());
|
|
head.nodes.insert(head.nodes.end(), extra_head.begin(), extra_head.end());
|
|
std::string html = "<!DOCTYPE html>"s + Element("html", {
|
|
std::move(head),
|
|
std::move(element)
|
|
}).serialize();
|
|
|
|
uint64_t hash = FastHash(html.data(), html.size(), 0);
|
|
res.set_header("ETag", std::string(1, '"') + std::to_string(hash) + '"');
|
|
|
|
if (should_send_304(req, hash)) {
|
|
res.status = 304;
|
|
res.set_header("Content-Length", std::to_string(html.size()));
|
|
res.set_header("Content-Type", "text/html");
|
|
} else {
|
|
res.set_content(std::move(html), "text/html");
|
|
}
|
|
}
|
|
|
|
void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config,
|
|
std::string title, std::optional<std::string> subtitle, std::optional<std::string> 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");
|
|
}
|
|
if (config.canonical_origin) {
|
|
return *config.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") || url.is_host_equal("embed.pixiv.net")) {
|
|
return url.to_string();
|
|
}
|
|
return proxy_url(config.image_proxy_url, std::move(url));
|
|
}
|
|
|
|
bool should_send_304(const httplib::Request& req, uint64_t hash) {
|
|
std::string header = req.get_header_value("If-None-Match");
|
|
if (header == "*") {
|
|
return true;
|
|
}
|
|
|
|
size_t pos = header.find(std::string(1, '"') + std::to_string(hash) + '"');
|
|
return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/');
|
|
}
|
|
|
|
|
|
|
|
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 illusts_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", "illusts_grid-illust"}}, {
|
|
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 ? "illusts_grid-illust_badge" : "illusts_grid-illust_badge 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"
|
|
});
|
|
}
|
|
}
|