Add primitive user page
This commit is contained in:
		
							parent
							
								
									1e4b221dd7
								
							
						
					
					
						commit
						aecde91ad1
					
				| 
						 | 
				
			
			@ -24,7 +24,7 @@ add_link_options(${FLAGS})
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp pixivclient.cpp blankie/serializer.cpp blankie/escape.cpp
 | 
			
		||||
    routes/home.cpp routes/css.cpp)
 | 
			
		||||
    routes/home.cpp routes/css.cpp routes/users/common.cpp routes/users/users.cpp)
 | 
			
		||||
set_target_properties(${PROJECT_NAME}
 | 
			
		||||
    PROPERTIES
 | 
			
		||||
        CXX_STANDARD 20
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								main.cpp
								
								
								
								
							
							
						
						
									
										15
									
								
								main.cpp
								
								
								
								
							| 
						 | 
				
			
			@ -28,6 +28,10 @@ int main(int argc, char** argv) {
 | 
			
		|||
    });
 | 
			
		||||
    server.Get("/style\\.css", css_route);
 | 
			
		||||
 | 
			
		||||
    server.Get("/users/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        users_route(req, res, config, pixiv_client);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    server.Get("/member\\.php", [&](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        std::string id = req.get_param_value("id");
 | 
			
		||||
        if (id.empty() || id.find_first_not_of("1234567890") != std::string::npos) {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,17 +58,6 @@ int main(int argc, char** argv) {
 | 
			
		|||
    server.Get("/debug/exception/unknown", [](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        throw "cope";
 | 
			
		||||
    });
 | 
			
		||||
    // TODO stop it
 | 
			
		||||
    server.Get("/debug/getuser/good", [&](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        User user = pixiv_client.get_user(2583663);
 | 
			
		||||
        printf("%s\n", user.username.c_str());
 | 
			
		||||
        res.set_content(user.username, "text/plain");
 | 
			
		||||
    });
 | 
			
		||||
    server.Get("/debug/getuser/bad", [&](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        User user = pixiv_client.get_user(1);
 | 
			
		||||
        printf("%s\n", user.username.c_str());
 | 
			
		||||
        res.set_content(user.username, "text/plain");
 | 
			
		||||
    });
 | 
			
		||||
#endif
 | 
			
		||||
    server.Get(".*", [&](const httplib::Request& req, httplib::Response& res) {
 | 
			
		||||
        res.status = 404;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,7 +38,7 @@ void from_json(const nlohmann::json& j, User& user) {
 | 
			
		|||
    j.at("user_name").get_to(user.display_name);
 | 
			
		||||
    user.user_id = to_ull(j.at("user_id").get_ref<const nlohmann::json::string_t&>());
 | 
			
		||||
 | 
			
		||||
    if (j.contains("cover_image")) {
 | 
			
		||||
    if (j.contains("cover_image") && j["cover_image"].is_object()) {
 | 
			
		||||
        nlohmann::json cover_image = j["cover_image"];
 | 
			
		||||
        std::string c_720x360 = cover_image.at("profile_cover_image").at("720x360").get<std::string>();
 | 
			
		||||
        std::optional<std::string> original = get_original_cover_image(c_720x360);
 | 
			
		||||
| 
						 | 
				
			
			@ -59,9 +59,9 @@ void from_json(const nlohmann::json& j, User& user) {
 | 
			
		|||
 | 
			
		||||
    std::string user_webpage = j.at("user_webpage").get<std::string>();
 | 
			
		||||
    if (!user_webpage.empty()) {
 | 
			
		||||
        user.links.push_back({"webpage", std::move(user_webpage)});
 | 
			
		||||
        user.links.push_back({"Webpage", std::move(user_webpage)});
 | 
			
		||||
    }
 | 
			
		||||
    auto add_social_as_needed = [&](const char* key) {
 | 
			
		||||
    auto add_social_as_needed = [&](const char* key, const char* public_name) {
 | 
			
		||||
        nlohmann::json social = j.at("social");
 | 
			
		||||
        if (!social.is_object()) {
 | 
			
		||||
            return;
 | 
			
		||||
| 
						 | 
				
			
			@ -71,14 +71,14 @@ void from_json(const nlohmann::json& j, User& user) {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        std::string url = social[key].at("url").get<std::string>();
 | 
			
		||||
        user.links.push_back({key, std::move(url)});
 | 
			
		||||
        user.links.push_back({public_name, std::move(url)});
 | 
			
		||||
    };
 | 
			
		||||
    add_social_as_needed("twitter");
 | 
			
		||||
    add_social_as_needed("instagram");
 | 
			
		||||
    add_social_as_needed("tumblr");
 | 
			
		||||
    add_social_as_needed("facebook");
 | 
			
		||||
    add_social_as_needed("circlems");
 | 
			
		||||
    add_social_as_needed("pawoo");
 | 
			
		||||
    add_social_as_needed("twitter", "Twitter");
 | 
			
		||||
    add_social_as_needed("instagram", "Instagram");
 | 
			
		||||
    add_social_as_needed("tumblr", "Tumblr");
 | 
			
		||||
    add_social_as_needed("facebook", "Facebook");
 | 
			
		||||
    add_social_as_needed("circlems", "Circle.ms");
 | 
			
		||||
    add_social_as_needed("pawoo", "Pawoo");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::regex c1920x960_cover_image_thumbnail_regex(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,24 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
 | 
			
		|||
        text-decoration: underline;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .cover {
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 50vh;
 | 
			
		||||
        margin-bottom: 1em;
 | 
			
		||||
    }
 | 
			
		||||
    .profilepicture {
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
        width: 5em;
 | 
			
		||||
        height: 5em;
 | 
			
		||||
        margin-right: .5em;
 | 
			
		||||
    }
 | 
			
		||||
    .usermetadata {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        margin-left: .5em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .error {
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        background-color: var(--error-background-color);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,9 @@
 | 
			
		|||
 | 
			
		||||
#include <httplib/httplib.h>
 | 
			
		||||
 | 
			
		||||
struct Config; // forward declaration from config.h
 | 
			
		||||
struct Config; // forward declaration from ../config.h
 | 
			
		||||
class PixivClient; // forward declaration from ../pixivclient.h
 | 
			
		||||
 | 
			
		||||
void home_route(const httplib::Request& req, httplib::Response& res, const Config& config);
 | 
			
		||||
void css_route(const httplib::Request& req, httplib::Response& res);
 | 
			
		||||
void users_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
#include "common.h"
 | 
			
		||||
#include "../../config.h"
 | 
			
		||||
#include "../../servehelper.h"
 | 
			
		||||
#include "../../pixivclient.h"
 | 
			
		||||
 | 
			
		||||
static inline Element generate_user_links(const User& user);
 | 
			
		||||
static std::string thumbnail_or_original(const Images& images);
 | 
			
		||||
static std::string original_or_thumbnail(const Images& images);
 | 
			
		||||
 | 
			
		||||
Element generate_user_header(const User& user, const Config& config) {
 | 
			
		||||
    auto proxy_url = [&](std::string url) {
 | 
			
		||||
        return config.image_proxy_url + remove_origin(std::move(url));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Element header("header");
 | 
			
		||||
    if (user.cover_images) {
 | 
			
		||||
        header.nodes.push_back(Element("a", {{"href", proxy_url(original_or_thumbnail(*user.cover_images))}}, {
 | 
			
		||||
            Element("img", {{"class", "cover"}, {"src", proxy_url(thumbnail_or_original(*user.cover_images))}}, {})
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    header.nodes.push_back(Element("div", {{"class", "usermetadata"}}, {
 | 
			
		||||
        Element("a", {{"href", proxy_url(original_or_thumbnail(user.profile_pictures))}}, {
 | 
			
		||||
            Element("img", {{"class", "profilepicture"}, {"src", proxy_url(thumbnail_or_original(user.profile_pictures))}}, {})
 | 
			
		||||
        }),
 | 
			
		||||
        Element("div", {
 | 
			
		||||
            Element("p", {Element("b", {user.display_name}), " (@", user.username, ")"}),
 | 
			
		||||
            generate_user_links(user)
 | 
			
		||||
        })
 | 
			
		||||
    }));
 | 
			
		||||
    return header;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline Element generate_user_links(const User& user) {
 | 
			
		||||
    Element p("p");
 | 
			
		||||
    for (const auto &[name, url] : user.links) {
 | 
			
		||||
        if (!p.nodes.empty()) {
 | 
			
		||||
            p.nodes.push_back(", ");
 | 
			
		||||
        }
 | 
			
		||||
        p.nodes.push_back(Element("a", {{"href", url}}, {name}));
 | 
			
		||||
    }
 | 
			
		||||
    return p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string thumbnail_or_original(const Images& images) {
 | 
			
		||||
    if (!images.thumbnails.empty()) {
 | 
			
		||||
        return images.thumbnails.back();
 | 
			
		||||
    }
 | 
			
		||||
    return images.original.value_or("");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::string original_or_thumbnail(const Images& images) {
 | 
			
		||||
    if (images.original) {
 | 
			
		||||
        return *images.original;
 | 
			
		||||
    }
 | 
			
		||||
    if (!images.thumbnails.empty()) {
 | 
			
		||||
        return images.thumbnails.back();
 | 
			
		||||
    }
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../../blankie/serializer.h"
 | 
			
		||||
struct User; // forward declaration from ../../pixivclient.h
 | 
			
		||||
struct Config; // forward declaration from ../../config.h
 | 
			
		||||
 | 
			
		||||
using Element = blankie::html::Element;
 | 
			
		||||
 | 
			
		||||
Element generate_user_header(const User& user, const Config& config);
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
#include "../routes.h"
 | 
			
		||||
#include "../../servehelper.h"
 | 
			
		||||
#include "../../pixivclient.h"
 | 
			
		||||
#include "common.h"
 | 
			
		||||
 | 
			
		||||
static inline uint64_t to_ull(const std::string& str);
 | 
			
		||||
 | 
			
		||||
void users_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) {
 | 
			
		||||
    uint64_t user_id = to_ull(req.matches[1].str());
 | 
			
		||||
    User user;
 | 
			
		||||
    try {
 | 
			
		||||
        user = pixiv_client.get_user(user_id);
 | 
			
		||||
    } catch (const PixivException& e) {
 | 
			
		||||
        if (e.status == 404) {
 | 
			
		||||
            res.status = 404;
 | 
			
		||||
            serve_error(req, res, config, "404: User not found", e.what());
 | 
			
		||||
        } else {
 | 
			
		||||
            res.status = 500;
 | 
			
		||||
            serve_error(req, res, config, "500: Internal server error", "Failed to fetch user information", e.what());
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    } catch (const std::exception& e) {
 | 
			
		||||
        res.status = 500;
 | 
			
		||||
        serve_error(req, res, config, "500: Internal server error", "Failed to fetch user information", e.what());
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    serve(req, res, config, user.display_name + " (@" + user.username + ')', generate_user_header(user, config));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uint64_t to_ull(const std::string& str) {
 | 
			
		||||
    char* endptr;
 | 
			
		||||
 | 
			
		||||
    errno = 0;
 | 
			
		||||
    uint64_t res = strtoull(str.c_str(), &endptr, 10);
 | 
			
		||||
    if (res == ULLONG_MAX && errno == ERANGE) {
 | 
			
		||||
        throw std::overflow_error(str + " is too big");
 | 
			
		||||
    } else if (endptr[0] != '\0') {
 | 
			
		||||
        throw std::invalid_argument(str + " contains trailing text or is not an integer");
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,11 +3,6 @@
 | 
			
		|||
#include "config.h"
 | 
			
		||||
#include "servehelper.h"
 | 
			
		||||
 | 
			
		||||
static std::regex image_proxy_regex(
 | 
			
		||||
    "(https?://)?" // optional scheme
 | 
			
		||||
    "(?:.+?@)?" // optional username and pass
 | 
			
		||||
    "([^/]+(?::\\d+)?)" // host
 | 
			
		||||
    "(?:/.*)?$");
 | 
			
		||||
static inline std::string get_image_proxy_origin(const std::string& url);
 | 
			
		||||
 | 
			
		||||
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element) {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +74,25 @@ std::string get_origin(const httplib::Request& req, const Config& config) {
 | 
			
		|||
    return origin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::regex remove_origin_regex(
 | 
			
		||||
    "(?:https?://)?" // optional schema
 | 
			
		||||
    "(?:.+?@)?" // optional username and pass
 | 
			
		||||
    "(?:[^/]+[.:][^/]+(?:\\d+)?)" // host
 | 
			
		||||
    "(/.*)");
 | 
			
		||||
std::string remove_origin(const std::string& url) {
 | 
			
		||||
    std::smatch sm;
 | 
			
		||||
    if (!std::regex_match(url, sm, remove_origin_regex)) {
 | 
			
		||||
        return url;
 | 
			
		||||
    }
 | 
			
		||||
    return sm[1].str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static std::regex image_proxy_regex(
 | 
			
		||||
    "(https?://)?" // optional scheme
 | 
			
		||||
    "(?:.+?@)?" // optional username and pass
 | 
			
		||||
    "([^/]+(?::\\d+)?)" // host
 | 
			
		||||
    "(?:/.*)?$");
 | 
			
		||||
static inline std::string get_image_proxy_origin(const std::string& url) {
 | 
			
		||||
    std::smatch sm;
 | 
			
		||||
    if (!std::regex_match(url, sm, image_proxy_regex)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,3 +13,4 @@ void serve_error(const httplib::Request& req, httplib::Response& res, const Conf
 | 
			
		|||
        std::string title, std::optional<std::string> subtitle = std::nullopt, std::optional<std::string> info = std::nullopt);
 | 
			
		||||
void serve_redirect(const httplib::Request& req, httplib::Response& res, const Config& config, std::string url);
 | 
			
		||||
std::string get_origin(const httplib::Request& req, const Config& config);
 | 
			
		||||
std::string remove_origin(const std::string& url);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue