135 lines
5.5 KiB
C++
135 lines
5.5 KiB
C++
#include "routes.h"
|
|
#include "../lxb_wrapper.h"
|
|
#include "../servehelper.h"
|
|
#include "../client.h"
|
|
#include "../models.h"
|
|
|
|
static inline std::string make_title(const Post& post);
|
|
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Post& post, const std::string& server);
|
|
static inline void generate_media_ogp_nodes(Nodes& nodes, const Media& media, bool* has_video, bool* has_image);
|
|
|
|
|
|
void status_route(const httplib::Request& req, httplib::Response& res) {
|
|
std::string server = req.matches.str(1);
|
|
std::string id = req.matches.str(2);
|
|
|
|
std::optional<Post> post;
|
|
PostContext context;
|
|
try {
|
|
post = mastodon_client.get_post(server, id);
|
|
if (post) {
|
|
context = mastodon_client.get_post_context(server, id);
|
|
}
|
|
} catch (const std::exception& e) {
|
|
res.status = 500;
|
|
serve_error(req, res, "500: Internal server error", "Failed to fetch post information", e.what());
|
|
return;
|
|
}
|
|
|
|
if (!post) {
|
|
res.status = 404;
|
|
serve_error(req, res, "404: Post not found");
|
|
return;
|
|
}
|
|
|
|
if (post->reblog) {
|
|
serve_redirect(req, res, get_origin(req) + '/' + server + "/@" + post->reblog->account.acct(false) + '/' + post->reblog->id, true);
|
|
return;
|
|
}
|
|
|
|
Element body("body");
|
|
body.nodes.reserve(context.ancestors.size() * 2 + 1 + context.descendants.size() * 2);
|
|
|
|
for (const Post& i : context.ancestors) {
|
|
body.nodes.push_back(serialize_post(req, server, i));
|
|
body.nodes.push_back(Element("hr"));
|
|
}
|
|
body.nodes.push_back(serialize_post(req, server, *post, false, true));
|
|
for (const Post& i : context.descendants) {
|
|
body.nodes.push_back(Element("hr"));
|
|
body.nodes.push_back(serialize_post(req, server, i));
|
|
}
|
|
|
|
serve(req, res, make_title(*post), std::move(body), generate_ogp_nodes(req, *post, server));
|
|
}
|
|
|
|
|
|
static inline std::string make_title(const Post& post) {
|
|
LXB::HTML::Document document(post.content.str);
|
|
size_t content_len;
|
|
const char* content = reinterpret_cast<const char*>(lxb_dom_node_text_content(document.body(), &content_len));
|
|
|
|
std::string title = post.account.display_name + " (@" + post.account.acct() + "): ";
|
|
if (content_len) {
|
|
title.append(content, content_len > 50 ? 50 : content_len);
|
|
if (content_len > 50) {
|
|
title += "…";
|
|
}
|
|
} else {
|
|
title += "Post";
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Post& post, const std::string& server) {
|
|
using namespace std::string_literals;
|
|
std::string url = get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id;
|
|
bool has_video = false, has_image = false;
|
|
|
|
Nodes nodes({
|
|
// left-to-right override--thank https://anarres.family/@alice@mk.nyaa.place
|
|
Element("meta", {{"property", "og:title"}, {"content", post.account.display_name + "\u202d (@" + post.account.acct() + ')'}}, {}),
|
|
Element("meta", {{"property", "og:site_name"}, {"content", "Coyote"}}, {}),
|
|
Element("meta", {{"property", "og:url"}, {"content", std::move(url)}}, {}),
|
|
});
|
|
if (!post.sensitive) {
|
|
nodes.push_back(Element("meta", {{"property", "og:description"}, {"content", get_text_content(post.content)}}, {}));
|
|
|
|
for (const Media& media : post.media_attachments) {
|
|
generate_media_ogp_nodes(nodes, media, &has_video, &has_image);
|
|
}
|
|
} else if (!post.spoiler_text.empty()) {
|
|
nodes.push_back(Element("meta", {{"property", "og:description"}, {"content", "CW: "s + post.spoiler_text}}, {}));
|
|
}
|
|
|
|
const char* type = !post.sensitive && has_video
|
|
? "video"
|
|
: !post.sensitive && has_image ? "photo" : "article";
|
|
nodes.push_back(Element("meta", {{"property", "og:type"}, {"content", type}}, {}));
|
|
|
|
return nodes;
|
|
}
|
|
|
|
static inline void generate_media_ogp_nodes(Nodes& nodes, const Media& media, bool* has_video, bool* has_image) {
|
|
if (media.type == "image") {
|
|
*has_image = true;
|
|
nodes.push_back(Element("meta", {{"property", "og:image"}, {"content", media.preview_url.value_or(media.url)}}, {}));
|
|
|
|
std::optional<Size> size = media.preview_size ? media.preview_size : media.size;
|
|
if (size) {
|
|
nodes.push_back(Element("meta", {{"property", "og:image:width"}, {"content", std::to_string(size->width)}}, {}));
|
|
nodes.push_back(Element("meta", {{"property", "og:image:height"}, {"content", std::to_string(size->height)}}, {}));
|
|
}
|
|
|
|
if (media.description) {
|
|
nodes.push_back(Element("meta", {{"property", "og:image:alt"}, {"content", *media.description}}, {}));
|
|
}
|
|
} else if (media.type == "video" || media.type == "gifv") {
|
|
*has_video = true;
|
|
nodes.push_back(Element("meta", {{"property", "og:video"}, {"content", media.preview_url.value_or(media.url)}}, {}));
|
|
|
|
std::optional<Size> size = media.preview_size ? media.preview_size : media.size;
|
|
if (size) {
|
|
nodes.push_back(Element("meta", {{"property", "og:video:width"}, {"content", std::to_string(size->width)}}, {}));
|
|
nodes.push_back(Element("meta", {{"property", "og:video:height"}, {"content", std::to_string(size->height)}}, {}));
|
|
}
|
|
|
|
if (media.description) {
|
|
nodes.push_back(Element("meta", {{"property", "og:video:alt"}, {"content", *media.description}}, {}));
|
|
}
|
|
} else if (media.type == "audio") {
|
|
nodes.push_back(Element("meta", {{"property", "og:audio"}, {"content", media.url}}, {}));
|
|
}
|
|
}
|