Add user mute list sync via polling
This commit is contained in:
@@ -6,7 +6,7 @@ use std::fmt;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Copy, Hash)]
|
#[derive(Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
|
||||||
pub struct Pubkey([u8; 32]);
|
pub struct Pubkey([u8; 32]);
|
||||||
|
|
||||||
static HRP_NPUB: Hrp = Hrp::parse_unchecked("npub");
|
static HRP_NPUB: Hrp = Hrp::parse_unchecked("npub");
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::{
|
|||||||
column::Columns,
|
column::Columns,
|
||||||
imgcache::ImageCache,
|
imgcache::ImageCache,
|
||||||
login_manager::AcquireKeyState,
|
login_manager::AcquireKeyState,
|
||||||
|
muted::Muted,
|
||||||
route::{Route, Router},
|
route::{Route, Router},
|
||||||
storage::{KeyStorageResponse, KeyStorageType},
|
storage::{KeyStorageResponse, KeyStorageType},
|
||||||
ui::{
|
ui::{
|
||||||
@@ -116,8 +117,98 @@ impl AccountRelayData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AccountMutedData {
|
||||||
|
filter: Filter,
|
||||||
|
subid: String,
|
||||||
|
sub: Option<Subscription>,
|
||||||
|
muted: Muted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountMutedData {
|
||||||
|
pub fn new(ndb: &Ndb, pool: &mut RelayPool, pubkey: &[u8; 32]) -> Self {
|
||||||
|
// Construct a filter for the user's NIP-51 muted list
|
||||||
|
let filter = Filter::new()
|
||||||
|
.authors([pubkey])
|
||||||
|
.kinds([10000])
|
||||||
|
.limit(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Local ndb subscription
|
||||||
|
let ndbsub = ndb
|
||||||
|
.subscribe(&[filter.clone()])
|
||||||
|
.expect("ndb muted subscription");
|
||||||
|
|
||||||
|
// Query the ndb immediately to see if the user's muted 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 muted results")
|
||||||
|
.iter()
|
||||||
|
.map(|qr| qr.note_key)
|
||||||
|
.collect::<Vec<NoteKey>>();
|
||||||
|
let muted = Self::harvest_nip51_muted(ndb, &txn, &nks);
|
||||||
|
debug!("pubkey {}: initial muted {:?}", hex::encode(pubkey), muted);
|
||||||
|
|
||||||
|
// Id for future remote relay subscriptions
|
||||||
|
let subid = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
// Add remote subscription to existing relays
|
||||||
|
pool.subscribe(subid.clone(), vec![filter.clone()]);
|
||||||
|
|
||||||
|
AccountMutedData {
|
||||||
|
filter,
|
||||||
|
subid,
|
||||||
|
sub: Some(ndbsub),
|
||||||
|
muted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn harvest_nip51_muted(ndb: &Ndb, txn: &Transaction, nks: &[NoteKey]) -> Muted {
|
||||||
|
let mut muted = Muted::default();
|
||||||
|
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("p") => {
|
||||||
|
if let Some(id) = tag.get(1).and_then(|f| f.variant().id()) {
|
||||||
|
muted.pubkeys.insert(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("t") => {
|
||||||
|
if let Some(str) = tag.get(1).and_then(|f| f.variant().str()) {
|
||||||
|
muted.hashtags.insert(str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("word") => {
|
||||||
|
if let Some(str) = tag.get(1).and_then(|f| f.variant().str()) {
|
||||||
|
muted.words.insert(str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("e") => {
|
||||||
|
if let Some(id) = tag.get(1).and_then(|f| f.variant().id()) {
|
||||||
|
muted.threads.insert(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("alt") => {
|
||||||
|
// maybe we can ignore these?
|
||||||
|
}
|
||||||
|
Some(x) => error!("query_nip51_muted: unexpected tag: {}", x),
|
||||||
|
None => error!(
|
||||||
|
"query_nip51_muted: bad tag value: {:?}",
|
||||||
|
tag.get_unchecked(0).variant()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AccountData {
|
pub struct AccountData {
|
||||||
relay: AccountRelayData,
|
relay: AccountRelayData,
|
||||||
|
muted: AccountMutedData,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The interface for managing the user's accounts.
|
/// The interface for managing the user's accounts.
|
||||||
@@ -355,6 +446,10 @@ impl Accounts {
|
|||||||
&ClientMessage::req(data.relay.subid.clone(), vec![data.relay.filter.clone()]),
|
&ClientMessage::req(data.relay.subid.clone(), vec![data.relay.filter.clone()]),
|
||||||
relay_url,
|
relay_url,
|
||||||
);
|
);
|
||||||
|
pool.send_to(
|
||||||
|
&ClientMessage::req(data.muted.subid.clone(), vec![data.muted.filter.clone()]),
|
||||||
|
relay_url,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +476,7 @@ impl Accounts {
|
|||||||
// Create the user account data
|
// Create the user account data
|
||||||
let new_account_data = AccountData {
|
let new_account_data = AccountData {
|
||||||
relay: AccountRelayData::new(ndb, pool, pubkey),
|
relay: AccountRelayData::new(ndb, pool, pubkey),
|
||||||
|
muted: AccountMutedData::new(ndb, pool, pubkey),
|
||||||
};
|
};
|
||||||
self.account_data.insert(*pubkey, new_account_data);
|
self.account_data.insert(*pubkey, new_account_data);
|
||||||
}
|
}
|
||||||
@@ -408,6 +504,16 @@ impl Accounts {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(sub) = data.muted.sub {
|
||||||
|
let nks = ndb.poll_for_notes(sub, 1);
|
||||||
|
if !nks.is_empty() {
|
||||||
|
let txn = Transaction::new(ndb).expect("txn");
|
||||||
|
let muted = AccountMutedData::harvest_nip51_muted(ndb, &txn, &nks);
|
||||||
|
debug!("pubkey {}: updated muted {:?}", hex::encode(pubkey), muted);
|
||||||
|
data.muted.muted = muted;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ mod imgcache;
|
|||||||
mod key_parsing;
|
mod key_parsing;
|
||||||
pub mod login_manager;
|
pub mod login_manager;
|
||||||
mod multi_subscriber;
|
mod multi_subscriber;
|
||||||
|
mod muted;
|
||||||
mod nav;
|
mod nav;
|
||||||
mod note;
|
mod note;
|
||||||
mod notecache;
|
mod notecache;
|
||||||
|
|||||||
59
src/muted.rs
Normal file
59
src/muted.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use nostrdb::Note;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Muted {
|
||||||
|
// TODO - implement private mutes
|
||||||
|
pub pubkeys: BTreeSet<[u8; 32]>,
|
||||||
|
pub hashtags: BTreeSet<String>,
|
||||||
|
pub words: BTreeSet<String>,
|
||||||
|
pub threads: BTreeSet<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Muted {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Muted")
|
||||||
|
.field(
|
||||||
|
"pubkeys",
|
||||||
|
&self.pubkeys.iter().map(hex::encode).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.field("hashtags", &self.hashtags)
|
||||||
|
.field("words", &self.words)
|
||||||
|
.field(
|
||||||
|
"threads",
|
||||||
|
&self.threads.iter().map(hex::encode).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Muted {
|
||||||
|
pub fn is_muted(&self, note: &Note) -> bool {
|
||||||
|
if self.pubkeys.contains(note.pubkey()) {
|
||||||
|
debug!(
|
||||||
|
"{}: MUTED pubkey: {}",
|
||||||
|
hex::encode(note.id()),
|
||||||
|
hex::encode(note.pubkey())
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// FIXME - Implement hashtag muting here
|
||||||
|
|
||||||
|
// TODO - let's not add this for now, we will likely need to
|
||||||
|
// have an optimized data structure in nostrdb to properly
|
||||||
|
// mute words. this mutes substrings which is not ideal.
|
||||||
|
//
|
||||||
|
// let content = note.content().to_lowercase();
|
||||||
|
// for word in &self.words {
|
||||||
|
// if content.contains(&word.to_lowercase()) {
|
||||||
|
// debug!("{}: MUTED word: {}", hex::encode(note.id()), word);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FIXME - Implement thread muting here
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user