mirror of
https://github.com/scsibug/nostr-rs-relay.git
synced 2025-09-01 11:40:48 -04:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d78bbfc290 | ||
|
2924da88bc | ||
|
3024e9fba4 | ||
|
d3da4eb009 | ||
|
19637d612e | ||
|
afc9a0096a | ||
|
3d56262386 | ||
|
6673fcfd11 | ||
|
b5da3fa2b0 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -649,7 +649,7 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-rs-relay"
|
name = "nostr-rs-relay"
|
||||||
version = "0.2.3"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin_hashes 0.9.7",
|
"bitcoin_hashes 0.9.7",
|
||||||
"config",
|
"config",
|
||||||
@@ -1097,6 +1097,7 @@ version = "1.0.72"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
|
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde 1.0.131",
|
"serde 1.0.131",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nostr-rs-relay"
|
name = "nostr-rs-relay"
|
||||||
version = "0.2.3"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -17,7 +17,7 @@ config = { version = "0.11", features = ["toml"] }
|
|||||||
bitcoin_hashes = { version = "^0.9", features = ["serde"] }
|
bitcoin_hashes = { version = "^0.9", features = ["serde"] }
|
||||||
secp256k1 = {git = "https://github.com/rust-bitcoin/rust-secp256k1.git", rev = "50034ccb18fdd84904ab3aa6c84a12fcced33209", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] }
|
secp256k1 = {git = "https://github.com/rust-bitcoin/rust-secp256k1.git", rev = "50034ccb18fdd84904ab3aa6c84a12fcced33209", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = "^1.0"
|
serde_json = {version = "^1.0", features = ["preserve_order"]}
|
||||||
hex = "^0.4"
|
hex = "^0.4"
|
||||||
rusqlite = "^0.26"
|
rusqlite = "^0.26"
|
||||||
lazy_static = "^1.4"
|
lazy_static = "^1.4"
|
||||||
|
@@ -39,6 +39,9 @@ Text Note [81cf...2652] from 296a...9b92 5 seconds ago
|
|||||||
hello world
|
hello world
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A pre-built container is also available on DockerHub:
|
||||||
|
https://hub.docker.com/repository/docker/scsibug/nostr-rs-relay
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The sample `[config.toml](config.toml)` file demonstrates the
|
The sample `[config.toml](config.toml)` file demonstrates the
|
||||||
|
17
config.toml
17
config.toml
@@ -1,4 +1,21 @@
|
|||||||
# Nostr-rs-relay configuration
|
# Nostr-rs-relay configuration
|
||||||
|
|
||||||
|
[info]
|
||||||
|
# The advertised URL for the Nostr websocket.
|
||||||
|
relay_url = "wss://nostr.example.com/"
|
||||||
|
|
||||||
|
# Relay information for clients. Put your unique server name here.
|
||||||
|
name = "nostr-rs-relay"
|
||||||
|
|
||||||
|
# Description
|
||||||
|
description = "A newly created nostr-rs-relay.\n\nCustomize this with your own info."
|
||||||
|
|
||||||
|
# Administrative contact pubkey
|
||||||
|
#pubkey = "0c2d168a4ae8ca58c9f1ab237b5df682599c6c7ab74307ea8b05684b60405d41"
|
||||||
|
|
||||||
|
# Administrative contact email
|
||||||
|
#email = "contact@example.com"
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
# Directory for SQLite files. Defaults to the current directory. Can
|
# Directory for SQLite files. Defaults to the current directory. Can
|
||||||
# also be specified (and overriden) with the "--db dirname" command
|
# also be specified (and overriden) with the "--db dirname" command
|
||||||
|
@@ -8,6 +8,16 @@ lazy_static! {
|
|||||||
pub static ref SETTINGS: RwLock<Settings> = RwLock::new(Settings::default());
|
pub static ref SETTINGS: RwLock<Settings> = RwLock::new(Settings::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub struct Info {
|
||||||
|
pub relay_url: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub pubkey: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
@@ -52,6 +62,7 @@ pub struct Limits {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
pub info: Info,
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
pub limits: Limits,
|
pub limits: Limits,
|
||||||
@@ -89,6 +100,13 @@ impl Settings {
|
|||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Settings {
|
Settings {
|
||||||
|
info: Info {
|
||||||
|
relay_url: None,
|
||||||
|
name: Some("Unnamed nostr-rs-relay".to_owned()),
|
||||||
|
description: None,
|
||||||
|
pubkey: None,
|
||||||
|
email: None,
|
||||||
|
},
|
||||||
database: Database {
|
database: Database {
|
||||||
data_directory: ".".to_owned(),
|
data_directory: ".".to_owned(),
|
||||||
},
|
},
|
||||||
|
76
src/db.rs
76
src/db.rs
@@ -13,6 +13,7 @@ use rusqlite::OpenFlags;
|
|||||||
use crate::config::SETTINGS;
|
use crate::config::SETTINGS;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::time::Instant;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
|
|
||||||
/// Database file
|
/// Database file
|
||||||
@@ -157,12 +158,17 @@ pub async fn db_writer(
|
|||||||
}
|
}
|
||||||
let mut event_write = false;
|
let mut event_write = false;
|
||||||
let event = next_event.unwrap();
|
let event = next_event.unwrap();
|
||||||
|
let start = Instant::now();
|
||||||
match write_event(&mut conn, &event) {
|
match write_event(&mut conn, &event) {
|
||||||
Ok(updated) => {
|
Ok(updated) => {
|
||||||
if updated == 0 {
|
if updated == 0 {
|
||||||
debug!("ignoring duplicate event");
|
debug!("ignoring duplicate event");
|
||||||
} else {
|
} else {
|
||||||
info!("persisted event: {}", event.get_event_id_prefix());
|
info!(
|
||||||
|
"persisted event: {} in {:?}",
|
||||||
|
event.get_event_id_prefix(),
|
||||||
|
start.elapsed()
|
||||||
|
);
|
||||||
event_write = true;
|
event_write = true;
|
||||||
// send this out to all clients
|
// send this out to all clients
|
||||||
bcast_tx.send(event.clone()).ok();
|
bcast_tx.send(event.clone()).ok();
|
||||||
@@ -302,35 +308,52 @@ fn query_from_sub(sub: &Subscription) -> String {
|
|||||||
filter_components.push(authors_clause);
|
filter_components.push(authors_clause);
|
||||||
}
|
}
|
||||||
// Query for Kind
|
// Query for Kind
|
||||||
if f.kind.is_some() {
|
if let Some(ks) = &f.kinds {
|
||||||
// kind is number, no escaping needed
|
// kind is number, no escaping needed
|
||||||
let kind_clause = format!("kind = {}", f.kind.unwrap());
|
let str_kinds: Vec<String> = ks.iter().map(|x| x.to_string()).collect();
|
||||||
|
let kind_clause = format!("kind IN ({})", str_kinds.join(", "));
|
||||||
filter_components.push(kind_clause);
|
filter_components.push(kind_clause);
|
||||||
}
|
}
|
||||||
// Query for event
|
// Query for event
|
||||||
if f.id.is_some() {
|
if f.ids.is_some() {
|
||||||
let id_str = f.id.as_ref().unwrap();
|
let ids_escaped: Vec<String> = f
|
||||||
if is_hex(id_str) {
|
.ids
|
||||||
let id_clause = format!("event_hash = x'{}'", id_str);
|
.as_ref()
|
||||||
filter_components.push(id_clause);
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
|
.filter(|&x| is_hex(x))
|
||||||
|
.map(|x| format!("x'{}'", x))
|
||||||
|
.collect();
|
||||||
|
let id_clause = format!("event_hash IN ({})", ids_escaped.join(", "));
|
||||||
|
filter_components.push(id_clause);
|
||||||
}
|
}
|
||||||
// Query for referenced event
|
// Query for referenced event
|
||||||
if f.event.is_some() {
|
if f.events.is_some() {
|
||||||
let ev_str = f.event.as_ref().unwrap();
|
let events_escaped: Vec<String> = f
|
||||||
if is_hex(ev_str) {
|
.events
|
||||||
let ev_clause = format!("referenced_event = x'{}'", ev_str);
|
.as_ref()
|
||||||
filter_components.push(ev_clause);
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
|
.filter(|&x| is_hex(x))
|
||||||
|
.map(|x| format!("x'{}'", x))
|
||||||
|
.collect();
|
||||||
|
let events_clause = format!("referenced_event IN ({})", events_escaped.join(", "));
|
||||||
|
filter_components.push(events_clause);
|
||||||
}
|
}
|
||||||
// Query for referenced pet name pubkey
|
// Query for referenced pubkey
|
||||||
if f.pubkey.is_some() {
|
if f.pubkeys.is_some() {
|
||||||
let pet_str = f.pubkey.as_ref().unwrap();
|
let pubkeys_escaped: Vec<String> = f
|
||||||
if is_hex(pet_str) {
|
.pubkeys
|
||||||
let pet_clause = format!("referenced_pubkey = x'{}'", pet_str);
|
.as_ref()
|
||||||
filter_components.push(pet_clause);
|
.unwrap()
|
||||||
}
|
.iter()
|
||||||
|
.filter(|&x| is_hex(x))
|
||||||
|
.map(|x| format!("x'{}'", x))
|
||||||
|
.collect();
|
||||||
|
let pubkeys_clause = format!("referenced_pubkey IN ({})", pubkeys_escaped.join(", "));
|
||||||
|
filter_components.push(pubkeys_clause);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for timestamp
|
// Query for timestamp
|
||||||
if f.since.is_some() {
|
if f.since.is_some() {
|
||||||
let created_clause = format!("created_at > {}", f.since.unwrap());
|
let created_clause = format!("created_at > {}", f.since.unwrap());
|
||||||
@@ -385,6 +408,8 @@ pub async fn db_query(
|
|||||||
Connection::open_with_flags(&full_path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
|
Connection::open_with_flags(&full_path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap();
|
||||||
debug!("opened database for reading");
|
debug!("opened database for reading");
|
||||||
debug!("going to query for: {:?}", sub);
|
debug!("going to query for: {:?}", sub);
|
||||||
|
let mut row_count: usize = 0;
|
||||||
|
let start = Instant::now();
|
||||||
// generate SQL query
|
// generate SQL query
|
||||||
let q = query_from_sub(&sub);
|
let q = query_from_sub(&sub);
|
||||||
// execute the query
|
// execute the query
|
||||||
@@ -396,6 +421,7 @@ pub async fn db_query(
|
|||||||
debug!("query aborted");
|
debug!("query aborted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
row_count += 1;
|
||||||
// TODO: check before unwrapping
|
// TODO: check before unwrapping
|
||||||
let event_json = row.get(0).unwrap();
|
let event_json = row.get(0).unwrap();
|
||||||
query_tx
|
query_tx
|
||||||
@@ -405,6 +431,10 @@ pub async fn db_query(
|
|||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
debug!("query completed");
|
debug!(
|
||||||
|
"query completed ({} rows) in {:?}",
|
||||||
|
row_count,
|
||||||
|
start.elapsed()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
60
src/info.rs
Normal file
60
src/info.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::config;
|
||||||
|
/// Relay Info
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub struct RelayInfo {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub pubkey: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub email: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub supported_nips: Option<Vec<i64>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub software: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RelayInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
RelayInfo {
|
||||||
|
id: None,
|
||||||
|
name: None,
|
||||||
|
description: None,
|
||||||
|
pubkey: None,
|
||||||
|
email: None,
|
||||||
|
supported_nips: Some(vec![1]),
|
||||||
|
software: Some("https://git.sr.ht/~gheartsfield/nostr-rs-relay".to_owned()),
|
||||||
|
version: CARGO_PKG_VERSION.map(|x| x.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an Info struct into Relay Info json string
|
||||||
|
pub fn relay_info_json(info: &config::Info) -> String {
|
||||||
|
// get a default RelayInfo
|
||||||
|
let mut r = RelayInfo::default();
|
||||||
|
// update fields from Info, if present
|
||||||
|
r.id = info.relay_url.clone();
|
||||||
|
r.name = info.name.clone();
|
||||||
|
r.description = info.description.clone();
|
||||||
|
r.pubkey = info.pubkey.clone();
|
||||||
|
r.email = info.email.clone();
|
||||||
|
r.to_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelayInfo {
|
||||||
|
pub fn to_json(self) -> String {
|
||||||
|
serde_json::to_string_pretty(&self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
@@ -4,5 +4,6 @@ pub mod conn;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod info;
|
||||||
pub mod protostream;
|
pub mod protostream;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
25
src/main.rs
25
src/main.rs
@@ -1,6 +1,7 @@
|
|||||||
//! Server process
|
//! Server process
|
||||||
use futures::SinkExt;
|
use futures::SinkExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use hyper::header::ACCEPT;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::upgrade::Upgraded;
|
use hyper::upgrade::Upgraded;
|
||||||
use hyper::{
|
use hyper::{
|
||||||
@@ -13,6 +14,7 @@ use nostr_rs_relay::conn;
|
|||||||
use nostr_rs_relay::db;
|
use nostr_rs_relay::db;
|
||||||
use nostr_rs_relay::error::{Error, Result};
|
use nostr_rs_relay::error::{Error, Result};
|
||||||
use nostr_rs_relay::event::Event;
|
use nostr_rs_relay::event::Event;
|
||||||
|
use nostr_rs_relay::info::relay_info_json;
|
||||||
use nostr_rs_relay::protostream;
|
use nostr_rs_relay::protostream;
|
||||||
use nostr_rs_relay::protostream::NostrMessage::*;
|
use nostr_rs_relay::protostream::NostrMessage::*;
|
||||||
use nostr_rs_relay::protostream::NostrResponse::*;
|
use nostr_rs_relay::protostream::NostrResponse::*;
|
||||||
@@ -47,7 +49,7 @@ async fn handle_web_request(
|
|||||||
request.uri().path(),
|
request.uri().path(),
|
||||||
request.headers().contains_key(header::UPGRADE),
|
request.headers().contains_key(header::UPGRADE),
|
||||||
) {
|
) {
|
||||||
//if the request is ws_echo and the request headers contains an Upgrade key
|
// Request for / as websocket
|
||||||
("/", true) => {
|
("/", true) => {
|
||||||
debug!("websocket with upgrade request");
|
debug!("websocket with upgrade request");
|
||||||
//assume request is a handshake, so create the handshake response
|
//assume request is a handshake, so create the handshake response
|
||||||
@@ -96,10 +98,29 @@ async fn handle_web_request(
|
|||||||
};
|
};
|
||||||
Ok::<_, Infallible>(response)
|
Ok::<_, Infallible>(response)
|
||||||
}
|
}
|
||||||
|
// Request for Relay info
|
||||||
("/", false) => {
|
("/", false) => {
|
||||||
// handle request at root with no upgrade header
|
// handle request at root with no upgrade header
|
||||||
|
// Check if this is a nostr server info request
|
||||||
|
let accept_header = &request.headers().get(ACCEPT);
|
||||||
|
// check if application/nostr+json is included
|
||||||
|
if let Some(media_types) = accept_header {
|
||||||
|
if let Ok(mt_str) = media_types.to_str() {
|
||||||
|
if mt_str.contains("application/nostr+json") {
|
||||||
|
let config = config::SETTINGS.read().unwrap();
|
||||||
|
// build a relay info response
|
||||||
|
debug!("Responding to server info request");
|
||||||
|
let b = Body::from(relay_info_json(&config.info));
|
||||||
|
return Ok(Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("Content-Type", "application/nostr+json")
|
||||||
|
.body(b)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Response::new(Body::from(
|
Ok(Response::new(Body::from(
|
||||||
"This is a Nostr relay.\n".to_string(),
|
"Please use a Nostr client to connect.",
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
|
@@ -71,6 +71,7 @@ impl Stream for NostrStream {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("proto parse error: {:?}", e);
|
debug!("proto parse error: {:?}", e);
|
||||||
|
debug!("parse error on message: {}", msg.trim());
|
||||||
Err(Error::ProtoParseError)
|
Err(Error::ProtoParseError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// Subscription identifier and set of request filters
|
/// Subscription identifier and set of request filters
|
||||||
#[derive(Serialize, PartialEq, Debug, Clone)]
|
#[derive(Serialize, PartialEq, Debug, Clone)]
|
||||||
@@ -17,16 +18,16 @@ pub struct Subscription {
|
|||||||
/// absent ([`None`]) if it should be ignored.
|
/// absent ([`None`]) if it should be ignored.
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||||
pub struct ReqFilter {
|
pub struct ReqFilter {
|
||||||
/// Event hash
|
/// Event hashes
|
||||||
pub id: Option<String>,
|
pub ids: Option<Vec<String>>,
|
||||||
/// Event kind
|
/// Event kinds
|
||||||
pub kind: Option<u64>,
|
pub kinds: Option<Vec<u64>>,
|
||||||
/// Referenced event hash
|
/// Referenced event hash
|
||||||
#[serde(rename = "#e")]
|
#[serde(rename = "#e")]
|
||||||
pub event: Option<String>,
|
pub events: Option<Vec<String>>,
|
||||||
/// Referenced public key for a petname
|
/// Referenced public key for a petname
|
||||||
#[serde(rename = "#p")]
|
#[serde(rename = "#p")]
|
||||||
pub pubkey: Option<String>,
|
pub pubkeys: Option<Vec<String>>,
|
||||||
/// Events published after this time
|
/// Events published after this time
|
||||||
pub since: Option<u64>,
|
pub since: Option<u64>,
|
||||||
/// Events published before this time
|
/// Events published before this time
|
||||||
@@ -105,8 +106,13 @@ impl Subscription {
|
|||||||
|
|
||||||
impl ReqFilter {
|
impl ReqFilter {
|
||||||
/// Check for a match within the authors list.
|
/// Check for a match within the authors list.
|
||||||
// TODO: Ambiguity; what if the array is empty? Should we
|
fn ids_match(&self, event: &Event) -> bool {
|
||||||
// consider that the same as null?
|
self.ids
|
||||||
|
.as_ref()
|
||||||
|
.map(|vs| vs.contains(&event.id.to_owned()))
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn authors_match(&self, event: &Event) -> bool {
|
fn authors_match(&self, event: &Event) -> bool {
|
||||||
self.authors
|
self.authors
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -115,29 +121,47 @@ impl ReqFilter {
|
|||||||
}
|
}
|
||||||
/// Check if this filter either matches, or does not care about the event tags.
|
/// Check if this filter either matches, or does not care about the event tags.
|
||||||
fn event_match(&self, event: &Event) -> bool {
|
fn event_match(&self, event: &Event) -> bool {
|
||||||
self.event
|
// This needs to be analyzed for performance; building these
|
||||||
.as_ref()
|
// hash sets for each active subscription isn't great.
|
||||||
.map(|t| event.event_tag_match(t))
|
if let Some(es) = &self.events {
|
||||||
.unwrap_or(true)
|
let event_refs =
|
||||||
|
HashSet::<_>::from_iter(event.get_event_tags().iter().map(|x| x.to_owned()));
|
||||||
|
let filter_refs = HashSet::<_>::from_iter(es.iter().map(|x| &x[..]));
|
||||||
|
let cardinality = event_refs.intersection(&filter_refs).count();
|
||||||
|
cardinality > 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this filter either matches, or does not care about
|
/// Check if this filter either matches, or does not care about
|
||||||
/// the pubkey/petname tags.
|
/// the pubkey/petname tags.
|
||||||
fn pubkey_match(&self, event: &Event) -> bool {
|
fn pubkey_match(&self, event: &Event) -> bool {
|
||||||
self.pubkey
|
// This needs to be analyzed for performance; building these
|
||||||
.as_ref()
|
// hash sets for each active subscription isn't great.
|
||||||
.map(|t| event.pubkey_tag_match(t))
|
if let Some(ps) = &self.pubkeys {
|
||||||
.unwrap_or(true)
|
let pubkey_refs =
|
||||||
|
HashSet::<_>::from_iter(event.get_pubkey_tags().iter().map(|x| x.to_owned()));
|
||||||
|
let filter_refs = HashSet::<_>::from_iter(ps.iter().map(|x| &x[..]));
|
||||||
|
let cardinality = pubkey_refs.intersection(&filter_refs).count();
|
||||||
|
cardinality > 0
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this filter either matches, or does not care about the kind.
|
/// Check if this filter either matches, or does not care about the kind.
|
||||||
fn kind_match(&self, kind: u64) -> bool {
|
fn kind_match(&self, kind: u64) -> bool {
|
||||||
self.kind.map(|v| v == kind).unwrap_or(true)
|
self.kinds
|
||||||
|
.as_ref()
|
||||||
|
.map(|ks| ks.contains(&kind))
|
||||||
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if all populated fields in this filter match the provided event.
|
/// Determine if all populated fields in this filter match the provided event.
|
||||||
pub fn interested_in_event(&self, event: &Event) -> bool {
|
pub fn interested_in_event(&self, event: &Event) -> bool {
|
||||||
self.id.as_ref().map(|v| v == &event.id).unwrap_or(true)
|
// self.id.as_ref().map(|v| v == &event.id).unwrap_or(true)
|
||||||
|
self.ids_match(event)
|
||||||
&& self.since.map(|t| event.created_at > t).unwrap_or(true)
|
&& self.since.map(|t| event.created_at > t).unwrap_or(true)
|
||||||
&& self.kind_match(event.kind)
|
&& self.kind_match(event.kind)
|
||||||
&& self.authors_match(event)
|
&& self.authors_match(event)
|
||||||
|
Reference in New Issue
Block a user