Add media support

This commit is contained in:
blankie 2023-11-23 23:20:49 +11:00
parent 8cb8e86de6
commit fb1f45817c
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
5 changed files with 106 additions and 5 deletions

View File

@ -52,6 +52,20 @@ void from_json(const json& j, Account& account) {
account.server = sm.str(1);
}
void from_json(const json& j, Media& media) {
j.at("type").get_to(media.type);
j.at("url").get_to(media.url);
if (!j.at("preview_url").is_null()) {
media.preview_url = j["preview_url"].get<std::string>();
}
if (!j.at("remote_url").is_null()) {
media.remote_url = j["remote_url"].get<std::string>();
}
if (!j.at("description").is_null()) {
media.description = j["description"].get<std::string>();
}
}
void from_json(const json& j, Post& post) {
j.at("id").get_to(post.id);
post.created_at = parse_rfc3339(j.at("created_at").get_ref<const std::string&>());
@ -77,6 +91,7 @@ void from_json(const json& j, Post& post) {
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("emojis").get_to(post.emojis);
}

View File

@ -10,7 +10,7 @@
enum PostSortingMethod {
Posts = 0,
PostsAndReplies,
Media,
MediaOnly,
};
struct Emoji {
@ -50,6 +50,14 @@ struct Account {
}
};
struct Media {
std::string type;
std::string url;
std::optional<std::string> preview_url;
std::optional<std::string> remote_url;
std::optional<std::string> description;
};
struct Post {
std::string id;
time_t created_at;
@ -64,10 +72,12 @@ struct Post {
blankie::html::HTMLString content;
std::unique_ptr<Post> reblog;
Account account;
std::vector<Media> media_attachments;
std::vector<Emoji> emojis;
};
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, Account& account);
void from_json(const nlohmann::json& j, Media& media);
void from_json(const nlohmann::json& j, Post& post);

View File

@ -28,11 +28,19 @@ html {
font-size: var(--font-size);
font-family: sans-serif;
padding: 10px;
overflow-wrap: break-word;
}
p, details {
margin-top: 1em;
margin-bottom: 1em;
}
details[open] {
margin-bottom: 0;
}
img {
object-fit: cover;
}
a {
color: var(--accent-color);
@ -76,6 +84,32 @@ a:hover {
margin-left: auto;
}
.post-attachments:not(:empty) {
margin-top: 1em;
margin-bottom: 1em;
}
/* https://stackoverflow.com/a/7354648 */
@media (min-width: 801px) {
.post-attachments {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0.5em;
width: fit-content;
}
.post-attachments * {
margin: initial;
}
}
.post-attachments :is(img, video, audio) {
width: 320px;
height: 180px;
}
/* do not ask why not fit-content or min-content. i spent one hour on this, and that is one hour too long to be spending on this god damn grid */
.post-attachments audio {
height: 40px;
}
/* ERROR PAGE */
.error {
text-align: center;

View File

@ -62,7 +62,7 @@ void user_route(const httplib::Request& req, httplib::Response& res) {
body.nodes.push_back(Element("p", {{"class", "user_page-more_posts"}}, {"There are no more posts"}));
}
serve(req, res, account->display_name + " (" + account->acct() + ')', std::move(body));
serve(req, res, account->display_name + " (@" + account->acct() + ')', std::move(body));
}
@ -112,7 +112,7 @@ static inline Element user_header(const httplib::Request& req, const std::string
Element("nav", {{"class", "user_page-user_posts_nav"}, {"id", "user_posts_nav"}}, {
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::Posts),
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::PostsAndReplies),
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::Media),
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::MediaOnly),
}),
});
return header;

View File

@ -17,6 +17,7 @@ static inline void preprocess_link(const httplib::Request& req, const std::strin
static inline bool should_fix_link(lxb_dom_element_t* element, const std::string& cls);
static inline lxb_dom_node_t* emojify(lxb_dom_node_t* child, const std::vector<Emoji>& emojis);
static inline std::vector<lxb_dom_node*> emojify(lxb_dom_document_t* document, std::string str, const std::vector<Emoji>& emojis);
static inline Element serialize_media(const Media& media);
class CurlUrlException : public std::exception {
public:
@ -34,7 +35,7 @@ void serve(const httplib::Request& req, httplib::Response& res, std::string titl
using namespace std::string_literals;
std::string css_url = get_origin(req) + "/style.css";
res.set_header("Content-Security-Policy", "default-src 'none'; img-src https:; media-src: https:; style-src "s + css_url);
res.set_header("Content-Security-Policy", "default-src 'none'; img-src https:; media-src https:; style-src "s + css_url);
Element head("head", {
Element("meta", {{"charset", "utf-8"}}, {}),
@ -176,7 +177,14 @@ Element serialize_post(const httplib::Request& req, const std::string& server, c
: "Created: "s + full_time(post.created_at) + "\nEdited: " + full_time(post.edited_at);
const char* time_badge = post.edited_at < 0 ? "" : " (edited)";
Node contents = preprocess_html(req, server, post.emojis, post.content);
Element contents("div", {preprocess_html(req, server, post.emojis, post.content)});
Element post_attachments("div", {{"class", "post-attachments"}}, {});
post_attachments.nodes.reserve(post.media_attachments.size());
for (const Media& media : post.media_attachments) {
post_attachments.nodes.push_back(serialize_media(media));
}
contents.nodes.push_back(std::move(post_attachments));
if (post.sensitive) {
std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more";
contents = Element("details", {
@ -366,3 +374,37 @@ static inline std::vector<lxb_dom_node_t*> emojify(lxb_dom_document_t* document,
return res;
}
static inline Element serialize_media(const Media& media) {
Element element = [&]() {
if (media.type == "image") {
return Element("a", {{"href", media.url}}, {
Element("img", {{"src", media.preview_url.value_or(media.url)}}, {}),
});
} else if (media.type == "video") {
Element video("video", {{"controls", ""}, {"src", media.url}}, {});
if (media.preview_url) {
video.attributes.push_back({"poster", *media.preview_url});
}
return video;
} else if (media.type == "audio") {
return Element("audio", {{"controls", ""}, {"src", media.url}}, {});
} else if (media.type == "unknown" && media.remote_url) {
if (media.remote_url) {
// https://botsin.space/@lina@vt.social/111053598696451525
return Element("a", {{"href", *media.remote_url}}, {"Media is not available from this instance, view externally"});
} else {
return Element("p", {"Media is not available from this instance"});
}
} else {
return Element("p", {"Unsupported media type: ", media.type});
}
}();
if (media.description) {
element.attributes.push_back({"alt", *media.description});
element.attributes.push_back({"title", *media.description});
}
return element;
}