caracal 0.4.2

Nostr client for Gemini
use crate::user::CaracalUser;
use duration_str::parse as dur_parse;
use nostr_sdk::prelude::*;
use std::sync::Arc;

pub async fn user_subscribe(user: &CaracalUser) {
    let Ok(contacts) =
        user.client.get_contact_list(dur_parse("8s").unwrap()).await
    else {
        eprintln!("Failed to retrieve contact list");
        return;
    };

    let now = Timestamp::now();
    let sub_id = SubscriptionId::new("user");

    let Ok(signer_pubk) = user.signer.get_public_key().await else {
        eprintln!("Cannot get public key from signer");
        return;
    };

    user.client.connect().await;
    user.client.unsubscribe(&sub_id).await;

    let mut follow_pubkeys: Vec<PublicKey> =
        contacts.into_iter().map(|c| c.public_key).collect();
    follow_pubkeys.push(signer_pubk);

    let subs = vec![
        // Metadata and relay+inbox lists
        Filter::new()
            .kind(Kind::Metadata)
            .kind(Kind::RelayList)
            .kind(Kind::InboxRelays)
            .since(now - dur_parse("6mon").unwrap())
            .limit(30),
        // Contact and bookmarks
        Filter::new()
            .kind(Kind::ContactList)
            .kind(Kind::Bookmarks)
            // Payment targets
            .kind(Kind::Custom(10133))
            .since(now - dur_parse("90d").unwrap())
            .limit(10),
        // Notes, reactions and reposts
        Filter::new()
            .kind(Kind::TextNote)
            .kind(Kind::LongFormTextNote)
            .kind(Kind::Reaction)
            .kind(Kind::GenericRepost)
            .kind(Kind::Repost)
            .since(now - dur_parse("90d").unwrap())
            .limit(100),
    ];

    for pubkey in &follow_pubkeys {
        for sub in &subs {
            if let Err(err) = user
                .client
                .subscribe_with_id(
                    SubscriptionId::new(format!("{pubkey}")),
                    sub.clone().author(*pubkey),
                    None,
                )
                .await
            {
                eprintln!("Error subscribing: {err}")
            }
        }
    }
}

pub async fn prepare_nostr_client(client: Arc<Client>) {
    client.connect().await;

    let _ = client
        .subscribe(
            Filter::new()
                .kind(Kind::Metadata)
                .limit(1000)
                .since(Timestamp::now() - dur_parse("90d").unwrap()),
            None,
        )
        .await;
}

pub async fn handle_nostr_notifications(
    client: Arc<Client>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    client
        .handle_notifications(|notification| async {
            if let RelayPoolNotification::Event {
                relay_url: _,
                subscription_id: _,
                event,
            } = notification
            {
                match event.kind {
                    Kind::TextNote => {
                        if let Err(err) = client
                            .subscribe(
                                Filter::new()
                                    .kind(Kind::Metadata)
                                    .author(event.pubkey)
                                    .limit(1),
                                None,
                            )
                            .await
                        {
                            eprintln!("Metadata sub failed: {err}");
                        }
                    }
                    Kind::RelayList => {
                        for (url, _metadata) in
                            nip65::extract_relay_list(&event)
                        {
                            let Ok(_) = client.add_discovery_relay(url).await
                            else {
                                continue;
                            };

                            let Ok(_) = client.add_read_relay(url).await else {
                                continue;
                            };
                        }
                    }
                    _ => {}
                }
            } else if let RelayPoolNotification::Message {
                relay_url: _,
                message,
            } = notification
                && let RelayMessage::Event {
                    subscription_id: _,
                    ref event,
                } = message
                && let Kind::RelayList = event.kind
            {
                for (url, _metadata) in nip65::extract_relay_list(event) {
                    let Ok(_) = client.add_discovery_relay(url).await else {
                        continue;
                    };

                    let Ok(_) = client.add_read_relay(url).await else {
                        continue;
                    };

                    /*
                    if let Some(RelayMetadata::Write) = metadata {
                        println!("{} {}", "=>".cyan(), url.to_string().bold().purple());
                    }
                    */
                }
            }

            Ok(false)
        })
        .await?;

    Ok(())
}