Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions cagliostr.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
#include <malloc.h>
#endif

#include <algorithm>
#include <cctype>
#include <nlohmann/json.hpp>
#include <optional>
#include <spdlog/common.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <sstream>

using event_t = struct event_t {
std::string id;
Expand All @@ -27,6 +30,7 @@ using filter_t = struct filter_t {
std::vector<std::string> authors{};
std::vector<int> kinds{};
std::vector<std::vector<std::string>> tags{};
std::vector<std::vector<std::string>> and_tags{};
std::time_t since{};
std::time_t until{};
int limit{500};
Expand Down Expand Up @@ -126,6 +130,119 @@ inline bool created_at_within_limits(std::time_t created_at, std::time_t now,
return true;
}

inline bool matched_filters(const std::vector<filter_t> &filters,
const event_t &ev) {
auto found = false;
for (const auto &filter : filters) {
if (!filter.ids.empty()) {
const auto result =
std::find(filter.ids.begin(), filter.ids.end(), ev.id);
if (result == filter.ids.end()) {
continue;
}
}
if (!filter.authors.empty()) {
const auto result =
std::find(filter.authors.begin(), filter.authors.end(), ev.pubkey);
if (result == filter.authors.end()) {
continue;
}
}
if (!filter.kinds.empty()) {
const auto result =
std::find(filter.kinds.begin(), filter.kinds.end(), ev.kind);
if (result == filter.kinds.end()) {
continue;
}
}
if (filter.since > 0) {
if (filter.since > ev.created_at) {
continue;
}
}
if (filter.until > 0) {
if (ev.created_at > filter.until) {
continue;
}
}
if (!filter.tags.empty()) {
auto all_tags_matched = true;
for (const auto &filter_tag : filter.tags) {
if (filter_tag.size() < 2)
continue;
bool this_tag_matched = false;
for (const auto &tag : ev.tags) {
if (tag.size() < 2)
continue;
if (tag[0] != filter_tag[0])
continue;
for (size_t fi = 1; fi < filter_tag.size(); fi++) {
if (tag[1] == filter_tag[fi]) {
this_tag_matched = true;
break;
}
}
if (this_tag_matched)
break;
}
if (!this_tag_matched) {
all_tags_matched = false;
break;
}
}
if (!all_tags_matched) {
continue;
}
}
if (!filter.and_tags.empty()) {
auto all_and_tags_matched = true;
for (const auto &filter_tag : filter.and_tags) {
if (filter_tag.size() < 2)
continue;
const auto &key = filter_tag[0];
for (size_t fi = 1; fi < filter_tag.size(); fi++) {
bool found_value = false;
for (const auto &tag : ev.tags) {
if (tag.size() >= 2 && tag[0] == key && tag[1] == filter_tag[fi]) {
found_value = true;
break;
}
}
if (!found_value) {
all_and_tags_matched = false;
break;
}
}
if (!all_and_tags_matched)
break;
}
if (!all_and_tags_matched) {
continue;
}
}
if (!filter.search.empty()) {
auto found_search = true;
std::string content = ev.content;
std::transform(content.begin(), content.end(), content.begin(),
::tolower);
std::istringstream iss(filter.search);
std::string word;
while (iss >> word) {
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
if (content.find(word) == std::string::npos) {
found_search = false;
break;
}
}
if (!found_search) {
continue;
}
}
found = true;
}
return found;
}

inline std::string escape_like(const std::string &data) {
std::string result;
for (const auto c : data) {
Expand Down
133 changes: 34 additions & 99 deletions main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static auto nip11 = nlohmann::json{
{"contact", "mattn.jp@gmail.com"},
{"supported_nips",
nlohmann::json::array({1, 2, 4, 9, 11, 12, 13, 15, 16, 20, 22, 26, 28, 33,
40, 42, 45, 50, 62, 67, 70})},
40, 42, 45, 50, 62, 67, 70, 91})},
{"software", "https://github.com/mattn/cagliostr"},
{"version", VERSION},
{"limitation", nlohmann::json{{"max_message_length", 1024 * 1024 * 5},
Expand All @@ -71,7 +71,9 @@ static auto nip11 = nlohmann::json{
{"min_pow_difficulty", 0},
{"auth_required", false},
{"payment_required", false},
{"restricted_writes", false}}},
{"restricted_writes", false},
{"max_tags_and", 20},
{"max_tags_per_and", 20}}},
{"fees", nlohmann::json::object()},
{"relay_countries", nlohmann::json::array({"JP"})},
{"icon",
Expand Down Expand Up @@ -294,17 +296,37 @@ static bool make_filter(filter_t &filter, const nlohmann::json &data) {
}
}
for (auto it = data.cbegin(); it != data.cend(); ++it) {
if (!it.key().empty() && it.key().front() == '#' && it.value().is_array()) {
std::vector<std::string> tag = {it.key().substr(1)};
for (const auto &v : it.value()) {
if (!v.is_string()) {
console->warn("make_filter: tag {} elements must be string",
it.key());
return false;
}
tag.push_back(v.get<std::string>());
if (it.key().size() < 2 || !it.value().is_array()) {
continue;
}
const auto prefix = it.key().front();
if (prefix != '#' && prefix != '&') {
continue;
}
std::vector<std::string> tag = {it.key().substr(1)};
for (const auto &v : it.value()) {
if (!v.is_string()) {
console->warn("make_filter: tag {} elements must be string", it.key());
return false;
}
filter.tags.push_back(tag);
tag.push_back(v.get<std::string>());
}
if (prefix == '&') {
const int max_per = nip11["limitation"]["max_tags_per_and"];
if (static_cast<int>(tag.size()) - 1 > max_per) {
console->warn("make_filter: AND tag {} exceeds max_tags_per_and ({})",
it.key(), max_per);
return false;
}
const int max_and = nip11["limitation"]["max_tags_and"];
if (static_cast<int>(filter.and_tags.size()) >= max_and) {
console->warn("make_filter: too many AND tag clauses (max {})",
max_and);
return false;
}
filter.and_tags.push_back(std::move(tag));
} else {
filter.tags.push_back(std::move(tag));
}
}
if (data.count("since") > 0) {
Expand Down Expand Up @@ -458,93 +480,6 @@ static void do_relay_close(WebSocket *ws, const nlohmann::json &data) {
}
}

static bool matched_filters(const std::vector<filter_t> &filters,
const event_t &ev) {
auto found = false;
for (const auto &filter : filters) {
if (!filter.ids.empty()) {
const auto result =
std::find(filter.ids.begin(), filter.ids.end(), ev.id);
if (result == filter.ids.end()) {
continue;
}
}
if (!filter.authors.empty()) {
const auto result =
std::find(filter.authors.begin(), filter.authors.end(), ev.pubkey);
if (result == filter.authors.end()) {
continue;
}
}
if (!filter.kinds.empty()) {
const auto result =
std::find(filter.kinds.begin(), filter.kinds.end(), ev.kind);
if (result == filter.kinds.end()) {
continue;
}
}
if (filter.since > 0) {
if (filter.since > ev.created_at) {
continue;
}
}
if (filter.until > 0) {
if (ev.created_at > filter.until) {
continue;
}
}
if (!filter.tags.empty()) {
auto all_tags_matched = true;
for (const auto &filter_tag : filter.tags) {
if (filter_tag.size() < 2)
continue;
bool this_tag_matched = false;
for (const auto &tag : ev.tags) {
if (tag.size() < 2)
continue;
if (tag[0] != filter_tag[0])
continue;
for (size_t fi = 1; fi < filter_tag.size(); fi++) {
if (tag[1] == filter_tag[fi]) {
this_tag_matched = true;
break;
}
}
if (this_tag_matched)
break;
}
if (!this_tag_matched) {
all_tags_matched = false;
break;
}
}
if (!all_tags_matched) {
continue;
}
}
if (!filter.search.empty()) {
auto found_search = true;
std::string content = ev.content;
std::transform(content.begin(), content.end(), content.begin(),
::tolower);
std::istringstream iss(filter.search);
std::string word;
while (iss >> word) {
std::transform(word.begin(), word.end(), word.begin(), ::tolower);
if (content.find(word) == std::string::npos) {
found_search = false;
break;
}
}
if (!found_search) {
continue;
}
}
found = true;
}
return found;
}

static void do_relay_event(WebSocket *ws, const nlohmann::json &data) {
try {
const event_t ev = data[1];
Expand Down
14 changes: 14 additions & 0 deletions postgresql.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,20 @@ static bool send_records(std::function<void(const nlohmann::json &)> sender,
conditions.push_back("(" + join(match, " OR ") + ")");
}
}
if (!filter.and_tags.empty()) {
for (const auto &tag : filter.and_tags) {
if (tag.size() < 2) {
continue;
}
const auto &first = tag[0];
for (decltype(tag.size()) i = 1; i < tag.size(); i++) {
nlohmann::json data = nlohmann::json::array(
{nlohmann::json::array({first, tag[i]})});
params.append(data.dump());
conditions.push_back("tags @> $" + std::to_string(++pno) + "::jsonb");
}
}
}
if (filter.since != 0) {
std::ostringstream os;
os << filter.since;
Expand Down
14 changes: 14 additions & 0 deletions sqlite3.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ static bool send_records(std::function<void(const nlohmann::json &)> sender,
conditions.push_back("(" + join(match, " OR ") + ")");
}
}
if (!filter.and_tags.empty()) {
for (const auto &tag : filter.and_tags) {
if (tag.size() < 2) {
continue;
}
const auto &first = tag[0];
for (decltype(tag.size()) i = 1; i < tag.size(); i++) {
nlohmann::json data = {first, tag[i]};
params.push_back({.t = PARAM_TYPE_STRING,
.s = "%" + escape_like(data.dump()) + "%"});
conditions.push_back(R"(tags LIKE ? ESCAPE '\')");
}
}
}
if (filter.since != 0) {
std::ostringstream os;
os << filter.since;
Expand Down
Loading
Loading