Add the option to automatically open content warnings
This commit is contained in:
parent
e231afb49c
commit
402e74dd6e
|
@ -29,8 +29,8 @@ list(APPEND FLAGS -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-s
|
||||||
add_link_options(${FLAGS})
|
add_link_options(${FLAGS})
|
||||||
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp hiredis_wrapper.cpp
|
add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp settings.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp hiredis_wrapper.cpp
|
||||||
routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp
|
routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp routes/user_settings.cpp
|
||||||
blankie/serializer.cpp blankie/escape.cpp)
|
blankie/serializer.cpp blankie/escape.cpp)
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
|
|
4
main.cpp
4
main.cpp
|
@ -40,9 +40,13 @@ int main(int argc, char** argv) {
|
||||||
atexit(MastodonClient::cleanup);
|
atexit(MastodonClient::cleanup);
|
||||||
|
|
||||||
httplib::Server server;
|
httplib::Server server;
|
||||||
|
server.set_payload_max_length(8192);
|
||||||
|
|
||||||
server.Get("/", home_route);
|
server.Get("/", home_route);
|
||||||
server.Get("/style.css", css_route);
|
server.Get("/style.css", css_route);
|
||||||
|
server.Get("/settings", user_settings_route);
|
||||||
|
server.Post("/settings", user_settings_route);
|
||||||
|
|
||||||
server.Get("/(" DOMAIN_RE ")/@(" ACCT_RE ")(|/with_replies|/media)", user_route);
|
server.Get("/(" DOMAIN_RE ")/@(" ACCT_RE ")(|/with_replies|/media)", user_route);
|
||||||
server.Get("/(" DOMAIN_RE ")/users/(" ACCT_RE ")(|/with_replies|/media)", [](const httplib::Request& req, httplib::Response& res) {
|
server.Get("/(" DOMAIN_RE ")/users/(" ACCT_RE ")(|/with_replies|/media)", [](const httplib::Request& req, httplib::Response& res) {
|
||||||
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + req.matches.str(3), true);
|
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + req.matches.str(3), true);
|
||||||
|
|
|
@ -14,6 +14,10 @@ static const constexpr char css[] = R"EOF(
|
||||||
--error-border-color: red;
|
--error-border-color: red;
|
||||||
--error-text-color: white;
|
--error-text-color: white;
|
||||||
|
|
||||||
|
--success-background-color: green;
|
||||||
|
--success-border-color: lightgreen;
|
||||||
|
--success-text-color: white;
|
||||||
|
|
||||||
--accent-color: #962AC3;
|
--accent-color: #962AC3;
|
||||||
--bright-accent-color: #DE6DE6;
|
--bright-accent-color: #DE6DE6;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +167,20 @@ svg {
|
||||||
padding: revert;
|
padding: revert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SUCCESS BOX */
|
||||||
|
.success {
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--success-background-color);
|
||||||
|
color: var(--success-text-color);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--success-border-color);
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.success * {
|
||||||
|
margin: revert;
|
||||||
|
padding: revert;
|
||||||
|
}
|
||||||
|
|
||||||
/* USER PAGE */
|
/* USER PAGE */
|
||||||
.user_page-header {
|
.user_page-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -252,6 +270,18 @@ svg {
|
||||||
margin: revert;
|
margin: revert;
|
||||||
padding: revert;
|
padding: revert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* USER SETTINGS PAGE */
|
||||||
|
.user_settings_page-form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.user_settings_page-form input[type=submit] {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
padding: revert;
|
||||||
|
}
|
||||||
|
.user_settings_page-form .cancel {
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
)EOF";
|
)EOF";
|
||||||
// one for \0, one for trailing newline
|
// one for \0, one for trailing newline
|
||||||
#define CSS_LEN sizeof(css) / sizeof(css[0]) - 2
|
#define CSS_LEN sizeof(css) / sizeof(css[0]) - 2
|
||||||
|
|
|
@ -8,6 +8,7 @@ void home_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
"their posts, posts themselves (and their context), posts with a certain hashtag, "
|
"their posts, posts themselves (and their context), posts with a certain hashtag, "
|
||||||
"and instance details.",
|
"and instance details.",
|
||||||
}),
|
}),
|
||||||
|
Element("p", {Element("a", {{"href", get_origin(req) + "/settings"}}, {"Configure settings"})}),
|
||||||
Element("h2", {"Example links"}),
|
Element("h2", {"Example links"}),
|
||||||
Element("ul", {
|
Element("ul", {
|
||||||
Element("li", {Element("a", {{"href", get_origin(req) + "/vt.social/@lina"}}, {"Asahi Lina (朝日リナ) // nullptr::live"})}),
|
Element("li", {Element("a", {{"href", get_origin(req) + "/vt.social/@lina"}}, {"Asahi Lina (朝日リナ) // nullptr::live"})}),
|
||||||
|
|
|
@ -10,3 +10,4 @@ void user_route(const httplib::Request& req, httplib::Response& res);
|
||||||
void status_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 tags_route(const httplib::Request& req, httplib::Response& res);
|
||||||
void about_route(const httplib::Request& req, httplib::Response& res);
|
void about_route(const httplib::Request& req, httplib::Response& res);
|
||||||
|
void user_settings_route(const httplib::Request& req, httplib::Response& res);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
#include "routes.h"
|
||||||
|
#include "../servehelper.h"
|
||||||
|
#include "../settings.h"
|
||||||
|
#include "../timeutils.h"
|
||||||
|
#include "../curlu_wrapper.h"
|
||||||
|
|
||||||
|
static void set_cookie(const httplib::Request& req, httplib::Response& res, const char* key, std::string_view value);
|
||||||
|
|
||||||
|
|
||||||
|
void user_settings_route(const httplib::Request& req, httplib::Response& res) {
|
||||||
|
UserSettings settings;
|
||||||
|
|
||||||
|
if (req.method == "POST") {
|
||||||
|
for (const auto& i : req.params) {
|
||||||
|
settings.set(i.first, i.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cookie(req, res, "auto-open-cw", settings.auto_open_cw ? "true" : "false");
|
||||||
|
} else {
|
||||||
|
settings.load_from_cookies(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element auto_open_cw_checkbox("input", {{"type", "checkbox"}, {"name", "auto-open-cw"}, {"value", "true"}}, {});
|
||||||
|
if (settings.auto_open_cw) {
|
||||||
|
auto_open_cw_checkbox.attributes.push_back({"checked", ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
Element body("body", {
|
||||||
|
Element("form", {{"class", "user_settings_page-form"}, {"method", "post"}}, {
|
||||||
|
Element("label", {
|
||||||
|
std::move(auto_open_cw_checkbox),
|
||||||
|
" Automatically open Content Warnings",
|
||||||
|
}),
|
||||||
|
|
||||||
|
Element("br"),
|
||||||
|
Element("input", {{"type", "submit"}, {"value", "Save"}}, {}),
|
||||||
|
}),
|
||||||
|
Element("form", {{"class", "user_settings_page-form"}, {"method", "get"}, {"action", get_origin(req)}}, {
|
||||||
|
Element("input", {{"class", "cancel"}, {"type", "submit"}, {"value", "Cancel"}}, {}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (req.method == "POST") {
|
||||||
|
body.nodes.insert(body.nodes.begin(), Element("div", {{"class", "success"}}, {
|
||||||
|
Element("h3", {"Settings saved!"}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
serve(req, res, "User settings", std::move(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void set_cookie(const httplib::Request& req, httplib::Response& res, const char* key, std::string_view value) {
|
||||||
|
CurlUrl origin;
|
||||||
|
origin.set(CURLUPART_URL, get_origin(req));
|
||||||
|
|
||||||
|
std::string header = std::string(key) + '=' + std::string(value)
|
||||||
|
+ "; HttpOnly; SameSite=Strict; Domain=" + origin.get(CURLUPART_HOST).get() + "; Path=" + origin.get(CURLUPART_PATH).get()
|
||||||
|
+ "; Expires=" + to_web_date(current_time() + 365 * 24 * 60 * 60);
|
||||||
|
if (strcmp(origin.get(CURLUPART_SCHEME).get(), "https") == 0) {
|
||||||
|
header += "; Secure";
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set_header("Set-Cookie", header);
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "font_awesome.h"
|
#include "font_awesome.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "settings.h"
|
||||||
#include "models.h"
|
#include "models.h"
|
||||||
#include "timeutils.h"
|
#include "timeutils.h"
|
||||||
#include "servehelper.h"
|
#include "servehelper.h"
|
||||||
|
@ -394,6 +395,9 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
|
||||||
Element("summary", {preprocess_html(req, post.emojis, std::move(spoiler_text))}),
|
Element("summary", {preprocess_html(req, post.emojis, std::move(spoiler_text))}),
|
||||||
std::move(contents),
|
std::move(contents),
|
||||||
});
|
});
|
||||||
|
if (UserSettings(req).auto_open_cw) {
|
||||||
|
contents.attributes.push_back({"open", ""});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Element div("div", {{"class", "post"}}, {
|
Element div("div", {{"class", "post"}}, {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
static void set_settings(std::string_view str, const char* delimiter, UserSettings& settings);
|
||||||
|
static inline bool lowercase_compare(std::string_view lhs, std::string_view rhs);
|
||||||
|
static bool parse_bool(std::string_view value);
|
||||||
|
|
||||||
|
|
||||||
|
void UserSettings::set(std::string_view key, std::string_view value) {
|
||||||
|
if (key == "auto-open-cw") {
|
||||||
|
this->auto_open_cw = parse_bool(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserSettings::load_from_cookies(const httplib::Request& req) {
|
||||||
|
for (const auto& i : req.headers) {
|
||||||
|
if (lowercase_compare(i.first, "cookie")) {
|
||||||
|
set_settings(i.second, "; ", *this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void set_settings(std::string_view str, const char* delimiter, UserSettings& settings) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
size_t offset = 0;
|
||||||
|
size_t new_offset = 0;
|
||||||
|
size_t delimiter_len = strlen(delimiter);
|
||||||
|
|
||||||
|
while (offset < str.size()) {
|
||||||
|
new_offset = str.find(delimiter, offset);
|
||||||
|
|
||||||
|
std::string_view item = str.substr(offset, new_offset != std::string_view::npos ? new_offset - offset : std::string_view::npos);
|
||||||
|
size_t equal_offset = item.find('=');
|
||||||
|
if (equal_offset == std::string_view::npos) {
|
||||||
|
throw std::invalid_argument("invalid user setting item: "s + std::string(item));
|
||||||
|
}
|
||||||
|
settings.set(item.substr(0, equal_offset), item.substr(equal_offset + 1));
|
||||||
|
|
||||||
|
if (new_offset == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset = new_offset + delimiter_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool lowercase_compare(std::string_view lhs, std::string_view rhs) {
|
||||||
|
if (lhs.size() != rhs.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lower = [](char c) {
|
||||||
|
return c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c;
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < lhs.size(); i++) {
|
||||||
|
if (lower(lhs[i]) != lower(rhs[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_bool(std::string_view value) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
if (value == "true") {
|
||||||
|
return true;
|
||||||
|
} else if (value == "false") {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("unknown boolean value: "s + std::string(value));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <httplib/httplib.h>
|
||||||
|
|
||||||
|
struct UserSettings {
|
||||||
|
bool auto_open_cw = false;
|
||||||
|
|
||||||
|
UserSettings() = default;
|
||||||
|
UserSettings(const httplib::Request& req) {
|
||||||
|
this->load_from_cookies(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(std::string_view key, std::string_view value);
|
||||||
|
void load_from_cookies(const httplib::Request& req);
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <cstring>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
#include "timeutils.h"
|
#include "timeutils.h"
|
||||||
|
@ -39,6 +40,19 @@ std::string to_rfc3339(time_t time) {
|
||||||
return std::string(buf, len);
|
return std::string(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* day_names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||||
|
static const char* month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||||
|
std::string to_web_date(time_t time) {
|
||||||
|
struct tm tm;
|
||||||
|
gmtime_r(&time, &tm);
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
size_t len = strftime(buf, 32, "XXX, %d XXX %Y %H:%M:%S GMT", &tm);
|
||||||
|
memcpy(buf, day_names[tm.tm_wday], 3);
|
||||||
|
memcpy(buf + 3 + 2 + 2 + 1, month_names[tm.tm_mon], 3);
|
||||||
|
return std::string(buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
std::string relative_time(time_t from, time_t to) {
|
std::string relative_time(time_t from, time_t to) {
|
||||||
time_t diff = to - from;
|
time_t diff = to - from;
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,5 @@ time_t current_time();
|
||||||
std::string short_time(time_t time);
|
std::string short_time(time_t time);
|
||||||
std::string full_time(time_t time);
|
std::string full_time(time_t time);
|
||||||
std::string to_rfc3339(time_t time);
|
std::string to_rfc3339(time_t time);
|
||||||
|
std::string to_web_date(time_t time);
|
||||||
std::string relative_time(time_t from, time_t to);
|
std::string relative_time(time_t from, time_t to);
|
||||||
|
|
Loading…
Reference in New Issue