Compare commits
10 Commits
7ecbb7f8bc
...
2ecfe10584
Author | SHA1 | Date |
---|---|---|
blankie | 2ecfe10584 | |
blankie | 4586931adf | |
blankie | 71994b234b | |
blankie | fd946d1336 | |
blankie | 81dd9c9973 | |
blankie | b71cbd039d | |
blankie | a448dd9b6e | |
blankie | 36a750762b | |
blankie | c4407d86e5 | |
blankie | 14a08e4937 |
|
@ -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
|
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
|
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}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
CXX_STANDARD 20
|
CXX_STANDARD 20
|
||||||
|
|
|
@ -5,11 +5,13 @@ Pixwhile is an alternative frontend to Pixiv that utilizes no Javascript.
|
||||||
- Does not contain an asinine amount of Javascript
|
- Does not contain an asinine amount of Javascript
|
||||||
- Allows you to open the original versions of cover images and profile pictures
|
- Allows you to open the original versions of cover images and profile pictures
|
||||||
- Can view illustrations and list user illustrations
|
- Can view illustrations and list user illustrations
|
||||||
|
- Can search for newest and oldest illustrations
|
||||||
|
|
||||||
## Missing Features
|
## Missing Features
|
||||||
|
|
||||||
This list is not exhaustive, nor does it mean that these are being worked on.
|
This list is not exhaustive, nor does it mean that these are being worked on.
|
||||||
|
|
||||||
|
- Can only search for newest and oldest illustrations
|
||||||
- No search
|
- No search
|
||||||
- No ability to login
|
- No ability to login
|
||||||
- No ability to see comments or like counts
|
- No ability to see comments or like counts
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
"(?:\\?" QUERY ")?" \
|
"(?:\\?" QUERY ")?" \
|
||||||
"(?:#" FRAGMENT ")?"
|
"(?:#" 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 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);
|
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));
|
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::string normalize_path(const std::string& str) {
|
||||||
std::vector<std::string> segments;
|
std::vector<std::string> segments;
|
||||||
std::string res;
|
std::string res;
|
||||||
|
@ -137,6 +185,30 @@ std::string normalize_path(const std::string& str) {
|
||||||
} // namespace murl
|
} // namespace murl
|
||||||
} // namespace blankie
|
} // 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) {
|
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 (length == 2 && str[offset] == '.' && str[offset + 1] == '.') {
|
||||||
if (segments.empty()) {
|
if (segments.empty()) {
|
||||||
|
|
|
@ -38,6 +38,8 @@ struct Url {
|
||||||
std::string to_string() const;
|
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);
|
std::string normalize_path(const std::string& str);
|
||||||
|
|
||||||
} // namespace murl
|
} // namespace murl
|
||||||
|
|
|
@ -52,5 +52,9 @@ std::string Element::serialize() const {
|
||||||
} // namespace blankie
|
} // namespace blankie
|
||||||
|
|
||||||
static inline bool is_autoclosing_tag(const char* tag) {
|
static inline bool is_autoclosing_tag(const char* tag) {
|
||||||
return !strncmp(tag, "link", 5) || !strncmp(tag, "meta", 5) || !strncmp(tag, "img", 4) || !strncmp(tag, "br", 3);
|
return !strncmp(tag, "link", 5)
|
||||||
|
|| !strncmp(tag, "meta", 5)
|
||||||
|
|| !strncmp(tag, "img", 4)
|
||||||
|
|| !strncmp(tag, "br", 3)
|
||||||
|
|| !strncmp(tag, "label", 6);
|
||||||
}
|
}
|
||||||
|
|
15
main.cpp
15
main.cpp
|
@ -37,6 +37,12 @@ int main(int argc, char** argv) {
|
||||||
server.Get("/artworks/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
|
server.Get("/artworks/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
|
||||||
artworks_route(req, res, config, pixiv_client);
|
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) {
|
server.Get("/member\\.php", [&](const httplib::Request& req, httplib::Response& res) {
|
||||||
std::string id = req.get_param_value("id");
|
std::string id = req.get_param_value("id");
|
||||||
|
@ -56,6 +62,15 @@ int main(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
serve_redirect(req, res, config, get_origin(req, config) + "/artworks/" + illust_id);
|
serve_redirect(req, res, config, get_origin(req, config) + "/artworks/" + illust_id);
|
||||||
});
|
});
|
||||||
|
server.Get("/search", [&](const httplib::Request& req, httplib::Response& res) {
|
||||||
|
std::string q = req.get_param_value("q");
|
||||||
|
if (q.empty()) {
|
||||||
|
res.status = 400;
|
||||||
|
serve_error(req, res, config, "400: Bad Request", "Missing or empty search query");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serve_redirect(req, res, config, get_origin(req, config) + "/tags/" + blankie::murl::escape(std::move(q)));
|
||||||
|
});
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
server.Get("/debug/exception/known", [](const httplib::Request& req, httplib::Response& res) {
|
server.Get("/debug/exception/known", [](const httplib::Request& req, httplib::Response& res) {
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
|
#include "blankie/murl.h"
|
||||||
#include "numberhelper.h"
|
#include "numberhelper.h"
|
||||||
#include "pixivclient.h"
|
#include "pixivclient.h"
|
||||||
|
|
||||||
|
static const constexpr char* touch_user_agent = "Mozilla/5.0 (Android 12; Mobile; rv:97.0) Gecko/97.0 Firefox/97.0";
|
||||||
|
static const constexpr char* desktop_user_agent = "Mozilla/5.0 (Windows NT 10.0; rv:111.0) Gecko/20100101 Firefox/111.0";
|
||||||
|
|
||||||
PixivClient::PixivClient() {
|
PixivClient::PixivClient() {
|
||||||
this->_www_pixiv_net_client.set_keep_alive(true);
|
this->_www_pixiv_net_client.set_keep_alive(true);
|
||||||
this->_www_pixiv_net_client.set_default_headers({
|
this->_www_pixiv_net_client.set_default_headers({
|
||||||
{"User-Agent", "Mozilla/5.0 (Android 12; Mobile; rv:97.0) Gecko/97.0 Firefox/97.0"}
|
{"Cookie", "webp_available=1"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
User PixivClient::get_user(uint64_t user_id) {
|
User PixivClient::get_user(uint64_t user_id) {
|
||||||
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/user/details", {
|
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/user/details", {
|
||||||
{"lang", "en"}, {"id", std::to_string(user_id)}
|
{"lang", "en"}, {"id", std::to_string(user_id)}
|
||||||
}, httplib::Headers());
|
}, {{"User-Agent", touch_user_agent}});
|
||||||
return this->_handle_result(std::move(res)).at("user_details").get<User>();
|
return this->_handle_result(std::move(res)).at("user_details").get<User>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,17 +24,54 @@ Illusts PixivClient::get_illusts(uint64_t user_id, size_t page) {
|
||||||
if (page != 0) {
|
if (page != 0) {
|
||||||
params.insert({"p", std::to_string(page + 1)});
|
params.insert({"p", std::to_string(page + 1)});
|
||||||
}
|
}
|
||||||
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/user/illusts", std::move(params), httplib::Headers());
|
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/user/illusts", std::move(params), {{"User-Agent", touch_user_agent}});
|
||||||
return this->_handle_result(std::move(res)).get<Illusts>();
|
return this->_handle_result(std::move(res)).get<Illusts>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Illust PixivClient::get_illust(uint64_t illust_id) {
|
Illust PixivClient::get_illust(uint64_t illust_id) {
|
||||||
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/illust/details", {
|
httplib::Result res = this->_www_pixiv_net_client.Get("/touch/ajax/illust/details", {
|
||||||
{"lang", "en"}, {"illust_id", std::to_string(illust_id)}
|
{"lang", "en"}, {"illust_id", std::to_string(illust_id)}
|
||||||
}, httplib::Headers());
|
}, {{"User-Agent", touch_user_agent}});
|
||||||
return this->_handle_result(std::move(res)).get<Illust>();
|
return this->_handle_result(std::move(res)).get<Illust>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SearchResults PixivClient::search_illusts(const std::string& query, size_t page, const std::string& order) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
httplib::Result res = this->_www_pixiv_net_client.Get("/ajax/search/illustrations/"s + blankie::murl::escape(query), {
|
||||||
|
{"lang", "en"},
|
||||||
|
{"mode", "all"},
|
||||||
|
{"p", std::to_string(page + 1)},
|
||||||
|
{"s_mode", "s_tag_full"},
|
||||||
|
{"type", "illust_and_ugoira"},
|
||||||
|
{"order", order},
|
||||||
|
{"word", query}
|
||||||
|
}, {{"User-Agent", desktop_user_agent}});
|
||||||
|
return this->_handle_result(std::move(res)).get<SearchResults>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SearchSuggestion> PixivClient::get_search_suggestions(const std::string& query) {
|
||||||
|
httplib::Result res = this->_www_pixiv_net_client.Get("/rpc/cps.php", {
|
||||||
|
{"lang", "en"}, {"keyword", query}
|
||||||
|
}, {{"User-Agent", desktop_user_agent}, {"Referer", "https://www.pixiv.net/"}});
|
||||||
|
if (!res) {
|
||||||
|
throw HTTPLibException(res.error());
|
||||||
|
}
|
||||||
|
nlohmann::json j = nlohmann::json::parse(std::move(res->body)).at("candidates");
|
||||||
|
|
||||||
|
std::vector<SearchSuggestion> search_suggestions;
|
||||||
|
search_suggestions.reserve(j.size());
|
||||||
|
|
||||||
|
for (const nlohmann::json& i : j) {
|
||||||
|
SearchSuggestion search_suggestion = i.get<SearchSuggestion>();
|
||||||
|
if (search_suggestion.tag != query) {
|
||||||
|
search_suggestions.push_back(std::move(search_suggestion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return search_suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json PixivClient::_handle_result(httplib::Result res) {
|
nlohmann::json PixivClient::_handle_result(httplib::Result res) {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
throw HTTPLibException(res.error());
|
throw HTTPLibException(res.error());
|
||||||
|
|
|
@ -14,6 +14,9 @@ public:
|
||||||
Illusts get_illusts(uint64_t user_id, size_t page);
|
Illusts get_illusts(uint64_t user_id, size_t page);
|
||||||
Illust get_illust(uint64_t illust_id);
|
Illust get_illust(uint64_t illust_id);
|
||||||
|
|
||||||
|
SearchResults search_illusts(const std::string& query, size_t page, const std::string& order);
|
||||||
|
std::vector<SearchSuggestion> get_search_suggestions(const std::string& query);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
nlohmann::json _handle_result(httplib::Result res);
|
nlohmann::json _handle_result(httplib::Result res);
|
||||||
httplib::Client _www_pixiv_net_client{"https://www.pixiv.net"};
|
httplib::Client _www_pixiv_net_client{"https://www.pixiv.net"};
|
||||||
|
|
110
pixivmodels.cpp
110
pixivmodels.cpp
|
@ -8,7 +8,9 @@
|
||||||
static inline std::optional<std::string> get_1920x960_cover_image(blankie::murl::Url url);
|
static inline std::optional<std::string> get_1920x960_cover_image(blankie::murl::Url url);
|
||||||
static inline std::optional<std::string> get_original_cover_image(blankie::murl::Url url, const nlohmann::json& cover_image);
|
static inline std::optional<std::string> get_original_cover_image(blankie::murl::Url url, const nlohmann::json& cover_image);
|
||||||
static inline std::optional<std::string> get_original_profile_picture(blankie::murl::Url url);
|
static inline std::optional<std::string> get_original_profile_picture(blankie::murl::Url url);
|
||||||
|
static inline std::optional<std::string> get_360x360_illust_thumbnail(blankie::murl::Url url);
|
||||||
static Images get_profile_pictures(const nlohmann::json& j);
|
static Images get_profile_pictures(const nlohmann::json& j);
|
||||||
|
static Images get_profile_pictures(const std::string& url);
|
||||||
static Images get_illust_image(const nlohmann::json& j);
|
static Images get_illust_image(const nlohmann::json& j);
|
||||||
|
|
||||||
const std::string& Images::original_or_thumbnail() const {
|
const std::string& Images::original_or_thumbnail() const {
|
||||||
|
@ -80,9 +82,6 @@ void from_json(const nlohmann::json& j, User& user) {
|
||||||
|
|
||||||
void from_json(const nlohmann::json& j, Tag& tag) {
|
void from_json(const nlohmann::json& j, Tag& tag) {
|
||||||
j.at("tag").get_to(tag.japanese);
|
j.at("tag").get_to(tag.japanese);
|
||||||
if (j.contains("romaji")) {
|
|
||||||
tag.romaji = j["romaji"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (j.contains("translation")) {
|
if (j.contains("translation")) {
|
||||||
tag.english = j["translation"].get<std::string>();
|
tag.english = j["translation"].get<std::string>();
|
||||||
}
|
}
|
||||||
|
@ -129,6 +128,75 @@ void from_json(const nlohmann::json& j, Illusts& illusts) {
|
||||||
j.at("lastPage").get_to(illusts.total_pages);
|
j.at("lastPage").get_to(illusts.total_pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, SearchResults& search_results) {
|
||||||
|
const nlohmann::json& tag_translations = j.at("tagTranslation");
|
||||||
|
auto get_translated_tag = [&](const std::string& japanese) -> std::optional<std::string> {
|
||||||
|
if (!tag_translations.is_object() || !tag_translations.contains(japanese)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const nlohmann::json& tag = tag_translations[japanese];
|
||||||
|
return tag.contains("en")
|
||||||
|
? std::optional(tag["en"].get<std::string>())
|
||||||
|
: std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nlohmann::json& illusts = j.at("illust").at("data");
|
||||||
|
search_results.illusts.illusts.reserve(illusts.size());
|
||||||
|
for (const nlohmann::json& i : illusts) {
|
||||||
|
const nlohmann::json& i_tags = i.at("tags");
|
||||||
|
std::vector<Tag> tags;
|
||||||
|
tags.reserve(i_tags.size());
|
||||||
|
|
||||||
|
for (const nlohmann::json& tag : i_tags) {
|
||||||
|
std::string japanese = tag.get<std::string>();
|
||||||
|
tags.push_back({japanese, get_translated_tag(std::move(japanese))});
|
||||||
|
}
|
||||||
|
|
||||||
|
Illust illust = {
|
||||||
|
.username = "",
|
||||||
|
.user_display_name = i.at("userName").get<std::string>(),
|
||||||
|
.user_id = to_ull(i.at("userId").get<std::string>()),
|
||||||
|
.user_profile_pictures = get_profile_pictures(i.at("profileImageUrl").get<std::string>()),
|
||||||
|
|
||||||
|
.illust_id = to_ull(i.at("id").get<std::string>()),
|
||||||
|
.title = i.at("title").get<std::string>(),
|
||||||
|
.ai_generated = i.at("aiType").get<int>() == 2,
|
||||||
|
// pixiv does have a createDate field, but it can't be portably parsed by strptime
|
||||||
|
// and i cba to use regex for it, especially when it's not even used in this context
|
||||||
|
.upload_time = -1,
|
||||||
|
|
||||||
|
.comment = std::nullopt,
|
||||||
|
.tags = std::move(tags),
|
||||||
|
.images = {get_illust_image(i)}
|
||||||
|
};
|
||||||
|
search_results.illusts.illusts.push_back(illust);
|
||||||
|
}
|
||||||
|
|
||||||
|
j.at("illust").at("total").get_to(search_results.illusts.total_illusts);
|
||||||
|
search_results.illusts.total_pages = search_results.illusts.total_illusts / 60;
|
||||||
|
if (search_results.illusts.total_illusts % 60 != 0) {
|
||||||
|
search_results.illusts.total_pages++;
|
||||||
|
}
|
||||||
|
if (search_results.illusts.total_pages > 10) {
|
||||||
|
search_results.illusts.total_pages = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
search_results.tag_translations.reserve(tag_translations.size());
|
||||||
|
for (auto &[key, val] : tag_translations.items()) {
|
||||||
|
std::optional<std::string> translated_tag = get_translated_tag(key);
|
||||||
|
if (translated_tag) {
|
||||||
|
search_results.tag_translations.insert({std::move(key), std::move(*translated_tag)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, SearchSuggestion& search_suggestion) {
|
||||||
|
j.at("tag_name").get_to(search_suggestion.tag);
|
||||||
|
if (j.at("type").get_ref<const nlohmann::json::string_t&>() == "tag_translation") {
|
||||||
|
search_suggestion.english_tag = j.at("tag_translation").get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static std::regex resolution_path_regex("/c/(\\d+x\\d+)(.+)");
|
static std::regex resolution_path_regex("/c/(\\d+x\\d+)(.+)");
|
||||||
static inline std::optional<std::string> get_1920x960_cover_image(blankie::murl::Url url) {
|
static inline std::optional<std::string> get_1920x960_cover_image(blankie::murl::Url url) {
|
||||||
std::smatch sm;
|
std::smatch sm;
|
||||||
|
@ -182,22 +250,56 @@ static Images get_profile_pictures(const nlohmann::json& j) {
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Images get_profile_pictures(const std::string& url) {
|
||||||
|
return {
|
||||||
|
.original = get_original_profile_picture(url),
|
||||||
|
.thumbnails = {url}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::regex illust_360x360_thumbnail_path_regex(
|
||||||
|
"(?:/c/[^/]+?/img-master|/img-original)?"
|
||||||
|
"(/img/.*/\\d+_p\\d+)(?:_master1200|_square1200)?\\.\\w{3,4}"
|
||||||
|
);
|
||||||
|
static inline std::optional<std::string> get_360x360_illust_thumbnail(blankie::murl::Url url) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
std::smatch sm;
|
||||||
|
if (!std::regex_match(url.path, sm, illust_360x360_thumbnail_path_regex)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
url.path = "/c/360x360_10_webp/img-master"s + sm.str(1) + "_square1200.jpg";
|
||||||
|
return url.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
static Images get_illust_image(const nlohmann::json& j) {
|
static Images get_illust_image(const nlohmann::json& j) {
|
||||||
Images images;
|
Images images;
|
||||||
|
ssize_t add_360x360_to = -1;
|
||||||
|
|
||||||
auto add_if_exists = [&](const char* key) {
|
auto add_if_exists = [&](const char* key) {
|
||||||
if (j.contains(key) && j[key].is_string()) {
|
if (j.contains(key) && j[key].is_string()) {
|
||||||
images.thumbnails.push_back(j[key].get<std::string>());
|
images.thumbnails.push_back(j[key].get<std::string>());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
add_if_exists("url_ss");
|
add_if_exists("url_ss");
|
||||||
add_if_exists("url_placeholder");
|
add_if_exists("url_placeholder");
|
||||||
add_if_exists("url_small");
|
add_if_exists("url_small");
|
||||||
add_if_exists("url_s");
|
if (!add_if_exists("url_s")) {
|
||||||
|
add_360x360_to = static_cast<ssize_t>(images.thumbnails.size());
|
||||||
|
}
|
||||||
add_if_exists("url");
|
add_if_exists("url");
|
||||||
if (j.contains("url_big") && j["url_big"].is_string()) {
|
if (j.contains("url_big") && j["url_big"].is_string()) {
|
||||||
images.original = j["url_big"].get<std::string>();
|
images.original = j["url_big"].get<std::string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (add_360x360_to >= 0) {
|
||||||
|
std::optional<std::string> c_360x360 = get_360x360_illust_thumbnail(images.original_or_thumbnail());
|
||||||
|
if (c_360x360) {
|
||||||
|
images.thumbnails.insert(images.thumbnails.begin() + add_360x360_to, std::move(*c_360x360));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ struct User {
|
||||||
|
|
||||||
struct Tag {
|
struct Tag {
|
||||||
std::string japanese;
|
std::string japanese;
|
||||||
std::optional<std::string> romaji;
|
|
||||||
std::optional<std::string> english;
|
std::optional<std::string> english;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +52,19 @@ struct Illusts {
|
||||||
size_t total_pages;
|
size_t total_pages;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SearchResults {
|
||||||
|
Illusts illusts;
|
||||||
|
std::unordered_map<std::string, std::string> tag_translations;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SearchSuggestion {
|
||||||
|
std::string tag;
|
||||||
|
std::optional<std::string> english_tag;
|
||||||
|
};
|
||||||
|
|
||||||
void from_json(const nlohmann::json& j, User& user);
|
void from_json(const nlohmann::json& j, User& user);
|
||||||
void from_json(const nlohmann::json& j, Tag& tag);
|
void from_json(const nlohmann::json& j, Tag& tag);
|
||||||
void from_json(const nlohmann::json& j, Illust& illust);
|
void from_json(const nlohmann::json& j, Illust& illust);
|
||||||
void from_json(const nlohmann::json& j, Illusts& illusts);
|
void from_json(const nlohmann::json& j, Illusts& illusts);
|
||||||
|
void from_json(const nlohmann::json& j, SearchResults& search_results);
|
||||||
|
void from_json(const nlohmann::json& j, SearchSuggestion& search_suggestion);
|
||||||
|
|
|
@ -11,7 +11,7 @@ static inline Element generate_images(const httplib::Request& req, const Config&
|
||||||
static inline Element generate_preview_images(const httplib::Request& req, const Config& config, const Illust& illust);
|
static inline Element generate_preview_images(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||||
static inline std::vector<blankie::html::Node> parse_description_line(const httplib::Request& req, const Config& config, std::string str);
|
static inline std::vector<blankie::html::Node> parse_description_line(const httplib::Request& req, const Config& config, std::string str);
|
||||||
static inline Element generate_description(const httplib::Request& req, const Config& config, const std::string& description);
|
static inline Element generate_description(const httplib::Request& req, const Config& config, const std::string& description);
|
||||||
static inline Element generate_illust_tags(const Illust& illust);
|
static inline Element generate_illust_tags(const httplib::Request& req, const Config& config, const Illust& illust);
|
||||||
|
|
||||||
static inline bool is_true(const std::string& str);
|
static inline bool is_true(const std::string& str);
|
||||||
static inline std::string time_to_string(time_t time);
|
static inline std::string time_to_string(time_t time);
|
||||||
|
@ -42,13 +42,14 @@ void artworks_route(const httplib::Request& req, httplib::Response& res, const C
|
||||||
Element body("body", {
|
Element body("body", {
|
||||||
Element("h2", {illust.title}),
|
Element("h2", {illust.title}),
|
||||||
generate_user_link(req, config, illust),
|
generate_user_link(req, config, illust),
|
||||||
|
Element("br"),
|
||||||
!preview ? generate_images(req, config, illust) : generate_preview_images(req, config, illust),
|
!preview ? generate_images(req, config, illust) : generate_preview_images(req, config, illust),
|
||||||
Element("br")
|
Element("br")
|
||||||
});
|
});
|
||||||
if (illust.comment) {
|
if (illust.comment) {
|
||||||
body.nodes.push_back(generate_description(req, config, *illust.comment));
|
body.nodes.push_back(generate_description(req, config, *illust.comment));
|
||||||
}
|
}
|
||||||
body.nodes.push_back(generate_illust_tags(illust));
|
body.nodes.push_back(generate_illust_tags(req, config, illust));
|
||||||
body.nodes.push_back(Element("p", {time_to_string(illust.upload_time)}));
|
body.nodes.push_back(Element("p", {time_to_string(illust.upload_time)}));
|
||||||
serve(req, res, config, std::move(illust.title), std::move(body));
|
serve(req, res, config, std::move(illust.title), std::move(body));
|
||||||
}
|
}
|
||||||
|
@ -160,7 +161,7 @@ static inline Element generate_description(const httplib::Request& req, const Co
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline Element generate_illust_tags(const Illust& illust) {
|
static inline Element generate_illust_tags(const httplib::Request& req, const Config& config, const Illust& illust) {
|
||||||
Element div("div", {{"class", "illusttags"}}, {});
|
Element div("div", {{"class", "illusttags"}}, {});
|
||||||
|
|
||||||
if (illust.ai_generated) {
|
if (illust.ai_generated) {
|
||||||
|
@ -170,11 +171,12 @@ static inline Element generate_illust_tags(const Illust& illust) {
|
||||||
for (const Tag& i : illust.tags) {
|
for (const Tag& i : illust.tags) {
|
||||||
std::string tag = [&]() {
|
std::string tag = [&]() {
|
||||||
if (i.english) return *i.english;
|
if (i.english) return *i.english;
|
||||||
if (i.romaji) return *i.romaji;
|
|
||||||
return i.japanese;
|
return i.japanese;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
div.nodes.push_back(Element("span", {"#", std::move(tag)}));
|
div.nodes.push_back(Element("a", {{"href", get_origin(req, config) + "/tags/" + blankie::murl::escape(i.japanese)}}, {
|
||||||
|
"#", std::move(tag)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
|
|
|
@ -37,6 +37,11 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* USER PAGE (and a tiny bit for illustrations page) */
|
/* USER PAGE (and a tiny bit for illustrations page) */
|
||||||
.cover {
|
.cover {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -61,10 +66,6 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* USER ILLUSTRATIONS PAGE (and illustrations page) */
|
/* USER ILLUSTRATIONS PAGE (and illustrations page) */
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.grid {
|
.grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -94,6 +95,14 @@ void css_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SEARCH RESULTS PAGE */
|
||||||
|
.searchsuggestions {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: .5em;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ERROR PAGE */
|
/* ERROR PAGE */
|
||||||
.error {
|
.error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -15,6 +15,12 @@ void home_route(const httplib::Request& req, httplib::Response& res, const Confi
|
||||||
"Pixwhile is an alternative frontend to Pixiv that utilizes no Javascript. (",
|
"Pixwhile is an alternative frontend to Pixiv that utilizes no Javascript. (",
|
||||||
Element("a", {{"href", "https://gitlab.com/blankX/pixwhile"}}, {"source code"}),
|
Element("a", {{"href", "https://gitlab.com/blankX/pixwhile"}}, {"source code"}),
|
||||||
")",
|
")",
|
||||||
|
Element("form", {{"method", "get"}, {"action", "search"}}, {
|
||||||
|
Element("br"),
|
||||||
|
Element("input", {{"name", "q"}, {"required", ""}}, {}),
|
||||||
|
" ",
|
||||||
|
Element("button", {"Search for illustrations"})
|
||||||
|
}),
|
||||||
Element("h2", {"Try it out"}),
|
Element("h2", {"Try it out"}),
|
||||||
Element("ul", {
|
Element("ul", {
|
||||||
get_artwork_element(106623268, "アル社長の日常", 1960050, "torino"),
|
get_artwork_element(106623268, "アル社長の日常", 1960050, "torino"),
|
||||||
|
@ -24,12 +30,13 @@ void home_route(const httplib::Request& req, httplib::Response& res, const Confi
|
||||||
Element("ul", {
|
Element("ul", {
|
||||||
Element("li", {"Does not contain an asinine amount of Javascript"}),
|
Element("li", {"Does not contain an asinine amount of Javascript"}),
|
||||||
Element("li", {"Allows you to open the original versions of cover images and profile pictures"}),
|
Element("li", {"Allows you to open the original versions of cover images and profile pictures"}),
|
||||||
Element("li", {"Can view illustrations and list user illustrations"})
|
Element("li", {"Can view illustrations and list user illustrations"}),
|
||||||
|
Element("li", {"Can search for newest and oldest illustrations"})
|
||||||
}),
|
}),
|
||||||
Element("h2", {"Missing Features"}),
|
Element("h2", {"Missing Features"}),
|
||||||
Element("p", {"This list is not exhaustive, nor does it mean that these are being worked on."}),
|
Element("p", {"This list is not exhaustive, nor does it mean that these are being worked on."}),
|
||||||
Element("ul", {
|
Element("ul", {
|
||||||
Element("li", {"No search"}),
|
Element("li", {"Can only search for newest and oldest illustrations"}),
|
||||||
Element("li", {"No ability to login"}),
|
Element("li", {"No ability to login"}),
|
||||||
Element("li", {"No ability to see comments or like counts"}),
|
Element("li", {"No ability to see comments or like counts"}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 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 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 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,177 @@
|
||||||
|
#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,
|
||||||
|
const std::vector<SearchSuggestion>& search_suggestions);
|
||||||
|
static inline Element generate_search_suggestions(const httplib::Request& req, const Config& config,
|
||||||
|
const std::vector<std::string>& tags, const std::vector<SearchSuggestion>& search_suggestions, bool open_by_default);
|
||||||
|
|
||||||
|
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);
|
||||||
|
static inline std::string join(const std::vector<std::string>& items, 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;
|
||||||
|
}
|
||||||
|
std::vector<SearchSuggestion> search_suggestions;
|
||||||
|
try {
|
||||||
|
search_suggestions = pixiv_client.get_search_suggestions(tags.back());
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
res.status = 500;
|
||||||
|
serve_error(req, res, config, "500: Internal server error", "Failed to get search suggestions", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element body("body", {
|
||||||
|
generate_header(req, config, search_results, query, tags, order, search_suggestions),
|
||||||
|
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,
|
||||||
|
const std::vector<SearchSuggestion>& search_suggestions) {
|
||||||
|
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"})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!search_suggestions.empty()) {
|
||||||
|
header.nodes.push_back(generate_search_suggestions(req, config, tags, search_suggestions, search_results.illusts.total_illusts == 0));
|
||||||
|
}
|
||||||
|
header.nodes.push_back(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 inline Element generate_search_suggestions(const httplib::Request& req, const Config& config,
|
||||||
|
const std::vector<std::string>& tags, const std::vector<SearchSuggestion>& search_suggestions, bool open_by_default) {
|
||||||
|
std::vector<blankie::html::Node> ul_nodes;
|
||||||
|
ul_nodes.reserve(search_suggestions.size());
|
||||||
|
for (const SearchSuggestion& search_suggestion : search_suggestions) {
|
||||||
|
std::string text = search_suggestion.tag;
|
||||||
|
if (search_suggestion.english_tag) {
|
||||||
|
text += " (";
|
||||||
|
text += *search_suggestion.english_tag;
|
||||||
|
text += ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> new_tags = tags;
|
||||||
|
new_tags.pop_back();
|
||||||
|
new_tags.push_back(search_suggestion.tag);
|
||||||
|
std::string url = get_origin(req, config) + "/tags/" + blankie::murl::escape(join(new_tags, ' '));
|
||||||
|
|
||||||
|
ul_nodes.push_back(Element("li", {
|
||||||
|
Element("a", {{"href", std::move(url)}}, {std::move(text)})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Element details("details", {
|
||||||
|
Element("summary", {"Search suggestions"}),
|
||||||
|
Element("ul", {{"class", "searchsuggestions"}}, ul_nodes)
|
||||||
|
});
|
||||||
|
if (open_by_default) {
|
||||||
|
details.attributes.push_back({"open", ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::string join(const std::vector<std::string>& items, char c) {
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < items.size(); i++) {
|
||||||
|
if (i) {
|
||||||
|
ret += c;
|
||||||
|
}
|
||||||
|
ret += items[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -6,9 +6,6 @@
|
||||||
#include "../../pixivclient.h"
|
#include "../../pixivclient.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
static Element generate_pager(const Illusts& illusts, size_t page, bool first_selector);
|
|
||||||
static inline Element generate_content(const httplib::Request& req, const Config& config, const Illusts& illusts);
|
|
||||||
|
|
||||||
void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) {
|
void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client) {
|
||||||
uint64_t user_id = to_ull(req.matches[1].str());
|
uint64_t user_id = to_ull(req.matches[1].str());
|
||||||
uint64_t page = req.has_param("p") ? to_ull(req.get_param_value("p")) - 1 : 0;
|
uint64_t page = req.has_param("p") ? to_ull(req.get_param_value("p")) - 1 : 0;
|
||||||
|
@ -35,55 +32,7 @@ void user_illustrations_route(const httplib::Request& req, httplib::Response& re
|
||||||
|
|
||||||
Element body("body", {
|
Element body("body", {
|
||||||
generate_user_header(std::move(user), config),
|
generate_user_header(std::move(user), config),
|
||||||
generate_pager(illusts, page, true),
|
generate_illusts_pager(req, config, illusts, page, "illusts")
|
||||||
Element("br"),
|
|
||||||
generate_content(req, config, illusts),
|
|
||||||
generate_pager(illusts, page, false)
|
|
||||||
});
|
});
|
||||||
serve(req, res, config, user.display_name + "'s illustrations", std::move(body));
|
serve(req, res, config, user.display_name + "'s illustrations", std::move(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Element generate_pager(const Illusts& illusts, size_t page, bool first_selector) {
|
|
||||||
auto link = [](size_t new_page, const char* text, bool add_link) {
|
|
||||||
using namespace std::string_literals;
|
|
||||||
|
|
||||||
Element b("b");
|
|
||||||
std::string href = "?p="s + std::to_string(new_page) + "#pageselector";
|
|
||||||
|
|
||||||
if (add_link) {
|
|
||||||
b.nodes.push_back(Element("a", {{"href", std::move(href)}}, {text}));
|
|
||||||
} else {
|
|
||||||
b.nodes.push_back(text);
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
};
|
|
||||||
|
|
||||||
Element div("div", {{"class", "center"}}, {
|
|
||||||
link(1, "First", page != 0), " ",
|
|
||||||
link(page, "Prev", page != 0), " ",
|
|
||||||
std::to_string(page + 1), "/", std::to_string(illusts.total_pages), " ",
|
|
||||||
link(page + 2, "Next", page + 1 < illusts.total_pages), " ",
|
|
||||||
link(illusts.total_pages, "Last", page + 1 < illusts.total_pages)
|
|
||||||
});
|
|
||||||
if (first_selector) {
|
|
||||||
div.attributes.push_back({"id", "pageselector"});
|
|
||||||
}
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline Element generate_content(const httplib::Request& req, const Config& config, const Illusts& illusts) {
|
|
||||||
Element div("div", {{"class", "grid"}}, {});
|
|
||||||
|
|
||||||
div.nodes.reserve(illusts.illusts.size());
|
|
||||||
for (const Illust& i : illusts.illusts) {
|
|
||||||
std::string illust_url = get_origin(req, config) + "/artworks/" + std::to_string(i.illust_id);
|
|
||||||
std::string image_url = proxy_image_url(config, i.images[0].thumbnail_or_original(1));
|
|
||||||
|
|
||||||
div.nodes.push_back(Element("a", {{"href", {std::move(illust_url)}}}, {
|
|
||||||
Element("img", {{"loading", "lazy"}, {"src", std::move(image_url)}}, {}),
|
|
||||||
Element("p", {i.title})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "pixivmodels.h"
|
||||||
#include "servehelper.h"
|
#include "servehelper.h"
|
||||||
|
|
||||||
|
static Element generate_pager(const Illusts& illusts, size_t page, const char* id);
|
||||||
|
static inline Element generate_content(const httplib::Request& req, const Config& config, const Illusts& illusts);
|
||||||
|
|
||||||
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element) {
|
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element) {
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
@ -55,6 +59,8 @@ void serve_redirect(const httplib::Request& req, httplib::Response& res, const C
|
||||||
serve(req, res, config, "Redirecting to "s + std::move(url) + "…", std::move(body));
|
serve(req, res, config, "Redirecting to "s + std::move(url) + "…", std::move(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string get_origin(const httplib::Request& req, const Config& config) {
|
std::string get_origin(const httplib::Request& req, const Config& config) {
|
||||||
if (req.has_header("X-Canonical-Origin")) {
|
if (req.has_header("X-Canonical-Origin")) {
|
||||||
return req.get_header_value("X-Canonical-Origin");
|
return req.get_header_value("X-Canonical-Origin");
|
||||||
|
@ -91,3 +97,54 @@ std::string proxy_image_url(const Config& config, blankie::murl::Url url) {
|
||||||
}
|
}
|
||||||
return proxy_url(config.image_proxy_url, std::move(url));
|
return proxy_url(config.image_proxy_url, std::move(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Element generate_illusts_pager(const httplib::Request& req, const Config& config, const Illusts& illusts, size_t page, const char* id) {
|
||||||
|
return Element("div", {{"id", id}}, {
|
||||||
|
generate_pager(illusts, page, id),
|
||||||
|
Element("br"),
|
||||||
|
generate_content(req, config, illusts),
|
||||||
|
generate_pager(illusts, page, id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Element generate_pager(const Illusts& illusts, size_t page, const char* id) {
|
||||||
|
auto link = [&](size_t new_page, const char* text, bool add_link) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
Element b("b");
|
||||||
|
if (add_link) {
|
||||||
|
std::string href = "?p="s + std::to_string(new_page) + '#' + id;
|
||||||
|
b.nodes.push_back(Element("a", {{"href", std::move(href)}}, {text}));
|
||||||
|
} else {
|
||||||
|
b.nodes.push_back(text);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Element("div", {{"class", "center"}}, {
|
||||||
|
link(1, "First", page != 0), " ",
|
||||||
|
link(page, "Prev", page != 0), " ",
|
||||||
|
std::to_string(page + 1), "/", std::to_string(illusts.total_pages), " ",
|
||||||
|
link(page + 2, "Next", page + 1 < illusts.total_pages), " ",
|
||||||
|
link(illusts.total_pages, "Last", page + 1 < illusts.total_pages)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Element generate_content(const httplib::Request& req, const Config& config, const Illusts& illusts) {
|
||||||
|
Element div("div", {{"class", "grid"}}, {});
|
||||||
|
|
||||||
|
div.nodes.reserve(illusts.illusts.size());
|
||||||
|
for (const Illust& i : illusts.illusts) {
|
||||||
|
std::string illust_url = get_origin(req, config) + "/artworks/" + std::to_string(i.illust_id);
|
||||||
|
std::string image_url = proxy_image_url(config, i.images[0].thumbnail_or_original(1));
|
||||||
|
|
||||||
|
div.nodes.push_back(Element("a", {{"href", {std::move(illust_url)}}}, {
|
||||||
|
Element("img", {{"loading", "lazy"}, {"src", std::move(image_url)}}, {}),
|
||||||
|
Element("p", {i.title})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
|
@ -7,12 +7,16 @@
|
||||||
#include "blankie/serializer.h"
|
#include "blankie/serializer.h"
|
||||||
|
|
||||||
struct Config; // forward declaration from config.h
|
struct Config; // forward declaration from config.h
|
||||||
|
struct Illusts; // forward declaration from pixivmodels.h
|
||||||
using Element = blankie::html::Element;
|
using Element = blankie::html::Element;
|
||||||
|
|
||||||
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element);
|
void serve(const httplib::Request& req, httplib::Response& res, const Config& config, std::string title, Element element);
|
||||||
void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config,
|
void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config,
|
||||||
std::string title, std::optional<std::string> subtitle = std::nullopt, std::optional<std::string> info = std::nullopt);
|
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);
|
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 get_origin(const httplib::Request& req, const Config& config);
|
||||||
std::string proxy_url(blankie::murl::Url base, blankie::murl::Url url);
|
std::string proxy_url(blankie::murl::Url base, blankie::murl::Url url);
|
||||||
std::string proxy_image_url(const Config& config, blankie::murl::Url url);
|
std::string proxy_image_url(const Config& config, blankie::murl::Url url);
|
||||||
|
|
||||||
|
Element generate_illusts_pager(const httplib::Request& req, const Config& config, const Illusts& illusts, size_t page, const char* id);
|
||||||
|
|
Loading…
Reference in New Issue