From c564313f7e97f2fd7db98c2acca187c809a40a8c Mon Sep 17 00:00:00 2001 From: Michael Jurkoic Date: Tue, 12 Mar 2024 09:34:51 -0500 Subject: Add a filters struct for relay queries --- CMakeLists.txt | 1 + include/nostr.hpp | 33 +++++++++++++++++++++++++ src/filters.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/filters.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d472c1..4e46b70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCE_DIR ./src) set(CLIENT_SOURCE_DIR ./src/client) set(SOURCES ${SOURCE_DIR}/event.cpp + ${SOURCE_DIR}/filters.cpp ${SOURCE_DIR}/nostr_service.cpp ${CLIENT_SOURCE_DIR}/websocketpp_client.cpp ) diff --git a/include/nostr.hpp b/include/nostr.hpp index ec8d1a8..ce25446 100644 --- a/include/nostr.hpp +++ b/include/nostr.hpp @@ -15,6 +15,7 @@ namespace nostr { typedef std::vector RelayList; +typedef std::unordered_map> TagMap; // TODO: Add null checking to seralization and deserialization methods. /** @@ -55,6 +56,38 @@ private: void validate(); }; +/** + * @brief A set of filters for querying Nostr relays. + * @remark The `limit` field should always be included to keep the response size reasonable. The + * `since` field is not required, and the `until` field will default to the present. At least one + * of the other fields must be set for a valid filter. + */ +struct Filters +{ + std::vector ids; ///< Event IDs. + std::vector authors; ///< Event author npubs. + std::vector kinds; ///< Kind numbers. + TagMap tags; ///< Tag names mapped to lists of tag values. + std::time_t since; ///< Unix timestamp. Matching events must be newer than this. + std::time_t until; ///< Unix timestamp. Matching events must be older than this. + int limit; ///< The maximum number of events the relay should return on the initial query. + + /** + * @brief Serializes the filters to a JSON object. + * @returns A stringified JSON object representing the filters. + * @throws `std::invalid_argument` if the filter object is invalid. + */ + std::string serialize(); + +private: + /** + * @brief Validates the filters. + * @throws `std::invalid_argument` if the filter object is invalid. + * @remark The `until` field defaults to the present if it is not already set. + */ + void validate(); +}; + class NostrService { public: diff --git a/src/filters.cpp b/src/filters.cpp new file mode 100644 index 0000000..78f3ce4 --- /dev/null +++ b/src/filters.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +#include "nostr.hpp" + +using nlohmann::json; +using std::invalid_argument; +using std::stringstream; +using std::string; +using std::time; + +namespace nostr +{ +string Filters::serialize() +{ + try + { + this->validate(); + } + catch (const invalid_argument& e) + { + throw e; + } + + json j = { + {"ids", this->ids}, + {"authors", this->authors}, + {"kinds", this->kinds}, + {"since", this->since}, + {"until", this->until}, + {"limit", this->limit} + }; + + for (auto& tag : this->tags) + { + stringstream jss; + jss << "#" << tag.first; + string js = jss.str(); + + j[js] = tag.second; + } + + return j.dump(); +}; + +void Filters::validate() +{ + bool hasLimit = this->limit > 0; + if (!hasLimit) + { + throw invalid_argument("Filters::validate: The limit must be greater than 0."); + } + + bool hasUntil = this->until > 0; + if (!hasUntil) + { + this->until = time(nullptr); + } + + bool hasIds = this->ids.size() > 0; + bool hasAuthors = this->authors.size() > 0; + bool hasKinds = this->kinds.size() > 0; + bool hasTags = this->tags.size() > 0; + + bool hasFilter = hasIds || hasAuthors || hasKinds || hasTags; + + if (!hasFilter) + { + throw invalid_argument("Filters::validate: At least one filter must be set."); + } +}; +} // namespace nostr -- cgit