Add post context support
This commit is contained in:
parent
e861ab7ba6
commit
452ac756e6
71
client.cpp
71
client.cpp
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
MastodonClient mastodon_client;
|
MastodonClient mastodon_client;
|
||||||
|
|
||||||
|
static void handle_post_server(Post& post, const std::string& host);
|
||||||
static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp);
|
static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp);
|
||||||
static void share_unlock(CURL* curl, curl_lock_data data, void* clientp);
|
static void share_unlock(CURL* curl, curl_lock_data data, void* clientp);
|
||||||
static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata);
|
static size_t curl_write_cb(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||||
|
@ -82,26 +83,6 @@ std::optional<Account> MastodonClient::get_account_by_username(const std::string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Post> MastodonClient::get_post(const std::string& host, const std::string& id) {
|
|
||||||
using namespace std::string_literals;
|
|
||||||
|
|
||||||
try {
|
|
||||||
std::string resp = this->_send_request("https://"s + host + "/api/v1/statuses/" + id);
|
|
||||||
Post post = nlohmann::json::parse(std::move(resp));
|
|
||||||
post.account.same_server = host == post.account.server;
|
|
||||||
if (post.reblog) {
|
|
||||||
post.reblog->account.same_server = host == post.reblog->account.server;
|
|
||||||
}
|
|
||||||
return post;
|
|
||||||
} catch (const CurlException& e) {
|
|
||||||
if (e.code != CURLE_HTTP_RETURNED_ERROR || this->_response_status_code() != 404) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Post> MastodonClient::get_pinned_posts(const std::string& host, const std::string& account_id) {
|
std::vector<Post> MastodonClient::get_pinned_posts(const std::string& host, const std::string& account_id) {
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
@ -109,10 +90,7 @@ std::vector<Post> MastodonClient::get_pinned_posts(const std::string& host, cons
|
||||||
std::vector<Post> posts = nlohmann::json::parse(std::move(resp));
|
std::vector<Post> posts = nlohmann::json::parse(std::move(resp));
|
||||||
|
|
||||||
for (Post& post : posts) {
|
for (Post& post : posts) {
|
||||||
post.account.same_server = host == post.account.server;
|
handle_post_server(post, host);
|
||||||
if (post.reblog) {
|
|
||||||
post.reblog->account.same_server = host == post.reblog->account.server;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return posts;
|
return posts;
|
||||||
|
@ -140,15 +118,45 @@ std::vector<Post> MastodonClient::get_posts(const std::string& host, const std::
|
||||||
std::vector<Post> posts = nlohmann::json::parse(std::move(resp));
|
std::vector<Post> posts = nlohmann::json::parse(std::move(resp));
|
||||||
|
|
||||||
for (Post& post : posts) {
|
for (Post& post : posts) {
|
||||||
post.account.same_server = host == post.account.server;
|
handle_post_server(post, host);
|
||||||
if (post.reblog) {
|
|
||||||
post.reblog->account.same_server = host == post.reblog->account.server;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<Post> MastodonClient::get_post(const std::string& host, const std::string& id) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string resp = this->_send_request("https://"s + host + "/api/v1/statuses/" + id);
|
||||||
|
Post post = nlohmann::json::parse(std::move(resp));
|
||||||
|
handle_post_server(post, host);
|
||||||
|
return post;
|
||||||
|
} catch (const CurlException& e) {
|
||||||
|
if (e.code != CURLE_HTTP_RETURNED_ERROR || this->_response_status_code() != 404) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostContext MastodonClient::get_post_context(const std::string& host, const std::string& id) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
std::string resp = this->_send_request("https://"s + host + "/api/v1/statuses/" + id + "/context");
|
||||||
|
PostContext context = nlohmann::json::parse(std::move(resp));
|
||||||
|
|
||||||
|
for (Post& post : context.ancestors) {
|
||||||
|
handle_post_server(post, host);
|
||||||
|
}
|
||||||
|
for (Post& post : context.descendants) {
|
||||||
|
handle_post_server(post, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
CURL* MastodonClient::_get_easy() {
|
CURL* MastodonClient::_get_easy() {
|
||||||
CURL* curl = pthread_getspecific(this->_easy_key);
|
CURL* curl = pthread_getspecific(this->_easy_key);
|
||||||
if (!curl) {
|
if (!curl) {
|
||||||
|
@ -203,6 +211,13 @@ long MastodonClient::_response_status_code() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void handle_post_server(Post& post, const std::string& host) {
|
||||||
|
post.account.same_server = host == post.account.server;
|
||||||
|
if (post.reblog) {
|
||||||
|
post.reblog->account.same_server = host == post.reblog->account.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp) {
|
static void share_lock(CURL* curl, curl_lock_data data, curl_lock_access access, void* clientp) {
|
||||||
(void)curl;
|
(void)curl;
|
||||||
(void)access;
|
(void)access;
|
||||||
|
|
4
client.h
4
client.h
|
@ -50,10 +50,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Account> get_account_by_username(const std::string& host, const std::string& username);
|
std::optional<Account> get_account_by_username(const std::string& host, const std::string& username);
|
||||||
std::optional<Post> get_post(const std::string& host, const std::string& id);
|
|
||||||
std::vector<Post> get_pinned_posts(const std::string& host, const std::string& account_id);
|
std::vector<Post> get_pinned_posts(const std::string& host, const std::string& account_id);
|
||||||
std::vector<Post> get_posts(const std::string& host, const std::string& account_id, PostSortingMethod sorting_method, std::optional<std::string> max_id);
|
std::vector<Post> get_posts(const std::string& host, const std::string& account_id, PostSortingMethod sorting_method, std::optional<std::string> max_id);
|
||||||
|
|
||||||
|
std::optional<Post> get_post(const std::string& host, const std::string& id);
|
||||||
|
PostContext get_post_context(const std::string& host, const std::string& id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CURL* _get_easy();
|
CURL* _get_easy();
|
||||||
std::string _send_request(const std::string& url);
|
std::string _send_request(const std::string& url);
|
||||||
|
|
|
@ -95,6 +95,11 @@ void from_json(const json& j, Post& post) {
|
||||||
j.at("emojis").get_to(post.emojis);
|
j.at("emojis").get_to(post.emojis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void from_json(const json& j, PostContext& context) {
|
||||||
|
j.at("ancestors").get_to(context.ancestors);
|
||||||
|
j.at("descendants").get_to(context.descendants);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static std::regex rfc3339_re(R"EOF((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:(Z)|([+-]\d{2}):(\d{2})))EOF", std::regex::ECMAScript | std::regex::icase);
|
static std::regex rfc3339_re(R"EOF((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:(Z)|([+-]\d{2}):(\d{2})))EOF", std::regex::ECMAScript | std::regex::icase);
|
||||||
time_t parse_rfc3339(const std::string& str) {
|
time_t parse_rfc3339(const std::string& str) {
|
||||||
|
|
6
models.h
6
models.h
|
@ -76,8 +76,14 @@ struct Post {
|
||||||
std::vector<Emoji> emojis;
|
std::vector<Emoji> emojis;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PostContext {
|
||||||
|
std::vector<Post> ancestors;
|
||||||
|
std::vector<Post> descendants;
|
||||||
|
};
|
||||||
|
|
||||||
void from_json(const nlohmann::json& j, Emoji& emoji);
|
void from_json(const nlohmann::json& j, Emoji& emoji);
|
||||||
void from_json(const nlohmann::json& j, AccountField& field);
|
void from_json(const nlohmann::json& j, AccountField& field);
|
||||||
void from_json(const nlohmann::json& j, Account& account);
|
void from_json(const nlohmann::json& j, Account& account);
|
||||||
void from_json(const nlohmann::json& j, Media& media);
|
void from_json(const nlohmann::json& j, Media& media);
|
||||||
void from_json(const nlohmann::json& j, Post& post);
|
void from_json(const nlohmann::json& j, Post& post);
|
||||||
|
void from_json(const nlohmann::json& j, PostContext& context);
|
||||||
|
|
|
@ -62,6 +62,9 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* POST */
|
/* POST */
|
||||||
|
.main_post .post-contents {
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
.post {
|
.post {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,12 @@ void status_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
std::string id = req.matches.str(2);
|
std::string id = req.matches.str(2);
|
||||||
|
|
||||||
std::optional<Post> post;
|
std::optional<Post> post;
|
||||||
|
PostContext context;
|
||||||
try {
|
try {
|
||||||
post = mastodon_client.get_post(server, id);
|
post = mastodon_client.get_post(server, id);
|
||||||
|
if (post) {
|
||||||
|
context = mastodon_client.get_post_context(server, id);
|
||||||
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
res.status = 500;
|
res.status = 500;
|
||||||
serve_error(req, res, "500: Internal server error", "Failed to fetch post information", e.what());
|
serve_error(req, res, "500: Internal server error", "Failed to fetch post information", e.what());
|
||||||
|
@ -27,8 +31,18 @@ void status_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Element body("body", {
|
Element body("body");
|
||||||
serialize_post(req, server, *post),
|
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, "", std::move(body));
|
serve(req, res, "", std::move(body));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,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, 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);
|
||||||
static inline Element serialize_media(const Media& media);
|
static inline Element serialize_media(const Media& media);
|
||||||
|
|
||||||
class CurlUrlException : public std::exception {
|
class CurlUrlException : public std::exception {
|
||||||
|
@ -172,7 +172,7 @@ bool should_send_304(const httplib::Request& req, uint64_t hash) {
|
||||||
return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/');
|
return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool pinned) {
|
Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool pinned, bool main_post) {
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
if (post.reblog) {
|
if (post.reblog) {
|
||||||
|
@ -180,21 +180,21 @@ 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, post_status);
|
return serialize_post(req, server, *post.reblog, main_post, post_status);
|
||||||
} else if (pinned) {
|
} else if (pinned) {
|
||||||
PostStatus post_status = {
|
PostStatus post_status = {
|
||||||
fa_thumbtack,
|
fa_thumbtack,
|
||||||
blankie::html::HTMLString("Pinned post"),
|
blankie::html::HTMLString("Pinned post"),
|
||||||
};
|
};
|
||||||
return serialize_post(req, server, post, post_status);
|
return serialize_post(req, server, post, main_post, post_status);
|
||||||
} else if (post.in_reply_to_id && post.in_reply_to_account_id && post.account.id == *post.in_reply_to_account_id) {
|
} else if (post.in_reply_to_id && post.in_reply_to_account_id && post.account.id == *post.in_reply_to_account_id) {
|
||||||
PostStatus post_status = {
|
PostStatus post_status = {
|
||||||
fa_reply,
|
fa_reply,
|
||||||
preprocess_html(req, post.account.emojis, "Replied to "s + post.account.display_name),
|
preprocess_html(req, post.account.emojis, "Replied to "s + post.account.display_name),
|
||||||
};
|
};
|
||||||
return serialize_post(req, server, post, post_status);
|
return serialize_post(req, server, post, main_post, post_status);
|
||||||
} else {
|
} else {
|
||||||
return serialize_post(req, server, post, std::nullopt);
|
return serialize_post(req, server, post, main_post, std::nullopt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@ 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, 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) {
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
std::string time_title = post.edited_at < 0
|
std::string time_title = post.edited_at < 0
|
||||||
|
@ -369,7 +369,7 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
|
||||||
: "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);
|
||||||
const char* time_badge = post.edited_at < 0 ? "" : " (edited)";
|
const char* time_badge = post.edited_at < 0 ? "" : " (edited)";
|
||||||
|
|
||||||
Element contents("div", {preprocess_html(req, server, post.emojis, post.content)});
|
Element contents("div", {{"class", "post-contents"}}, {preprocess_html(req, server, post.emojis, post.content)});
|
||||||
Element post_attachments("div", {{"class", "post-attachments"}}, {});
|
Element post_attachments("div", {{"class", "post-attachments"}}, {});
|
||||||
post_attachments.nodes.reserve(post.media_attachments.size());
|
post_attachments.nodes.reserve(post.media_attachments.size());
|
||||||
for (const Media& media : post.media_attachments) {
|
for (const Media& media : post.media_attachments) {
|
||||||
|
@ -394,7 +394,7 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
|
||||||
Element("br"), "@", post.account.acct(),
|
Element("br"), "@", post.account.acct(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id}, {"title", time_title}}, {
|
Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id + "#m"}, {"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}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
@ -406,6 +406,9 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
|
||||||
blankie::html::HTMLString(post_status->icon_html), " ", post_status->info_node,
|
blankie::html::HTMLString(post_status->icon_html), " ", post_status->info_node,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
if (main_post) {
|
||||||
|
div.attributes = {{"class", "post main_post"}, {"id", "m"}};
|
||||||
|
}
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ std::string get_origin(const httplib::Request& req);
|
||||||
std::string proxy_mastodon_url(const httplib::Request& req, const std::string& url_str);
|
std::string proxy_mastodon_url(const httplib::Request& req, const std::string& url_str);
|
||||||
bool should_send_304(const httplib::Request& req, uint64_t hash);
|
bool should_send_304(const httplib::Request& req, uint64_t hash);
|
||||||
|
|
||||||
Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool pinned = false);
|
Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool pinned = false, bool main_post = false);
|
||||||
|
|
||||||
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, const blankie::html::HTMLString& str);
|
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, const blankie::html::HTMLString& str);
|
||||||
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::vector<Emoji>& emojis, const std::string& str);
|
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::vector<Emoji>& emojis, const std::string& str);
|
||||||
|
|
Loading…
Reference in New Issue