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