Compare commits
3 Commits
42da969ac5
...
fd020ed756
Author | SHA1 | Date |
---|---|---|
|
fd020ed756 | |
|
ee03ab01d8 | |
|
6aeba6acb6 |
|
@ -16,6 +16,7 @@ liking. Here's a list of what they are:
|
|||
- `bind_host` (string): What address to bind to
|
||||
- `bind_port` (zero or positive integer): What port to bind to
|
||||
- `image_proxy_url` (string): URL to proxy images to (see https://pixiv.cat/reverseproxy.html), a trailing slash is not needed
|
||||
- `canonical_origin` (string or null): A fallback canonical origin if set, useful if you're, say, running Pixwhile behind Ngrok
|
||||
|
||||
Pixwhile is intended to be run behind a reverse proxy (e.g. Nginx), so you
|
||||
should set your reverse proxy to proxy requests to
|
||||
|
|
|
@ -17,4 +17,7 @@ void from_json(const nlohmann::json& j, Config& config) {
|
|||
throw std::invalid_argument("Invalid port to bind to: "s + std::to_string(config.bind_port));
|
||||
}
|
||||
config.image_proxy_url = j.at("image_proxy_url").get<std::string>();
|
||||
if (j.contains("canonical_origin") && j["canonical_origin"].is_string()) {
|
||||
config.canonical_origin = j["canonical_origin"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
|
1
config.h
1
config.h
|
@ -8,6 +8,7 @@ struct Config {
|
|||
std::string bind_host = "127.0.0.1";
|
||||
int bind_port = 8080;
|
||||
blankie::murl::Url image_proxy_url{"https://i.pixiv.cat"};
|
||||
std::optional<std::string> canonical_origin;
|
||||
};
|
||||
|
||||
Config load_config(const char* path);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"bind_host": "127.0.0.1",
|
||||
"bind_port": 8080,
|
||||
"image_proxy_url": "https://i.pixiv.cat"
|
||||
"image_proxy_url": "https://i.pixiv.cat",
|
||||
"canonical_origin": null
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ const Image& Images::thumbnail_or_original(size_t back) const {
|
|||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, User& user) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
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<const nlohmann::json::string_t&>());
|
||||
|
@ -79,6 +81,11 @@ void from_json(const nlohmann::json& j, User& user) {
|
|||
add_social_as_needed("circlems", "Circle.ms");
|
||||
add_social_as_needed("pawoo", "Pawoo");
|
||||
}
|
||||
|
||||
blankie::murl::Url ogp_image = j.at("meta").at("ogp").at("image").get<std::string>();
|
||||
if (ogp_image.is_host_equal("embed.pixiv.net")) {
|
||||
user.ogp_image = "https://embed.pixiv.net/user_profile.php?id="s + std::to_string(user.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, Tag& tag) {
|
||||
|
|
|
@ -31,6 +31,7 @@ struct User {
|
|||
std::optional<Images> cover_images;
|
||||
Images profile_pictures;
|
||||
std::vector<std::pair<std::string, std::string>> links;
|
||||
std::optional<std::string> ogp_image;
|
||||
};
|
||||
|
||||
struct Tag {
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
static inline Element generate_user_link(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||
static inline Element generate_images(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||
static inline Element generate_preview_images(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||
static inline std::vector<blankie::html::Node> parse_description_line(const httplib::Request& req, const Config& config, std::string str);
|
||||
static inline Nodes parse_description_line(const httplib::Request& req, const Config& config, std::string str);
|
||||
static inline Element generate_description(const httplib::Request& req, const Config& config, const std::string& description);
|
||||
static inline Element generate_illust_tags(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||
static inline std::string generate_description_text(const httplib::Request& req, const Config& config, std::string description);
|
||||
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||
|
||||
static inline bool is_true(const std::string& str);
|
||||
static inline std::string time_to_string(time_t time);
|
||||
|
@ -51,7 +53,7 @@ void artworks_route(const httplib::Request& req, httplib::Response& res, const C
|
|||
}
|
||||
body.nodes.push_back(generate_illust_tags(req, config, illust));
|
||||
body.nodes.push_back(Element("p", {time_to_string(illust.upload_time)}));
|
||||
serve(req, res, config, std::move(illust.title), std::move(body));
|
||||
serve(req, res, config, illust.title, std::move(body), generate_ogp_nodes(req, config, illust));
|
||||
}
|
||||
|
||||
static inline Element generate_user_link(const httplib::Request& req, const Config& config, const Illust& illust) {
|
||||
|
@ -120,8 +122,8 @@ static inline Element generate_preview_images(const httplib::Request& req, const
|
|||
return div;
|
||||
}
|
||||
|
||||
static inline std::vector<blankie::html::Node> parse_description_line(const httplib::Request& req, const Config& config, std::string str) {
|
||||
std::vector<blankie::html::Node> nodes;
|
||||
static inline Nodes parse_description_line(const httplib::Request& req, const Config& config, std::string str) {
|
||||
Nodes nodes;
|
||||
std::smatch sm;
|
||||
|
||||
while (std::regex_search(str, sm, blankie::murl::full_url_regex)) {
|
||||
|
@ -160,7 +162,7 @@ static inline Element generate_description(const httplib::Request& req, const Co
|
|||
if (!p.nodes.empty()) {
|
||||
p.nodes.push_back(Element("br"));
|
||||
}
|
||||
std::vector<blankie::html::Node> nodes = parse_description_line(req, config, std::move(str));
|
||||
Nodes nodes = parse_description_line(req, config, std::move(str));
|
||||
p.nodes.insert(p.nodes.end(), nodes.begin(), nodes.end());
|
||||
};
|
||||
|
||||
|
@ -196,6 +198,61 @@ static inline Element generate_illust_tags(const httplib::Request& req, const Co
|
|||
return div;
|
||||
}
|
||||
|
||||
static inline std::string generate_description_text(const httplib::Request& req, const Config& config, std::string description) {
|
||||
std::string new_description;
|
||||
std::smatch sm;
|
||||
|
||||
new_description.reserve(description.size());
|
||||
while (std::regex_search(description, sm, blankie::murl::full_url_regex)) {
|
||||
std::string prefix = sm.prefix();
|
||||
std::string url_str = sm.str(0);
|
||||
std::string suffix = sm.suffix();
|
||||
|
||||
if (prefix.ends_with('(') && url_str.ends_with(')')) {
|
||||
url_str.pop_back();
|
||||
suffix.insert(0, 1, ')');
|
||||
}
|
||||
new_description += std::move(prefix);
|
||||
|
||||
blankie::murl::Url url(std::move(url_str));
|
||||
url_str = url.is_host_equal("pixiv.net") || url.is_host_equal("www.pixiv.net")
|
||||
? proxy_pixiv_url(req, config, std::move(url))
|
||||
: url.to_string();
|
||||
new_description += std::move(url_str);
|
||||
|
||||
description = std::move(suffix);
|
||||
}
|
||||
new_description += std::move(description);
|
||||
|
||||
return new_description;
|
||||
}
|
||||
|
||||
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Config& config, const Illust& illust) {
|
||||
Nodes nodes({
|
||||
Element("meta", {{"property", "og:title"}, {"content", illust.title}}, {}),
|
||||
Element("meta", {{"property", "og:type"}, {"content", "photo"}}, {}),
|
||||
Element("meta", {{"property", "og:site_name"}, {"content", "Pixwhile"}}, {}),
|
||||
Element("meta", {{"property", "og:url"}, {"content", get_origin(req, config) + "/artworks/" + std::to_string(illust.illust_id)}}, {})
|
||||
});
|
||||
if (illust.comment) {
|
||||
nodes.push_back(Element("meta", {{"property", "og:description"}, {"content", generate_description_text(req, config, *illust.comment)}}, {}));
|
||||
}
|
||||
|
||||
// i don't even know what multiple og:images do anymore
|
||||
// https://stackoverflow.com/questions/13424780/facebook-multiple-ogimage-tags-which-is-default
|
||||
nodes.reserve(nodes.size() + illust.images.size() * 3);
|
||||
for (const Images& images : illust.images) {
|
||||
const Image& image = images.thumbnail_or_original();
|
||||
nodes.push_back(Element("meta", {{"property", "og:image"}, {"content", proxy_image_url(config, image.url)}}, {}));
|
||||
if (image.size) {
|
||||
nodes.push_back(Element("meta", {{"property", "og:image:width"}, {"content", std::to_string(image.size->first)}}, {}));
|
||||
nodes.push_back(Element("meta", {{"property", "og:image:height"}, {"content", std::to_string(image.size->second)}}, {}));
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
static inline bool is_true(const std::string& str) {
|
||||
return !str.empty() && str != "0" && str != "false" && str != "no";
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ static inline Element generate_header(const httplib::Request& req, const Config&
|
|||
|
||||
static inline Element generate_search_suggestions(const httplib::Request& req, const Config& config,
|
||||
const std::vector<std::string>& tags, const std::vector<SearchSuggestion>& search_suggestions, bool open_by_default) {
|
||||
std::vector<blankie::html::Node> ul_nodes;
|
||||
Nodes ul_nodes;
|
||||
ul_nodes.reserve(search_suggestions.size());
|
||||
for (const SearchSuggestion& search_suggestion : search_suggestions) {
|
||||
std::string text = search_suggestion.tag;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "../../pixivclient.h"
|
||||
#include "common.h"
|
||||
|
||||
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Config& config, const User& user);
|
||||
|
||||
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;
|
||||
|
@ -34,5 +36,26 @@ void user_illustrations_route(const httplib::Request& req, httplib::Response& re
|
|||
generate_user_header(std::move(user), config),
|
||||
generate_illusts_pager(req, config, illusts, page, "illusts")
|
||||
});
|
||||
serve(req, res, config, user.display_name + "'s illustrations", std::move(body));
|
||||
serve(req, res, config, user.display_name + "'s illustrations", std::move(body), generate_ogp_nodes(req, config, user));
|
||||
}
|
||||
|
||||
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Config& config, const User& user) {
|
||||
Nodes nodes({
|
||||
Element("meta", {{"property", "og:title"}, {"content", user.display_name + " (@" + user.username + ')'}}, {}),
|
||||
Element("meta", {{"property", "og:type"}, {"content", user.ogp_image ? "photo" : "website"}}, {}),
|
||||
Element("meta", {{"property", "og:site_name"}, {"content", "Pixwhile"}}, {}),
|
||||
Element("meta", {{"property", "og:url"}, {"content", get_origin(req, config) + "/users/" + std::to_string(user.user_id) + "/illustrations"}}, {})
|
||||
});
|
||||
if (user.ogp_image) {
|
||||
nodes.push_back(Element("meta", {{"property", "og:image"}, {"content", proxy_image_url(config, *user.ogp_image)}}, {}));
|
||||
} else {
|
||||
const Image& image = user.profile_pictures.thumbnail_or_original();
|
||||
nodes.push_back(Element("meta", {{"property", "og:image"}, {"content", proxy_image_url(config, image.url)}}, {}));
|
||||
if (image.size) {
|
||||
nodes.push_back(Element("meta", {{"property", "og:image:width"}, {"content", std::to_string(image.size->first)}}, {}));
|
||||
nodes.push_back(Element("meta", {{"property", "og:image:height"}, {"content", std::to_string(image.size->second)}}, {}));
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
|
|
@ -11,20 +11,23 @@ static inline Element generate_illusts_grid(const httplib::Request& req, const C
|
|||
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) {
|
||||
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", {
|
||||
Element("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"}}, {})
|
||||
}),
|
||||
std::move(head),
|
||||
std::move(element)
|
||||
}).serialize();
|
||||
|
||||
|
@ -79,6 +82,9 @@ 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")) {
|
||||
|
@ -106,7 +112,7 @@ std::string proxy_url(blankie::murl::Url base, blankie::murl::Url url) {
|
|||
}
|
||||
|
||||
std::string proxy_image_url(const Config& config, blankie::murl::Url url) {
|
||||
if (url.is_host_equal("s.pximg.net")) {
|
||||
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));
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
struct Config; // forward declaration from config.h
|
||||
struct Illusts; // forward declaration from pixivmodels.h
|
||||
using Element = blankie::html::Element;
|
||||
using Node = blankie::html::Node;
|
||||
using Nodes = std::vector<Node>;
|
||||
|
||||
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element);
|
||||
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element, Nodes extra_head = {});
|
||||
void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config,
|
||||
std::string title, std::optional<std::string> subtitle = std::nullopt, std::optional<std::string> info = std::nullopt);
|
||||
void serve_redirect(const httplib::Request& req, httplib::Response& res, const Config& config, std::string url);
|
||||
|
|
Loading…
Reference in New Issue