nostralink 0.2.1

Linked data library for nostr
Documentation
//! Events AdmitPolicy to be used with a nostr client

use nostr::Timestamp;
use std::collections::HashSet;
use std::sync::Arc;

use super::mute::MuteManager;
use super::prelude::*;
use nostr_sdk::prelude::*;

/// [`AdmitPolicy`] that loads muted public keys from the RDF store.
/// Muted pubkeys hash sets are cached by timestamp
#[derive(Debug)]
pub struct NostraEventPolicy {
    // A Cache of muted pubkeys loaded from the store
    muted_pubkeys_cache: Cache<u64, Arc<HashSet<PublicKey>>>,
    store: Arc<RdfEventsStore>,
}

impl NostraEventPolicy {
    /// ```
    /// use nostralink::prelude::*;
    /// use std::sync::Arc;
    ///
    /// let store = Arc::new(RdfEventsStore::new_inmem(None).unwrap());
    /// let admit_policy = NostraEventPolicy::from_store(&store);
    /// let client = Client::builder()
    ///     .admit_policy(admit_policy)
    ///     .database(store.clone())
    ///     .build();
    /// ```
    pub fn from_store(events_store: &Arc<RdfEventsStore>) -> Self {
        Self {
            muted_pubkeys_cache: Cache::new(4),
            store: events_store.clone(),
        }
    }

    pub fn get_cache_muted(
        &self,
    ) -> Result<Arc<HashSet<PublicKey>>, RdfStoreError> {
        let keys = Arc::new(self.store.all_muted_pubkeys()?);
        self.muted_pubkeys_cache
            .insert(Timestamp::now().as_u64(), keys.clone());
        Ok(keys)
    }

    pub fn muted_set(&self) -> Result<Arc<HashSet<PublicKey>>, RdfStoreError> {
        let ts_filter = Timestamp::now().as_u64() - 5;

        match self
            .muted_pubkeys_cache
            .iter()
            .filter(|(ts, _k)| *ts > ts_filter.into())
            .next()
        {
            Some((_ts, keys)) => Ok(keys),
            None => self.get_cache_muted(),
        }
    }
}

impl AdmitPolicy for NostraEventPolicy {
    fn admit_connection<'a>(
        &'a self,
        _relay_url: &'a RelayUrl,
    ) -> BoxedFuture<'a, Result<AdmitStatus, PolicyError>> {
        Box::pin(async move { Ok(AdmitStatus::success()) })
    }

    fn admit_event<'a>(
        &'a self,
        _relay_url: &'a RelayUrl,
        _subscription_id: &'a SubscriptionId,
        event: &'a Event,
    ) -> BoxedFuture<'a, Result<AdmitStatus, PolicyError>> {
        Box::pin(async move {
            let muted_pubkeys = self.muted_set().map_err(|_| {
                PolicyError::Backend(Box::from("Cannot load muted pubkeys"))
            })?;

            if muted_pubkeys.contains(&event.pubkey) {
                return Ok(AdmitStatus::rejected("Muted"));
            }

            Ok(AdmitStatus::success())
        })
    }
}