Try to fetch account information from posts without one

Example of one with: https://dlx.pink/notice/AbtdJkjioOo8ZSdDhw
Example of one without: https://dlx.pink/notice/AbD2kgNviafFEsebqq
This commit is contained in:
blankie 2023-12-04 14:48:21 +11:00
parent 8462aa21bb
commit 8635127ec3
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
4 changed files with 47 additions and 14 deletions

View File

@ -7,6 +7,10 @@
#include "models.h" #include "models.h"
#include "numberhelper.h" #include "numberhelper.h"
#define DOMAIN_RE "(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}"
// https://docs.joinmastodon.org/methods/accounts/#422-unprocessable-entity
#define USERNAME_RE "[a-zA-Z0-9_]+"
using json = nlohmann::json; using json = nlohmann::json;
static time_t parse_rfc3339(const std::string& str); static time_t parse_rfc3339(const std::string& str);
@ -28,15 +32,10 @@ void from_json(const json& j, AccountField& field) {
} }
} }
static std::regex host_regex(R"EOF(https?://([a-z0-9-.]+)/.+)EOF", std::regex::ECMAScript | std::regex::icase); static std::regex host_regex("https?://(" DOMAIN_RE ")/.+", std::regex::ECMAScript | std::regex::icase);
void from_json(const json& j, Account& account) { void from_json(const json& j, Account& account) {
using namespace std::string_literals; using namespace std::string_literals;
// https://dlx.pink/notice/AbtdJkjioOo8ZSdDhw
if (j.size() == 0) {
return;
}
j.at("id").get_to(account.id); j.at("id").get_to(account.id);
j.at("username").get_to(account.username); j.at("username").get_to(account.username);
j.at("display_name").get_to(account.display_name); j.at("display_name").get_to(account.display_name);
@ -117,6 +116,7 @@ void from_json(const json& j, Poll& poll) {
j.at("emojis").get_to(poll.emojis); j.at("emojis").get_to(poll.emojis);
} }
static std::regex akkoma_status_url_regex("https?://(" DOMAIN_RE ")/(?:@|users/)(" USERNAME_RE ")/.+");
void from_json(const json& j, Post& post) { void from_json(const json& j, Post& post) {
j.at("id").get_to(post.id); j.at("id").get_to(post.id);
post.created_at = parse_rfc3339(j.at("created_at").get_ref<const std::string&>()); post.created_at = parse_rfc3339(j.at("created_at").get_ref<const std::string&>());
@ -142,13 +142,30 @@ void from_json(const json& j, Post& post) {
post.reblog = std::make_unique<Post>(); post.reblog = std::make_unique<Post>();
from_json(j["reblog"].get<json>(), *post.reblog.get()); from_json(j["reblog"].get<json>(), *post.reblog.get());
} }
j.at("account").get_to(post.account);
j.at("media_attachments").get_to(post.media_attachments); j.at("media_attachments").get_to(post.media_attachments);
j.at("emojis").get_to(post.emojis); j.at("emojis").get_to(post.emojis);
// https://social.kernel.org/@monsieuricon/Ac6oYwtLhess6uil1c // https://social.kernel.org/@monsieuricon/Ac6oYwtLhess6uil1c
if (j.contains("poll") && !j["poll"].is_null()) { if (j.contains("poll") && !j["poll"].is_null()) {
post.poll = j["poll"].get<Poll>(); post.poll = j["poll"].get<Poll>();
} }
// empty account with username accessible: https://dlx.pink/notice/AbtdJkjioOo8ZSdDhw
// empty account with username inaccesible: https://dlx.pink/notice/AbD2kgNviafFEsebqq
if (j.at("account").size()) {
j["account"].get_to(post.account);
} else {
std::smatch sm;
const std::string& url = j.at("url").get_ref<const std::string&>();
if (!std::regex_match(url, sm, akkoma_status_url_regex)) {
return;
}
post.account = {
.username = sm.str(2),
.server = sm.str(1),
.display_name = sm.str(2),
};
}
} }
void from_json(const json& j, PostContext& context) { void from_json(const json& j, PostContext& context) {

View File

@ -105,7 +105,12 @@ svg {
.post-header span, .post-header time { .post-header span, .post-header time {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
margin-left: 0.5em; }
.post-header img {
margin-right: 0.5em;
}
.post-header time {
padding-left: 1em;
} }
.post-time_header { .post-time_header {
margin-left: auto; margin-left: auto;

View File

@ -33,6 +33,7 @@ void status_route(const httplib::Request& req, httplib::Response& res) {
} }
// https://dlx.pink/notice/AbtdJkjioOo8ZSdDhw // https://dlx.pink/notice/AbtdJkjioOo8ZSdDhw
// https://dlx.pink/notice/AbD2kgNviafFEsebqq
if (post->reblog && !post->reblog->account.id.empty()) { if (post->reblog && !post->reblog->account.id.empty()) {
serve_redirect(req, res, get_origin(req) + '/' + server + "/@" + post->reblog->account.acct(false) + '/' + post->reblog->id, true); serve_redirect(req, res, get_origin(req) + '/' + server + "/@" + post->reblog->account.acct(false) + '/' + post->reblog->id, true);
return; return;

View File

@ -26,7 +26,7 @@ struct PostStatus {
const char* icon_html; const char* icon_html;
Node info_node; Node info_node;
}; };
static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool main_post, const std::optional<PostStatus>& post_status); static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool main_post, const std::optional<PostStatus>& post_status, const Post* reblogged = nullptr);
static inline Element serialize_media(const Media& media); static inline Element serialize_media(const Media& media);
static inline Element serialize_poll(const httplib::Request& req, const Poll& poll); static inline Element serialize_poll(const httplib::Request& req, const Poll& poll);
@ -177,7 +177,7 @@ Element serialize_post(const httplib::Request& req, const std::string& server, c
fa_retweet, fa_retweet,
preprocess_html(req, post.account.emojis, post.account.display_name + " boosted"), preprocess_html(req, post.account.emojis, post.account.display_name + " boosted"),
}; };
return serialize_post(req, server, *post.reblog, main_post, post_status); return serialize_post(req, server, *post.reblog, main_post, post_status, &post);
} else if (pinned) { } else if (pinned) {
PostStatus post_status = { PostStatus post_status = {
fa_thumbtack, fa_thumbtack,
@ -437,9 +437,17 @@ static inline std::vector<lxb_dom_node_t*> emojify(lxb_dom_document_t* document,
return res; return res;
} }
static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool main_post, const std::optional<PostStatus>& post_status) { static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool main_post, const std::optional<PostStatus>& post_status, const Post* reblogged) {
using namespace std::string_literals; using namespace std::string_literals;
bool user_known = !post.account.id.empty();
bool user_ref_known = !post.account.username.empty() && !post.account.server.empty();
// `reblogged == nullptr` since a malicious server could take down the frontend
// by sending a post that is not a reblog with no account information
std::string post_url = user_known || reblogged == nullptr
? get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id + "#m"
: get_origin(req) + '/' + server + "/@" + reblogged->account.acct(false) + '/' + reblogged->id + "#m";
std::string time_title = post.edited_at < 0 std::string time_title = post.edited_at < 0
? full_time(post.created_at) ? full_time(post.created_at)
: "Created: "s + full_time(post.created_at) + "\nEdited: " + full_time(post.edited_at); : "Created: "s + full_time(post.created_at) + "\nEdited: " + full_time(post.edited_at);
@ -478,14 +486,16 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
Element div("div", {{"class", "post"}}, { Element div("div", {{"class", "post"}}, {
Element("div", {{"class", "post-header"}}, { Element("div", {{"class", "post-header"}}, {
!post.account.id.empty() ? Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false)}}, { user_ref_known ? Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false)}}, {
Element("img", {{"class", "post-avatar"}, {"alt", "User profile picture"}, {"loading", "lazy"}, {"src", post.account.avatar_static}}, {}), !post.account.avatar_static.empty()
? Element("img", {{"class", "post-avatar"}, {"alt", "User profile picture"}, {"loading", "lazy"}, {"src", post.account.avatar_static}}, {})
: Node(""),
Element("span", { Element("span", {
Element("b", {preprocess_html(req, post.account.emojis, post.account.display_name)}), Element("b", {preprocess_html(req, post.account.emojis, post.account.display_name)}),
Element("br"), "@", post.account.acct(), Element("br"), "@", post.account.acct(),
}), }),
}) : Element("b", {"Unknown user"}), }) : Element("b", {"Unknown user"}),
Element("a", {{"class", "post-time_header"}, {"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id + "#m"}, {"title", time_title}}, { Element("a", {{"class", "post-time_header"}, {"href", std::move(post_url)}, {"title", time_title}}, {
Element("time", {{"datetime", to_rfc3339(post.created_at)}}, {relative_time(post.created_at, current_time()), time_badge}), Element("time", {{"datetime", to_rfc3339(post.created_at)}}, {relative_time(post.created_at, current_time()), time_badge}),
}), }),
}), }),