From ee03ab01d847ed519953e8c83496eb3b4632f554 Mon Sep 17 00:00:00 2001 From: blankie Date: Sun, 4 Jun 2023 13:22:19 +0700 Subject: [PATCH] Add OGP metadata for artworks --- routes/artworks.cpp | 67 +++++++++++++++++++++++++++++++++++++++++---- routes/tags.cpp | 2 +- servehelper.cpp | 17 +++++++----- servehelper.h | 4 ++- 4 files changed, 76 insertions(+), 14 deletions(-) diff --git a/routes/artworks.cpp b/routes/artworks.cpp index 11a2781..2b1bda8 100644 --- a/routes/artworks.cpp +++ b/routes/artworks.cpp @@ -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 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 parse_description_line(const httplib::Request& req, const Config& config, std::string str) { - std::vector 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 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"; } diff --git a/routes/tags.cpp b/routes/tags.cpp index e2f9fac..ecb0ed8 100644 --- a/routes/tags.cpp +++ b/routes/tags.cpp @@ -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& tags, const std::vector& search_suggestions, bool open_by_default) { - std::vector ul_nodes; + Nodes ul_nodes; ul_nodes.reserve(search_suggestions.size()); for (const SearchSuggestion& search_suggestion : search_suggestions) { std::string text = search_suggestion.tag; diff --git a/servehelper.cpp b/servehelper.cpp index 3f8007a..9cabd2e 100644 --- a/servehelper.cpp +++ b/servehelper.cpp @@ -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 = ""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(); diff --git a/servehelper.h b/servehelper.h index 76e6a56..b1d4ff1 100644 --- a/servehelper.h +++ b/servehelper.h @@ -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; -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 subtitle = std::nullopt, std::optional info = std::nullopt); void serve_redirect(const httplib::Request& req, httplib::Response& res, const Config& config, std::string url);