notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 10ed593b6dbfcf65136c56a49e12e79cf087f4f3
parent e91684a7d540f1f8aaf599e606f2848a8ea1615d
Author: kernelkind <kernelkind@gmail.com>
Date:   Wed, 25 Jun 2025 16:19:54 -0400

accounts: move relay stuff to own file

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/account/accounts.rs | 146+++----------------------------------------------------------------------------
Mcrates/notedeck/src/account/mod.rs | 1+
Acrates/notedeck/src/account/relay.rs | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 156 insertions(+), 142 deletions(-)

diff --git a/crates/notedeck/src/account/accounts.rs b/crates/notedeck/src/account/accounts.rs @@ -1,13 +1,13 @@ use tracing::{debug, error, info}; +use crate::account::relay::AccountRelayData; use crate::{ AccountStorage, MuteFun, Muted, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount, }; use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; -use nostrdb::{Filter, Ndb, Note, NoteBuilder, NoteKey, Subscription, Transaction}; +use nostrdb::{Filter, Ndb, Note, NoteKey, Subscription, Transaction}; use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; -use url::Url; use uuid::Uuid; // TODO: remove this @@ -34,14 +34,6 @@ pub enum AccountsAction { Remove(usize), } -pub struct AccountRelayData { - filter: Filter, - subid: Option<String>, - sub: Option<Subscription>, - local: BTreeSet<RelaySpec>, // used locally but not advertised - advertised: BTreeSet<RelaySpec>, // advertised via NIP-65 -} - #[derive(Default)] pub struct ContainsAccount { pub has_nsec: bool, @@ -54,136 +46,6 @@ pub struct AddAccountAction { pub unk_id_action: SingleUnkIdAction, } -impl AccountRelayData { - pub fn new(ndb: &Ndb, pubkey: &[u8; 32]) -> Self { - // Construct a filter for the user's NIP-65 relay list - let filter = Filter::new() - .authors([pubkey]) - .kinds([10002]) - .limit(1) - .build(); - - // Query the ndb immediately to see if the user list is already there - let txn = Transaction::new(ndb).expect("transaction"); - let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32; - let nks = ndb - .query(&txn, &[filter.clone()], lim) - .expect("query user relays results") - .iter() - .map(|qr| qr.note_key) - .collect::<Vec<NoteKey>>(); - let relays = Self::harvest_nip65_relays(ndb, &txn, &nks); - debug!( - "pubkey {}: initial relays {:?}", - hex::encode(pubkey), - relays - ); - - AccountRelayData { - filter, - subid: None, - sub: None, - local: BTreeSet::new(), - advertised: relays.into_iter().collect(), - } - } - - // make this account the current selected account - pub fn activate(&mut self, ndb: &Ndb, pool: &mut RelayPool) { - debug!("activating relay sub {}", self.filter.json().unwrap()); - assert_eq!(self.subid, None, "subid already exists"); - assert_eq!(self.sub, None, "sub already exists"); - - // local subscription - let sub = ndb - .subscribe(&[self.filter.clone()]) - .expect("ndb relay list subscription"); - - // remote subscription - let subid = Uuid::new_v4().to_string(); - pool.subscribe(subid.clone(), vec![self.filter.clone()]); - - self.sub = Some(sub); - self.subid = Some(subid); - } - - // this account is no longer the selected account - pub fn deactivate(&mut self, ndb: &mut Ndb, pool: &mut RelayPool) { - debug!("deactivating relay sub {}", self.filter.json().unwrap()); - assert_ne!(self.subid, None, "subid doesn't exist"); - assert_ne!(self.sub, None, "sub doesn't exist"); - - // remote subscription - pool.unsubscribe(self.subid.as_ref().unwrap().clone()); - - // local subscription - ndb.unsubscribe(self.sub.unwrap()) - .expect("ndb relay list unsubscribe"); - - self.sub = None; - self.subid = None; - } - - // standardize the format (ie, trailing slashes) to avoid dups - pub fn canonicalize_url(url: &str) -> String { - match Url::parse(url) { - Ok(parsed_url) => parsed_url.to_string(), - Err(_) => url.to_owned(), // If parsing fails, return the original URL. - } - } - - fn harvest_nip65_relays(ndb: &Ndb, txn: &Transaction, nks: &[NoteKey]) -> Vec<RelaySpec> { - let mut relays = Vec::new(); - for nk in nks.iter() { - if let Ok(note) = ndb.get_note_by_key(txn, *nk) { - for tag in note.tags() { - match tag.get(0).and_then(|t| t.variant().str()) { - Some("r") => { - if let Some(url) = tag.get(1).and_then(|f| f.variant().str()) { - let has_read_marker = tag - .get(2) - .is_some_and(|m| m.variant().str() == Some("read")); - let has_write_marker = tag - .get(2) - .is_some_and(|m| m.variant().str() == Some("write")); - relays.push(RelaySpec::new( - Self::canonicalize_url(url), - has_read_marker, - has_write_marker, - )); - } - } - Some("alt") => { - // ignore for now - } - Some(x) => { - error!("harvest_nip65_relays: unexpected tag type: {}", x); - } - None => { - error!("harvest_nip65_relays: invalid tag"); - } - } - } - } - } - relays - } - - pub fn publish_nip65_relays(&self, seckey: &[u8; 32], pool: &mut RelayPool) { - let mut builder = NoteBuilder::new().kind(10002).content(""); - for rs in &self.advertised { - builder = builder.start_tag().tag_str("r").tag_str(&rs.url); - if rs.has_read_marker { - builder = builder.tag_str("read"); - } else if rs.has_write_marker { - builder = builder.tag_str("write"); - } - } - let note = builder.sign(seckey).build().expect("note build"); - pool.send(&enostr::ClientMessage::event(&note).expect("note client message")); - } -} - pub struct AccountMutedData { filter: Filter, subid: Option<String>, @@ -299,8 +161,8 @@ impl AccountMutedData { } pub struct AccountData { - relay: AccountRelayData, - muted: AccountMutedData, + pub(crate) relay: AccountRelayData, + pub(crate) muted: AccountMutedData, } /// The interface for managing the user's accounts. diff --git a/crates/notedeck/src/account/mod.rs b/crates/notedeck/src/account/mod.rs @@ -1 +1,2 @@ pub mod accounts; +pub mod relay; diff --git a/crates/notedeck/src/account/relay.rs b/crates/notedeck/src/account/relay.rs @@ -0,0 +1,151 @@ +use std::collections::BTreeSet; + +use enostr::RelayPool; +use nostrdb::{Filter, Ndb, NoteBuilder, NoteKey, Subscription, Transaction}; +use tracing::{debug, error}; +use url::Url; +use uuid::Uuid; + +use crate::RelaySpec; + +pub(crate) struct AccountRelayData { + pub filter: Filter, + pub subid: Option<String>, + pub sub: Option<Subscription>, + pub local: BTreeSet<RelaySpec>, // used locally but not advertised + pub advertised: BTreeSet<RelaySpec>, // advertised via NIP-65 +} + +impl AccountRelayData { + pub fn new(ndb: &Ndb, pubkey: &[u8; 32]) -> Self { + // Construct a filter for the user's NIP-65 relay list + let filter = Filter::new() + .authors([pubkey]) + .kinds([10002]) + .limit(1) + .build(); + + // Query the ndb immediately to see if the user list is already there + let txn = Transaction::new(ndb).expect("transaction"); + let lim = filter.limit().unwrap_or(crate::filter::default_limit()) as i32; + let nks = ndb + .query(&txn, &[filter.clone()], lim) + .expect("query user relays results") + .iter() + .map(|qr| qr.note_key) + .collect::<Vec<NoteKey>>(); + let relays = Self::harvest_nip65_relays(ndb, &txn, &nks); + debug!( + "pubkey {}: initial relays {:?}", + hex::encode(pubkey), + relays + ); + + AccountRelayData { + filter, + subid: None, + sub: None, + local: BTreeSet::new(), + advertised: relays.into_iter().collect(), + } + } + + // make this account the current selected account + pub fn activate(&mut self, ndb: &Ndb, pool: &mut RelayPool) { + debug!("activating relay sub {}", self.filter.json().unwrap()); + assert_eq!(self.subid, None, "subid already exists"); + assert_eq!(self.sub, None, "sub already exists"); + + // local subscription + let sub = ndb + .subscribe(&[self.filter.clone()]) + .expect("ndb relay list subscription"); + + // remote subscription + let subid = Uuid::new_v4().to_string(); + pool.subscribe(subid.clone(), vec![self.filter.clone()]); + + self.sub = Some(sub); + self.subid = Some(subid); + } + + // this account is no longer the selected account + pub fn deactivate(&mut self, ndb: &mut Ndb, pool: &mut RelayPool) { + debug!("deactivating relay sub {}", self.filter.json().unwrap()); + assert_ne!(self.subid, None, "subid doesn't exist"); + assert_ne!(self.sub, None, "sub doesn't exist"); + + // remote subscription + pool.unsubscribe(self.subid.as_ref().unwrap().clone()); + + // local subscription + ndb.unsubscribe(self.sub.unwrap()) + .expect("ndb relay list unsubscribe"); + + self.sub = None; + self.subid = None; + } + + // standardize the format (ie, trailing slashes) to avoid dups + pub fn canonicalize_url(url: &str) -> String { + match Url::parse(url) { + Ok(parsed_url) => parsed_url.to_string(), + Err(_) => url.to_owned(), // If parsing fails, return the original URL. + } + } + + pub(crate) fn harvest_nip65_relays( + ndb: &Ndb, + txn: &Transaction, + nks: &[NoteKey], + ) -> Vec<RelaySpec> { + let mut relays = Vec::new(); + for nk in nks.iter() { + if let Ok(note) = ndb.get_note_by_key(txn, *nk) { + for tag in note.tags() { + match tag.get(0).and_then(|t| t.variant().str()) { + Some("r") => { + if let Some(url) = tag.get(1).and_then(|f| f.variant().str()) { + let has_read_marker = tag + .get(2) + .is_some_and(|m| m.variant().str() == Some("read")); + let has_write_marker = tag + .get(2) + .is_some_and(|m| m.variant().str() == Some("write")); + relays.push(RelaySpec::new( + Self::canonicalize_url(url), + has_read_marker, + has_write_marker, + )); + } + } + Some("alt") => { + // ignore for now + } + Some(x) => { + error!("harvest_nip65_relays: unexpected tag type: {}", x); + } + None => { + error!("harvest_nip65_relays: invalid tag"); + } + } + } + } + } + relays + } + + pub fn publish_nip65_relays(&self, seckey: &[u8; 32], pool: &mut RelayPool) { + let mut builder = NoteBuilder::new().kind(10002).content(""); + for rs in &self.advertised { + builder = builder.start_tag().tag_str("r").tag_str(&rs.url); + if rs.has_read_marker { + builder = builder.tag_str("read"); + } else if rs.has_write_marker { + builder = builder.tag_str("write"); + } + } + let note = builder.sign(seckey).build().expect("note build"); + pool.send(&enostr::ClientMessage::event(&note).expect("note client message")); + } +}