Add about page
This commit is contained in:
		
							parent
							
								
									64ff7e7350
								
							
						
					
					
						commit
						b402239262
					
				| 
						 | 
				
			
			@ -30,7 +30,7 @@ add_link_options(${FLAGS})
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp
 | 
			
		||||
    routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp
 | 
			
		||||
    routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp
 | 
			
		||||
    blankie/serializer.cpp blankie/escape.cpp)
 | 
			
		||||
set_target_properties(${PROJECT_NAME}
 | 
			
		||||
    PROPERTIES
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								client.cpp
								
								
								
								
							
							
						
						
									
										15
									
								
								client.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -171,6 +171,21 @@ std::vector<Post> MastodonClient::get_tag_timeline(const std::string& host, cons
 | 
			
		|||
    return posts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Instance MastodonClient::get_instance(const std::string& host) {
 | 
			
		||||
    using namespace std::string_literals;
 | 
			
		||||
 | 
			
		||||
    Instance instance = this->_send_request("https://"s + host + "/api/v2/instance");
 | 
			
		||||
    instance.contact_account.same_server = instance.contact_account.server == host;
 | 
			
		||||
    return instance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
blankie::html::HTMLString MastodonClient::get_extended_description(const std::string& host) {
 | 
			
		||||
    using namespace std::string_literals;
 | 
			
		||||
 | 
			
		||||
    nlohmann::json j = this->_send_request("https://"s + host + "/api/v1/instance/extended_description");
 | 
			
		||||
    return blankie::html::HTMLString(j.at("content").get<std::string>());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CURL* MastodonClient::_get_easy() {
 | 
			
		||||
    CURL* curl = pthread_getspecific(this->_easy_key);
 | 
			
		||||
    if (!curl) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								client.h
								
								
								
								
							
							
						
						
									
										3
									
								
								client.h
								
								
								
								
							| 
						 | 
				
			
			@ -73,6 +73,9 @@ public:
 | 
			
		|||
 | 
			
		||||
    std::vector<Post> get_tag_timeline(const std::string& host, const std::string& tag, std::optional<std::string> max_id);
 | 
			
		||||
 | 
			
		||||
    Instance get_instance(const std::string& host);
 | 
			
		||||
    blankie::html::HTMLString get_extended_description(const std::string& host);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CURL* _get_easy();
 | 
			
		||||
    nlohmann::json _send_request(const std::string& url);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								main.cpp
								
								
								
								
							
							
						
						
									
										5
									
								
								main.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -49,6 +49,11 @@ int main(int argc, char** argv) {
 | 
			
		|||
    // protect against ..
 | 
			
		||||
    server.Get("/(" DOMAIN_RE ")/tags/(?!\\.|%2E|%2e)(.+)", tags_route);
 | 
			
		||||
 | 
			
		||||
    server.Get("/(" DOMAIN_RE ")/about", about_route);
 | 
			
		||||
    server.Get("/(" DOMAIN_RE ")/?", [](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/about", false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    server.Get("/https://?(.+)", [](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1), true);
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								models.cpp
								
								
								
								
							
							
						
						
									
										14
									
								
								models.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -127,6 +127,20 @@ void from_json(const json& j, PostContext& context) {
 | 
			
		|||
    j.at("descendants").get_to(context.descendants);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void from_json(const json& j, Instance& instance) {
 | 
			
		||||
    j.at("title").get_to(instance.title);
 | 
			
		||||
    j.at("description").get_to(instance.description);
 | 
			
		||||
    j.at("thumbnail").at("url").get_to(instance.thumbnail);
 | 
			
		||||
    j.at("contact").at("email").get_to(instance.contact_email);
 | 
			
		||||
    j.at("contact").at("account").get_to(instance.contact_account);
 | 
			
		||||
 | 
			
		||||
    json rules = j.at("rules");
 | 
			
		||||
    instance.rules.reserve(rules.size());
 | 
			
		||||
    for (const json& i : rules) {
 | 
			
		||||
        instance.rules.push_back(i.at("text").get<std::string>());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								models.h
								
								
								
								
							
							
						
						
									
										10
									
								
								models.h
								
								
								
								
							| 
						 | 
				
			
			@ -96,6 +96,15 @@ struct PostContext {
 | 
			
		|||
    std::vector<Post> descendants;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Instance {
 | 
			
		||||
    std::string title;
 | 
			
		||||
    std::string description;
 | 
			
		||||
    std::string thumbnail;
 | 
			
		||||
    std::string contact_email;
 | 
			
		||||
    Account contact_account;
 | 
			
		||||
    std::vector<std::string> rules;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			@ -104,3 +113,4 @@ void from_json(const nlohmann::json& j, PollOption& option);
 | 
			
		|||
void from_json(const nlohmann::json& j, Poll& poll);
 | 
			
		||||
void from_json(const nlohmann::json& j, Post& post);
 | 
			
		||||
void from_json(const nlohmann::json& j, PostContext& context);
 | 
			
		||||
void from_json(const nlohmann::json& j, Instance& instance);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
#include "routes.h"
 | 
			
		||||
#include "../servehelper.h"
 | 
			
		||||
#include "../client.h"
 | 
			
		||||
#include "../models.h"
 | 
			
		||||
 | 
			
		||||
void about_route(const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
    using namespace std::string_literals;
 | 
			
		||||
 | 
			
		||||
    std::string server = req.matches.str(1);
 | 
			
		||||
 | 
			
		||||
    Instance instance;
 | 
			
		||||
    blankie::html::HTMLString extended_description;
 | 
			
		||||
    try {
 | 
			
		||||
        instance = mastodon_client.get_instance(server);
 | 
			
		||||
        extended_description = mastodon_client.get_extended_description(server);
 | 
			
		||||
    } catch (const std::exception& e) {
 | 
			
		||||
        res.status = 500;
 | 
			
		||||
        serve_error(req, res, "500: Internal server error", "Failed to fetch instance information", e.what());
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Element rules("ol");
 | 
			
		||||
    rules.nodes.reserve(instance.rules.size());
 | 
			
		||||
    for (const std::string& rule : instance.rules) {
 | 
			
		||||
        rules.nodes.push_back(Element("li", {rule}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Element body("body", {
 | 
			
		||||
        Element("a", {{"href", instance.thumbnail}}, {
 | 
			
		||||
            Element("img", {{"class", "about_page-header"}, {"src", instance.thumbnail}}, {}),
 | 
			
		||||
        }),
 | 
			
		||||
        Element("p", {
 | 
			
		||||
            Element("b", {instance.title, ":"}), " ", instance.description,
 | 
			
		||||
            Element("br"), Element("b", {"Email:"}), " ", Element("a", {{"href", "mailto:"s + instance.contact_email}}, {instance.contact_email}),
 | 
			
		||||
            Element("br"), Element("b", {"Administered by:"}), " ", Element("a", {{"href", get_origin(req) + '/' + server + "/@" + instance.contact_account.acct(false)}}, {
 | 
			
		||||
                preprocess_html(req, instance.contact_account.emojis, instance.contact_account.display_name), " (@", instance.contact_account.acct(false), ")",
 | 
			
		||||
            }),
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        Element("details", {{"class", "about_page-about"}, {"open", ""}}, {
 | 
			
		||||
            Element("summary", {"About"}),
 | 
			
		||||
            preprocess_html(req, server, {}, std::move(extended_description)),
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        Element("details", {{"class", "about_page-rules"}}, {
 | 
			
		||||
            Element("summary", {"Rules"}),
 | 
			
		||||
            rules,
 | 
			
		||||
        }),
 | 
			
		||||
    });
 | 
			
		||||
    serve(req, res, "About "s + instance.title, std::move(body));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ p, details {
 | 
			
		|||
    margin-top: 1em;
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
ul {
 | 
			
		||||
ul, ol {
 | 
			
		||||
    margin: revert;
 | 
			
		||||
    padding: revert;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +165,6 @@ svg {
 | 
			
		|||
.user_page-header {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 15em;
 | 
			
		||||
    object-fit: cover;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user_page-main_header {
 | 
			
		||||
| 
						 | 
				
			
			@ -237,6 +236,20 @@ svg {
 | 
			
		|||
    /* don't ask why, but making it a block element just works */
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ABOUT PAGE */
 | 
			
		||||
.about_page-header {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 15em;
 | 
			
		||||
}
 | 
			
		||||
:is(.about_page-about, .about_page-rules) summary {
 | 
			
		||||
    font-size: 130%;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.about_page-about * {
 | 
			
		||||
    margin: revert;
 | 
			
		||||
    padding: revert;
 | 
			
		||||
}
 | 
			
		||||
)EOF";
 | 
			
		||||
// one for \0, one for trailing newline
 | 
			
		||||
#define CSS_LEN sizeof(css) / sizeof(css[0]) - 2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,3 +9,4 @@ void css_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		|||
void user_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		||||
void status_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		||||
void tags_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		||||
void about_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue