Add media support
This commit is contained in:
parent
8cb8e86de6
commit
fb1f45817c
15
models.cpp
15
models.cpp
|
@ -52,6 +52,20 @@ void from_json(const json& j, Account& account) {
|
||||||
account.server = sm.str(1);
|
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) {
|
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&>());
|
||||||
|
@ -77,6 +91,7 @@ void from_json(const json& j, Post& 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("account").get_to(post.account);
|
||||||
|
j.at("media_attachments").get_to(post.media_attachments);
|
||||||
j.at("emojis").get_to(post.emojis);
|
j.at("emojis").get_to(post.emojis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
models.h
12
models.h
|
@ -10,7 +10,7 @@
|
||||||
enum PostSortingMethod {
|
enum PostSortingMethod {
|
||||||
Posts = 0,
|
Posts = 0,
|
||||||
PostsAndReplies,
|
PostsAndReplies,
|
||||||
Media,
|
MediaOnly,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Emoji {
|
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 {
|
struct Post {
|
||||||
std::string id;
|
std::string id;
|
||||||
time_t created_at;
|
time_t created_at;
|
||||||
|
@ -64,10 +72,12 @@ struct Post {
|
||||||
blankie::html::HTMLString content;
|
blankie::html::HTMLString content;
|
||||||
std::unique_ptr<Post> reblog;
|
std::unique_ptr<Post> reblog;
|
||||||
Account account;
|
Account account;
|
||||||
|
std::vector<Media> media_attachments;
|
||||||
std::vector<Emoji> emojis;
|
std::vector<Emoji> emojis;
|
||||||
};
|
};
|
||||||
|
|
||||||
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, Post& post);
|
void from_json(const nlohmann::json& j, Post& post);
|
||||||
|
|
|
@ -28,11 +28,19 @@ html {
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
p, details {
|
p, details {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
details[open] {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
|
@ -76,6 +84,32 @@ a:hover {
|
||||||
margin-left: auto;
|
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 PAGE */
|
||||||
.error {
|
.error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -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"}));
|
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"}}, {
|
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::Posts),
|
||||||
sorting_method_link(req, server, account, sorting_method, PostSortingMethod::PostsAndReplies),
|
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;
|
return header;
|
||||||
|
|
|
@ -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 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 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 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 {
|
class CurlUrlException : public std::exception {
|
||||||
public:
|
public:
|
||||||
|
@ -34,7 +35,7 @@ void serve(const httplib::Request& req, httplib::Response& res, std::string titl
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
std::string css_url = get_origin(req) + "/style.css";
|
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 head("head", {
|
||||||
Element("meta", {{"charset", "utf-8"}}, {}),
|
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);
|
: "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)";
|
||||||
|
|
||||||
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) {
|
if (post.sensitive) {
|
||||||
std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more";
|
std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more";
|
||||||
contents = Element("details", {
|
contents = Element("details", {
|
||||||
|
@ -366,3 +374,37 @@ static inline std::vector<lxb_dom_node_t*> emojify(lxb_dom_document_t* document,
|
||||||
|
|
||||||
return res;
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue