Add support to "only notifications from following" setting

Testing
-------

PASS

Devices: Mix of iPhone simulators and real devices
notepush: This commit
Damus: 4ea6c360e6e33747cb09ecf085049948ec1dadd1 (WIP change from GH issue #2360)
Setup:
  - Account A with push notifications enabled, DM notifications enabled,
    and "only notifications from following enabled"
  - Account A follows B but not C
Steps:
1. Send DM to A from B. Push notification appears
2. Send DM to A from C. Push notification does not appear

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Closes: https://github.com/damus-io/damus/issues/2360
This commit is contained in:
Daniel D’Aquino
2024-08-04 11:47:47 -07:00
parent c51f37e6ea
commit 3ca3a83257
3 changed files with 75 additions and 10 deletions

View File

@ -1,7 +1,7 @@
pub mod mute_manager;
pub mod nostr_network_helper;
mod nostr_event_extensions;
pub mod notification_manager;
pub use mute_manager::MuteManager;
pub use nostr_network_helper::NostrNetworkHelper;
use nostr_event_extensions::{ExtendedEvent, SqlStringConvertible};
pub use notification_manager::NotificationManager;

View File

@ -2,16 +2,16 @@ use super::ExtendedEvent;
use nostr_sdk::prelude::*;
use tokio::time::{timeout, Duration};
pub struct MuteManager {
pub struct NostrNetworkHelper {
client: Client,
}
impl MuteManager {
impl NostrNetworkHelper {
pub async fn new(relay_url: String) -> Result<Self, Box<dyn std::error::Error>> {
let client = Client::new(&Keys::generate());
client.add_relay(relay_url.clone()).await?;
client.connect().await;
Ok(MuteManager { client })
Ok(NostrNetworkHelper { client })
}
pub async fn should_mute_notification_for_pubkey(
@ -125,4 +125,63 @@ impl MuteManager {
self.client.unsubscribe(this_subscription_id).await;
mute_list
}
pub async fn does_pubkey_follow_pubkey(
&self,
source_pubkey: &PublicKey,
target_pubkey: &PublicKey,
) -> bool {
log::debug!(
"Checking if pubkey {:?} follows pubkey {:?}",
source_pubkey,
target_pubkey
);
if let Some(contact_list) = self.get_contact_list(source_pubkey).await {
let tag_contents = contact_list.get_tags_content(TagKind::SingleLetter(SingleLetterTag {
character: Alphabet::P,
uppercase: false,
}));
return tag_contents.iter().any(|t| t == &target_pubkey.to_hex());
}
false
}
pub async fn get_contact_list(&self, pubkey: &PublicKey) -> Option<Event> {
let subscription_filter = Filter::new()
.kinds(vec![Kind::ContactList])
.authors(vec![pubkey.clone()])
.limit(1);
let this_subscription_id = self
.client
.subscribe(Vec::from([subscription_filter]), None)
.await;
let mut contact_list: Option<Event> = None;
let mut notifications = self.client.notifications();
let timeout_duration = Duration::from_secs(10);
while let Ok(result) = timeout(timeout_duration, notifications.recv()).await {
if let Ok(notification) = result {
if let RelayPoolNotification::Event {
subscription_id,
event,
..
} = notification
{
if this_subscription_id == subscription_id && event.kind == Kind::ContactList {
contact_list = Some((*event).clone());
break;
}
}
}
}
if contact_list.is_none() {
log::debug!("Contact list not found for pubkey {:?}", pubkey);
}
self.client.unsubscribe(this_subscription_id).await;
contact_list
}
}

View File

@ -13,7 +13,7 @@ use tokio::sync::Mutex;
use std::collections::HashSet;
use tokio;
use super::mute_manager::MuteManager;
use super::nostr_network_helper::NostrNetworkHelper;
use super::ExtendedEvent;
use super::SqlStringConvertible;
use nostr::Event;
@ -28,7 +28,7 @@ pub struct NotificationManager {
apns_topic: String,
apns_client: Mutex<Client>,
mute_manager: Mutex<MuteManager>,
nostr_network_helper: Mutex<NostrNetworkHelper>,
}
impl NotificationManager {
@ -43,7 +43,7 @@ impl NotificationManager {
apns_environment: a2::client::Endpoint,
apns_topic: String,
) -> Result<Self, Box<dyn std::error::Error>> {
let mute_manager = MuteManager::new(relay_url.clone()).await?;
let mute_manager = NostrNetworkHelper::new(relay_url.clone()).await?;
let connection = db.get()?;
Self::setup_database(&connection)?;
@ -61,7 +61,7 @@ impl NotificationManager {
apns_topic,
apns_client: Mutex::new(client),
db: Mutex::new(db),
mute_manager: Mutex::new(mute_manager),
nostr_network_helper: Mutex::new(mute_manager),
})
}
@ -221,7 +221,7 @@ impl NotificationManager {
let mut pubkeys_to_notify = HashSet::new();
for pubkey in relevant_pubkeys_yet_to_receive {
let should_mute: bool = {
let mute_manager_mutex_guard = self.mute_manager.lock().await;
let mute_manager_mutex_guard = self.nostr_network_helper.lock().await;
mute_manager_mutex_guard
.should_mute_notification_for_pubkey(event, &pubkey)
.await
@ -285,6 +285,12 @@ impl NotificationManager {
event: &Event,
) -> Result<bool, Box<dyn std::error::Error>> {
let notification_preferences = self.get_user_notification_settings(pubkey, device_token).await?;
if notification_preferences.only_notifications_from_following_enabled {
let nostr_network_helper_mutex_guard = self.nostr_network_helper.lock().await;
if !nostr_network_helper_mutex_guard.does_pubkey_follow_pubkey(pubkey, &event.author()).await {
return Ok(false);
}
}
match event.kind {
Kind::TextNote => Ok(notification_preferences.mention_notifications_enabled), // TODO: Not 100% accurate
Kind::EncryptedDirectMessage => Ok(notification_preferences.dm_notifications_enabled),