add Contacts

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-07-05 13:21:07 -04:00
parent 4014d122c9
commit 9b7033e208
3 changed files with 147 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
use std::collections::HashSet;
use enostr::Pubkey;
use nostrdb::{Filter, Ndb, Note, NoteKey, Subscription, Transaction};
pub struct Contacts {
pub filter: Filter,
pub(super) state: ContactState,
}
pub enum ContactState {
Unreceived,
Received {
contacts: HashSet<Pubkey>,
note_key: NoteKey,
},
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub enum IsFollowing {
/// We don't have the contact list, so we don't know
Unknown,
/// We are follow
Yes,
No,
}
impl Contacts {
pub fn new(pubkey: &[u8; 32]) -> Self {
let filter = Filter::new().authors([pubkey]).kinds([3]).limit(1).build();
Self {
filter,
state: ContactState::Unreceived,
}
}
pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
let binding = ndb
.query(txn, &[self.filter.clone()], 1)
.expect("query user relays results");
let Some(res) = binding.first() else {
return;
};
update_state(&mut self.state, &res.note, res.note_key);
}
pub fn is_following(&self, other: &Pubkey) -> IsFollowing {
match &self.state {
ContactState::Unreceived => IsFollowing::Unknown,
ContactState::Received {
contacts,
note_key: _,
} => {
if contacts.contains(other) {
IsFollowing::Yes
} else {
IsFollowing::No
}
}
}
}
pub(super) fn poll_for_updates(&mut self, ndb: &Ndb, txn: &Transaction, sub: Subscription) {
let nks = ndb.poll_for_notes(sub, 1);
let Some(key) = nks.first() else {
return;
};
let note = match ndb.get_note_by_key(txn, *key) {
Ok(note) => note,
Err(e) => {
tracing::error!("Could not find note at key {:?}: {e}", key);
return;
}
};
update_state(&mut self.state, &note, *key);
}
pub fn get_state(&self) -> &ContactState {
&self.state
}
}
fn update_state(state: &mut ContactState, note: &Note, key: NoteKey) {
match state {
ContactState::Unreceived => {
*state = ContactState::Received {
contacts: get_contacts_owned(note),
note_key: key,
};
}
ContactState::Received { contacts, note_key } => {
update_contacts(contacts, note);
*note_key = key;
}
};
}
fn get_contacts<'a>(note: &Note<'a>) -> HashSet<&'a [u8; 32]> {
let mut contacts = HashSet::with_capacity(note.tags().count().into());
for tag in note.tags() {
if tag.count() < 2 {
continue;
}
let Some("p") = tag.get_str(0) else {
continue;
};
let Some(cur_id) = tag.get_id(1) else {
continue;
};
contacts.insert(cur_id);
}
contacts
}
fn get_contacts_owned(note: &Note<'_>) -> HashSet<Pubkey> {
get_contacts(note)
.iter()
.map(|p| Pubkey::new(**p))
.collect()
}
fn update_contacts(cur: &mut HashSet<Pubkey>, new: &Note<'_>) {
let new_contacts = get_contacts(new);
cur.retain(|pk| new_contacts.contains(pk.bytes()));
new_contacts.iter().for_each(|c| {
if !cur.contains(*c) {
cur.insert(Pubkey::new(**c));
}
});
}

View File

@@ -1,5 +1,6 @@
pub mod accounts;
pub mod cache;
pub mod contacts;
pub mod mute;
pub mod relay;

View File

@@ -34,6 +34,7 @@ mod wallet;
mod zaps;
pub use account::accounts::{AccountData, Accounts};
pub use account::contacts::{ContactState, IsFollowing};
pub use account::relay::RelayAction;
pub use account::FALLBACK_PUBKEY;
pub use app::{App, AppAction, Notedeck};