Add tags page
This commit is contained in:
parent
81dd9c9973
commit
fd946d1336
|
@ -25,7 +25,7 @@ add_link_options(${FLAGS})
|
|||
|
||||
add_executable(${PROJECT_NAME} main.cpp misc.cpp config.cpp servehelper.cpp numberhelper.cpp pixivclient.cpp pixivmodels.cpp
|
||||
blankie/serializer.cpp blankie/escape.cpp blankie/murl.cpp
|
||||
routes/home.cpp routes/css.cpp routes/artworks.cpp routes/users/common.cpp routes/users/illustrations.cpp)
|
||||
routes/home.cpp routes/css.cpp routes/artworks.cpp routes/tags.cpp routes/users/common.cpp routes/users/illustrations.cpp)
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
"(?:\\?" QUERY ")?" \
|
||||
"(?:#" FRAGMENT ")?"
|
||||
|
||||
static inline void hexencode(char c, char out[2]);
|
||||
static inline char hexdecode(char nibble1, char nibble2);
|
||||
static void handle_segment(std::vector<std::string>& segments, const std::string& str, size_t offset, size_t length);
|
||||
static std::string tolower(std::string str);
|
||||
|
||||
|
@ -93,6 +95,52 @@ bool Url::is_host_equal(std::string other) const {
|
|||
return tolower(this->hostname) == tolower(std::move(other));
|
||||
}
|
||||
|
||||
std::string escape(const std::string& in) {
|
||||
std::string out;
|
||||
char encoded[2];
|
||||
size_t pos = 0;
|
||||
size_t last_pos = 0;
|
||||
|
||||
out.reserve(in.size());
|
||||
while ((pos = in.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", pos)) != std::string::npos) {
|
||||
out.append(in, last_pos, pos - last_pos);
|
||||
hexencode(in[pos], encoded);
|
||||
out += '%';
|
||||
out.append(encoded, 2);
|
||||
pos++;
|
||||
last_pos = pos;
|
||||
}
|
||||
|
||||
if (in.size() > last_pos) {
|
||||
out.append(in, last_pos);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string unescape(const std::string& in) {
|
||||
std::string out;
|
||||
size_t pos = 0;
|
||||
size_t last_pos = 0;
|
||||
|
||||
out.reserve(in.size());
|
||||
while ((pos = in.find('%', pos)) != std::string::npos) {
|
||||
if (pos + 2 >= in.size()) {
|
||||
throw std::invalid_argument("String abruptly terminated while finding percent-encoded nibbles");
|
||||
}
|
||||
out.append(in, last_pos, pos - last_pos);
|
||||
out += hexdecode(in[pos + 1], in[pos + 2]);
|
||||
pos += 3;
|
||||
last_pos = pos;
|
||||
}
|
||||
|
||||
if (in.size() > last_pos) {
|
||||
out.append(in, last_pos);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string normalize_path(const std::string& str) {
|
||||
std::vector<std::string> segments;
|
||||
std::string res;
|
||||
|
@ -137,6 +185,30 @@ std::string normalize_path(const std::string& str) {
|
|||
} // namespace murl
|
||||
} // namespace blankie
|
||||
|
||||
static inline void hexencode(char c, char out[2]) {
|
||||
char nibble1 = (c >> 4) & 0xF;
|
||||
char nibble2 = c & 0xF;
|
||||
|
||||
auto hexencode = [](char nibble) -> char {
|
||||
return nibble < 10
|
||||
? '0' + nibble
|
||||
: 'A' + nibble - 10;
|
||||
};
|
||||
out[0] = hexencode(nibble1);
|
||||
out[1] = hexencode(nibble2);
|
||||
}
|
||||
|
||||
static inline char hexdecode(char nibble1, char nibble2) {
|
||||
auto hexdecode = [](char nibble) -> char {
|
||||
if (nibble >= '0' && nibble <= '9') return nibble - '0';
|
||||
if (nibble >= 'A' && nibble <= 'F') return nibble - 'A' + 10;
|
||||
if (nibble >= 'a' && nibble <= 'f') return nibble - 'a' + 10;
|
||||
throw std::invalid_argument("Invalid percent-encoded nibble received");
|
||||
};
|
||||
|
||||
return static_cast<char>((hexdecode(nibble1) << 4) | hexdecode(nibble2));
|
||||
}
|
||||
|
||||
static void handle_segment(std::vector<std::string>& segments, const std::string& str, size_t offset, size_t length) {
|
||||
if (length == 2 && str[offset] == '.' && str[offset + 1] == '.') {
|
||||
if (segments.empty()) {
|
||||
|
|
|
@ -38,6 +38,8 @@ struct Url {
|
|||
std::string to_string() const;
|
||||
};
|
||||
|
||||
std::string escape(const std::string& in);
|
||||
std::string unescape(const std::string& in);
|
||||
std::string normalize_path(const std::string& str);
|
||||
|
||||
} // namespace murl
|
||||
|
|
6
main.cpp
6
main.cpp
|
@ -37,6 +37,12 @@ int main(int argc, char** argv) {
|
|||
server.Get("/artworks/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
|
||||
artworks_route(req, res, config, pixiv_client);
|
||||
});
|
||||
server.Get("/tags/([^/]+)", [&](const httplib::Request& req, httplib::Response& res) {
|
||||
serve_redirect(req, res, config, get_origin(req, config) + "/tags/" + req.matches.str(1) + "/illustrations");
|
||||
});
|
||||
server.Get("/tags/([^/]+)/illustrations", [&](const httplib::Request& req, httplib::Response& res) {
|
||||
tags_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");
|
||||
|
|
|
@ -37,6 +37,11 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* USER PAGE (and a tiny bit for illustrations page) */
|
||||
.cover {
|
||||
width: 100%;
|
||||
|
@ -61,10 +66,6 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
|
|||
}
|
||||
|
||||
/* USER ILLUSTRATIONS PAGE (and illustrations page) */
|
||||
.center {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -9,3 +9,4 @@ void home_route(const httplib::Request& req, httplib::Response& res, const Confi
|
|||
void css_route(const httplib::Request& req, httplib::Response& res);
|
||||
void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client);
|
||||
void artworks_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client);
|
||||
void tags_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client);
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
#include "routes.h"
|
||||
#include "../numberhelper.h"
|
||||
#include "../servehelper.h"
|
||||
#include "../pixivclient.h"
|
||||
|
||||
static inline Element generate_header(const httplib::Request& req, const Config& config,
|
||||
const SearchResults& search_results, const std::string& query, const std::vector<std::string>& tags, const std::string& order);
|
||||
|
||||
static std::string tags_to_string(const std::unordered_map<std::string, std::string>& tag_translations, const std::vector<std::string>& tags);
|
||||
static inline std::vector<std::string> split(const std::string& str, char c);
|
||||
|
||||
void tags_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) {
|
||||
std::string query = blankie::murl::unescape(req.matches.str(1));
|
||||
std::string order = req.has_param("order") ? req.get_param_value("order") : "date_d";
|
||||
unsigned long long page = req.has_param("p") ? to_ull(req.get_param_value("p")) - 1 : 0;
|
||||
std::vector<std::string> tags = split(query, ' ');
|
||||
|
||||
if (tags.empty()) {
|
||||
res.status = 400;
|
||||
serve_error(req, res, config, "400: Bad Request", "Empty search query");
|
||||
return;
|
||||
}
|
||||
|
||||
SearchResults search_results;
|
||||
try {
|
||||
search_results = pixiv_client.search_illusts(query, page, order);
|
||||
} catch (const std::exception& e) {
|
||||
res.status = 500;
|
||||
serve_error(req, res, config, "500: Internal server error", "Failed to search for illusts", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
Element body("body", {
|
||||
generate_header(req, config, search_results, query, tags, order),
|
||||
Element("br"),
|
||||
generate_illusts_pager(req, config, search_results.illusts, page, "illusts")
|
||||
});
|
||||
serve(req, res, config, tags_to_string(std::move(search_results.tag_translations), std::move(tags)), std::move(body));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static inline Element generate_header(const httplib::Request& req, const Config& config,
|
||||
const SearchResults& search_results, const std::string& query, const std::vector<std::string>& tags, const std::string& order) {
|
||||
auto sort_element = [&](const char* title, const char* new_order) {
|
||||
std::string url = get_origin(req, config) + "/tags/" + blankie::murl::escape(query) + "/illustrations?order=" + new_order;
|
||||
Element ret("a", {{"href", std::move(url)}}, {title});
|
||||
if (new_order == order) {
|
||||
ret = Element("b", {std::move(ret)});
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
Element header("header", {{"class", "center"}}, {
|
||||
Element("form", {{"method", "get"}, {"action", get_origin(req, config) + "/search"}}, {
|
||||
Element("input", {{"name", "q"}, {"required", ""}, {"value", query}}, {}),
|
||||
" ",
|
||||
Element("button", {"Search for illustrations"})
|
||||
}),
|
||||
Element("br")
|
||||
});
|
||||
if (search_results.illusts.total_illusts != 1) {
|
||||
header.nodes.push_back("There are ");
|
||||
header.nodes.push_back(std::to_string(search_results.illusts.total_illusts));
|
||||
header.nodes.push_back(" illustrations");
|
||||
} else {
|
||||
header.nodes.push_back("There is 1 illustration");
|
||||
}
|
||||
header.nodes.push_back(" of ");
|
||||
header.nodes.push_back(Element("b", {tags_to_string(search_results.tag_translations, tags)}));
|
||||
|
||||
header.nodes.push_back(Element("br"));
|
||||
header.nodes.push_back("Sort by: ");
|
||||
header.nodes.push_back(sort_element("Newest", "date_d"));
|
||||
header.nodes.push_back(" ");
|
||||
header.nodes.push_back(sort_element("Oldest", "date"));
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static std::string tags_to_string(const std::unordered_map<std::string, std::string>& tag_translations, const std::vector<std::string>& tags) {
|
||||
std::string str;
|
||||
for (const std::string& tag : tags) {
|
||||
if (!str.empty()) {
|
||||
str += ' ';
|
||||
}
|
||||
str += '#';
|
||||
|
||||
auto translated_tag = tag_translations.find(tag);
|
||||
str += translated_tag != tag_translations.cend() ? translated_tag->second : tag;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline std::vector<std::string> split(const std::string& str, char c) {
|
||||
std::vector<std::string> ret;
|
||||
size_t pos = 0;
|
||||
size_t last_pos = 0;
|
||||
|
||||
while ((pos = str.find(c, pos)) != std::string::npos) {
|
||||
if (pos - last_pos > 0) {
|
||||
ret.push_back(str.substr(last_pos, pos - last_pos));
|
||||
}
|
||||
pos++;
|
||||
last_pos = pos;
|
||||
}
|
||||
if (str.size() > last_pos) {
|
||||
ret.push_back(str.substr(last_pos));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue