coyote/routes/user.cpp

197 lines
8.9 KiB
C++
Raw Normal View History

2023-11-22 08:39:24 +00:00
#include "routes.h"
#include "../servehelper.h"
2023-12-08 07:38:55 +00:00
#include "../htmlhelper.h"
2023-11-22 08:39:24 +00:00
#include "../client.h"
#include "../models.h"
2023-11-23 06:05:17 +00:00
#include "../timeutils.h"
2023-11-22 08:39:24 +00:00
static const char* sorting_method_names[3] = {"Posts", "Posts and replies", "Media"};
static const char* sorting_method_suffixes[3] = {"", "/with_replies", "/media"};
static inline PostSortingMethod get_sorting_method(const std::string& method);
2023-11-23 06:05:17 +00:00
static inline Element user_header(const httplib::Request& req, const std::string& server, const Account& account, PostSortingMethod sorting_method);
2023-11-23 00:16:56 +00:00
static inline Element user_link_field(const httplib::Request& req, const Account& account, const AccountField& field);
2023-11-23 06:05:17 +00:00
static inline Element sorting_method_link(const httplib::Request& req, const std::string& server, const Account& account, PostSortingMethod current_method, PostSortingMethod new_method);
2023-11-22 08:39:24 +00:00
2023-12-02 13:50:33 +00:00
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Account& account, const std::optional<std::string>& max_id, PostSortingMethod sorting_method);
2023-11-22 08:39:24 +00:00
void user_route(const httplib::Request& req, httplib::Response& res) {
2023-11-23 08:49:27 +00:00
using namespace std::string_literals;
2023-11-22 08:39:24 +00:00
std::string server = req.matches.str(1);
std::string username = req.matches.str(2);
PostSortingMethod sorting_method = get_sorting_method(req.matches.str(3));
2023-11-23 08:49:27 +00:00
std::optional<std::string> max_id;
if (req.has_param("max_id")) {
max_id = req.get_param_value("max_id");
}
2023-11-22 08:39:24 +00:00
std::optional<Account> account;
2023-11-23 13:43:55 +00:00
std::vector<Post> pinned_posts, posts;
2023-11-22 08:39:24 +00:00
try {
account = mastodon_client.get_account_by_username(server, username);
2023-11-23 08:49:27 +00:00
if (account) {
2023-11-23 13:43:55 +00:00
if (sorting_method == PostSortingMethod::Posts && !max_id) {
pinned_posts = mastodon_client.get_pinned_posts(server, account->id);
}
posts = mastodon_client.get_posts(server, account->id, sorting_method, max_id);
2023-11-23 08:49:27 +00:00
}
2023-11-22 08:39:24 +00:00
} catch (const std::exception& e) {
res.status = 500;
serve_error(req, res, "500: Internal server error", "Failed to fetch user information", e.what());
return;
}
if (!account) {
res.status = 404;
serve_error(req, res, "404: User not found");
return;
}
Element body("body", {
2023-11-23 06:05:17 +00:00
user_header(req, server, *account, sorting_method),
2023-11-22 08:39:24 +00:00
});
2023-11-23 13:43:55 +00:00
body.nodes.reserve(body.nodes.size() + 2 * (pinned_posts.size() + posts.size()) + 1);
for (const Post& post : pinned_posts) {
body.nodes.push_back(serialize_post(req, server, post, true));
body.nodes.push_back(Element("hr"));
}
2023-11-23 08:49:27 +00:00
for (const Post& post : posts) {
body.nodes.push_back(serialize_post(req, server, post));
body.nodes.push_back(Element("hr"));
}
2023-11-23 13:43:55 +00:00
2023-11-23 08:49:27 +00:00
if (!posts.empty()) {
2023-11-24 11:43:53 +00:00
body.nodes.push_back(Element("a", {{"class", "more_posts"}, {"href", "?max_id="s + posts[posts.size() - 1].id + "#user_posts_nav"}}, {"See more"}));
2023-11-23 13:43:55 +00:00
} else if (max_id) {
2023-11-24 11:43:53 +00:00
body.nodes.push_back(Element("p", {{"class", "more_posts"}}, {"There are no more posts"}));
2023-11-23 08:49:27 +00:00
}
2023-12-02 13:50:33 +00:00
serve(req, res, account->display_name + " (@" + account->acct() + ')', std::move(body), generate_ogp_nodes(req, *account, max_id, sorting_method));
2023-11-22 08:39:24 +00:00
}
static inline PostSortingMethod get_sorting_method(const std::string& method) {
for (size_t i = 0; i < sizeof(sorting_method_suffixes) / sizeof(sorting_method_suffixes[0]); i++) {
if (method == sorting_method_suffixes[i]) {
return static_cast<PostSortingMethod>(i);
}
}
__builtin_unreachable();
}
2023-11-23 06:05:17 +00:00
static inline Element user_header(const httplib::Request& req, const std::string& server, const Account& account, PostSortingMethod sorting_method) {
blankie::html::HTMLString preprocessed_bio = preprocess_html(req, account.server, account.emojis, account.note_html);
// Workaround for https://social.kernel.org/@monsieuricon
if (preprocessed_bio.str.find("<p>") == std::string::npos) {
size_t offset = 0;
while ((offset = preprocessed_bio.str.find('\n', offset)) != std::string::npos) {
preprocessed_bio.str.replace(offset, 1, "<br>");
offset += 4;
}
preprocessed_bio.str.reserve(preprocessed_bio.str.size() + 3 + 4);
preprocessed_bio.str.insert(0, "<p>");
preprocessed_bio.str.append("</p>");
}
2023-11-22 08:39:24 +00:00
Element user_links("table", {{"class", "user_page-user_links"}}, {});
user_links.nodes.reserve(account.fields.size());
for (const AccountField& i : account.fields) {
2023-11-23 00:16:56 +00:00
user_links.nodes.push_back(user_link_field(req, account, i));
2023-11-22 08:39:24 +00:00
}
2023-11-23 23:55:02 +00:00
Node view_on_original = !account.same_server
? Element("span", {" (", Element("a", {{"href", get_origin(req) + '/' + account.server + "/@" + account.username}}, {"View on original instance"}), ")"})
: Node("");
2023-11-22 08:39:24 +00:00
Element header("header", {
Element("a", {{"href", account.header}}, {
2023-11-25 03:53:34 +00:00
Element("img", {{"class", "user_page-header"}, {"alt", "User header"}, {"loading", "lazy"}, {"src", account.header}}, {}),
2023-11-22 08:39:24 +00:00
}),
Element("div", {{"class", "user_page-main_header"}}, {
2023-11-22 08:39:24 +00:00
Element("a", {{"href", account.avatar}}, {
2023-11-25 03:53:34 +00:00
Element("img", {{"class", "user_page-profile"}, {"alt", "User profile picture"}, {"loading", "lazy"}, {"src", account.avatar}}, {}),
2023-11-22 08:39:24 +00:00
}),
Element("span", {{"class", "user_page-main_header_text"}}, {
// left-to-right override--thank https://anarres.family/@alice@mk.nyaa.place
Element("b", {preprocess_html(req, account.emojis, account.display_name)}), "\u202d", account.bot ? " (bot)" : "", " (@", account.acct(), ")", view_on_original,
2023-11-22 08:39:24 +00:00
Element("br"),
2023-11-23 06:05:17 +00:00
Element("br"), Element("b", {"Joined: "}), short_time(account.created_at),
2023-11-22 08:39:24 +00:00
Element("br"),
Element("b", {std::to_string(account.statuses_count)}), " Posts", " / ",
Element("b", {std::to_string(account.following_count)}), " Following", " / ",
// https://chaosfem.tw/@Terra
Element("b", {account.followers_count >= 0 ? std::to_string(account.followers_count) : "???"}), " Followers",
2023-11-22 08:39:24 +00:00
}),
}),
Element("div", {{"class", "user_page-user_description"}}, {
Element("div", {{"class", "user_page-user_bio"}}, {std::move(preprocessed_bio)}),
2023-11-22 08:39:24 +00:00
std::move(user_links),
}),
2023-11-23 08:49:27 +00:00
Element("nav", {{"class", "user_page-user_posts_nav"}, {"id", "user_posts_nav"}}, {
2023-11-23 06:05:17 +00:00
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::Posts),
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::PostsAndReplies),
2023-11-23 12:20:49 +00:00
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::MediaOnly),
2023-11-22 08:39:24 +00:00
}),
});
return header;
}
2023-11-23 00:16:56 +00:00
static inline Element user_link_field(const httplib::Request& req, const Account& account, const AccountField& field) {
2023-11-22 08:39:24 +00:00
using namespace std::string_literals;
Element tr("tr", {
Element("th", {preprocess_html(req, account.emojis, field.name)}),
2023-11-23 06:05:17 +00:00
Element("td", {preprocess_html(req, account.server, account.emojis, field.value_html)}),
2023-11-22 08:39:24 +00:00
});
if (field.verified_at >= 0) {
2023-11-23 06:05:17 +00:00
tr.attributes = {{"class", "verified"}, {"title", "Verified at "s + full_time(field.verified_at)}};
2023-11-22 08:39:24 +00:00
}
return tr;
}
2023-11-23 06:05:17 +00:00
static inline Element sorting_method_link(const httplib::Request& req, const std::string& server, const Account& account, PostSortingMethod current_method, PostSortingMethod new_method) {
2023-11-22 08:39:24 +00:00
const char* method_name = sorting_method_names[new_method];
2023-11-23 08:49:27 +00:00
Element a("a", {{"href", get_origin(req) + '/' + server + "/@" + account.acct(false) + sorting_method_suffixes[new_method] + "#user_posts_nav"}}, {
method_name,
});
2023-11-22 08:39:24 +00:00
if (current_method == new_method) {
2023-11-23 08:49:27 +00:00
a.attributes.push_back({"class", "selected"});
2023-11-22 08:39:24 +00:00
}
2023-11-23 08:49:27 +00:00
return a;
2023-11-22 08:39:24 +00:00
}
2023-12-02 13:50:33 +00:00
static inline Nodes generate_ogp_nodes(const httplib::Request& req, const Account& account, const std::optional<std::string>& max_id, PostSortingMethod sorting_method) {
std::string url = get_origin(req) + '/' + account.server + "/@" + account.acct(false) + sorting_method_suffixes[sorting_method];
if (max_id) {
url += "?max_id=";
url += *max_id;
}
std::string note = get_text_content(account.note_html);
Nodes nodes({
// left-to-right override--thank https://anarres.family/@alice@mk.nyaa.place
Element("meta", {{"property", "og:title"}, {"content", account.display_name + "\u202d (@" + account.acct() + ')'}}, {}),
Element("meta", {{"property", "og:type"}, {"content", "website"}}, {}),
Element("meta", {{"property", "og:site_name"}, {"content", "Coyote"}}, {}),
Element("meta", {{"property", "og:url"}, {"content", std::move(url)}}, {}),
Element("meta", {{"property", "og:image"}, {"content", account.avatar}}, {}),
});
if (!note.empty()) {
nodes.push_back(Element("meta", {{"property", "og:description"}, {"content", std::move(note)}}, {}));
}
return nodes;
}