From 7eefdca064dc77247d76ae0d03c5e80eab779415 Mon Sep 17 00:00:00 2001 From: Yuki Kishimoto Date: Sat, 6 Apr 2024 18:06:37 +0200 Subject: [PATCH] gossip: init `nostr-gossip` --- Cargo.lock | 7 ++ crates/nostr-gossip/Cargo.toml | 15 ++++ crates/nostr-gossip/README.md | 13 ++++ crates/nostr-gossip/src/constants.rs | 7 ++ crates/nostr-gossip/src/lib.rs | 106 +++++++++++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 crates/nostr-gossip/Cargo.toml create mode 100644 crates/nostr-gossip/README.md create mode 100644 crates/nostr-gossip/src/constants.rs create mode 100644 crates/nostr-gossip/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d9fe1c4d4..908b94ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1716,6 +1716,13 @@ dependencies = [ "uniffi", ] +[[package]] +name = "nostr-gossip" +version = "0.29.0" +dependencies = [ + "nostr-relay-pool", +] + [[package]] name = "nostr-indexeddb" version = "0.29.0" diff --git a/crates/nostr-gossip/Cargo.toml b/crates/nostr-gossip/Cargo.toml new file mode 100644 index 000000000..44da0eb39 --- /dev/null +++ b/crates/nostr-gossip/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nostr-gossip" +version = "0.29.0" +edition = "2021" +description = "Implementation of gossip protocol for nostr clients." +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +readme = "README.md" +rust-version.workspace = true +keywords = ["nostr", "gossip"] + +[dependencies] +nostr-relay-pool.workspace = true \ No newline at end of file diff --git a/crates/nostr-gossip/README.md b/crates/nostr-gossip/README.md new file mode 100644 index 000000000..21b0e0d87 --- /dev/null +++ b/crates/nostr-gossip/README.md @@ -0,0 +1,13 @@ +# Nostr Gossip + +## State + +**This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways. + +## Donations + +`rust-nostr` is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the `rust-nostr` libs/software/services, then please [donate](https://rust-nostr.org/donate). + +## License + +This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details \ No newline at end of file diff --git a/crates/nostr-gossip/src/constants.rs b/crates/nostr-gossip/src/constants.rs new file mode 100644 index 000000000..603539642 --- /dev/null +++ b/crates/nostr-gossip/src/constants.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Constants + +pub const DEFAULT_DISCOVERY_RELAYS: [&str; 1] = ["wss://purplepag.es"]; diff --git a/crates/nostr-gossip/src/lib.rs b/crates/nostr-gossip/src/lib.rs new file mode 100644 index 000000000..1ddbe6a07 --- /dev/null +++ b/crates/nostr-gossip/src/lib.rs @@ -0,0 +1,106 @@ +// Copyright (c) 2022-2023 Yuki Kishimoto +// Copyright (c) 2023-2024 Rust Nostr Developers +// Distributed under the MIT software license + +//! Gossip + +use std::ops::Deref; +use std::time::Duration; + +use nostr_relay_pool::pool::Error; +use nostr_relay_pool::prelude::*; +use nostr_relay_pool::{FilterOptions, RelayOptions, RelayPool, RelayServiceFlags}; + +pub mod constants; + +/// Gossip client +#[derive(Debug, Clone)] +pub struct NostrGossip { + pool: RelayPool, +} + +impl Deref for NostrGossip { + type Target = RelayPool; + + fn deref(&self) -> &Self::Target { + &self.pool + } +} + +impl NostrGossip { + pub async fn add_discovery_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::DISCOVERY); + + // Compose options + let opts: RelayOptions = RelayOptions::default().flags(flags); + + // Add relay + self.pool.add_relay(url, opts).await + } + + pub async fn add_inbox_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::READ); + flags.add(RelayServiceFlags::INBOX); + + // Compose options + let opts: RelayOptions = RelayOptions::default().flags(flags); + + // Add relay + self.pool.add_relay(url, opts).await + } + + pub async fn add_outbox_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::WRITE); + flags.add(RelayServiceFlags::OUTBOX); + + // Compose options + let opts: RelayOptions = RelayOptions::default().flags(flags); + + // Add relay + self.pool.add_relay(url, opts).await + } + + pub async fn get_relays_for_public_key( + &self, + public_key: PublicKey, + timeout: Duration, + ) -> Result)>, Error> { + // Get discovery relays + let relays = self + .pool + .relays_with_flag(RelayServiceFlags::DISCOVERY) + .await + .into_keys(); + + // Get events + let filter: Filter = Filter::default() + .author(public_key) + .kind(Kind::RelayList) + .limit(1); + let events: Vec = self + .get_events_from(relays, vec![filter], timeout, FilterOptions::ExitOnEOSE) + .await?; + + // Extract relay list (NIP65) + let event: &Event = events.first().unwrap(); // TODO: remove unwrap + Ok(nip65::extract_relay_list(&event)) + } +}