caracal 0.2.8

Nostr client for Gemini
use std::collections::HashMap;

use fast_config::FastConfig;
use nostr::nips::nip46::NostrConnectURI;
use nostr::{Keys, NostrSigner};
use nostr_sdk::prelude::*;

use once_cell::sync::Lazy;
use std::ops::DerefMut;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc::{Receiver, Sender};

use crate::config::BaseRelaysConfig;
use crate::relays::default_relays;
use crate::storage::Storage;

pub struct CaracalUser {
    pub identity_path: PathBuf,
    pub client: Arc<Client>,
    pub upload_keys: Keys,
    pub signer: Arc<dyn NostrSigner>,
    pub connect_uri: Option<NostrConnectURI>,
    pub storage: Storage,
    pub tx: Sender<u8>,
    pub rx: Receiver<u8>,
}

impl CaracalUser {
    pub fn path_relay_list(&self) -> PathBuf {
        self.identity_path.join("nip65_relays.json")
    }

    pub async fn configure(&self) {
        if !self.path_relay_list().is_file() {
            let config = BaseRelaysConfig::default();
            if let Err(_) =
                config.save(self.path_relay_list(), fast_config::Format::JSON)
            {
                eprintln!("Failed to save config");
            }
        }

        if let Err(err) = self.advertise_relay_list().await {
            eprintln!("Failed to advertise relay list: {err}");
        }
    }

    pub fn save_relay_list(
        &self,
        config: &BaseRelaysConfig,
    ) -> Result<(), fast_config::Error> {
        config.save(self.path_relay_list(), fast_config::Format::JSON)
    }

    pub fn relay_list_config(&self) -> BaseRelaysConfig {
        let mut config = BaseRelaysConfig::default();
        let _ = config.load(self.path_relay_list(), fast_config::Format::JSON);
        config
    }

    pub async fn advertise_relay_list(
        &self,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let relay_config = self.relay_list_config();

        let mut relay_metadata: HashMap<RelayUrl, Option<RelayMetadata>> =
            HashMap::new();

        for rurl in relay_config.read_relays {
            let _ = self.client.add_read_relay(&rurl).await;

            relay_metadata.insert(rurl, Some(RelayMetadata::Read));
        }

        for wurl in relay_config.write_relays {
            let _ = self.client.add_write_relay(&wurl).await;

            relay_metadata.insert(wurl, Some(RelayMetadata::Write));
        }

        let relay_list_builder = EventBuilder::relay_list(relay_metadata);

        self.client
            .send_event_builder_to(default_relays(), relay_list_builder)
            .await?;

        /* Send (fixed list for now to make the DMs UI work) inbox relays event */
        let inboxr: Vec<&str> =
            vec!["wss://relay.damus.io", "wss://nostr.bitcoiner.social"];

        let inbox_relays_builder = EventBuilder::new(Kind::InboxRelays, "")
            .tags(
                inboxr
                    .into_iter()
                    .map(|ir| Tag::custom(TagKind::Relay, vec![ir])),
            );

        self.client
            .send_event_builder_to(default_relays(), inbox_relays_builder)
            .await?;

        Ok(())
    }
}

pub static mut USERS: Lazy<HashMap<String, CaracalUser>> =
    Lazy::new(HashMap::new);

pub fn lookup_user(fingerprint: String) -> Option<&'static CaracalUser> {
    #[allow(static_mut_refs)]
    unsafe {
        let us = USERS.deref_mut();
        us.get(&fingerprint.clone())
    }
}

pub fn lookup_user_mut(
    fingerprint: String,
) -> Option<&'static mut CaracalUser> {
    #[allow(static_mut_refs)]
    unsafe {
        let us = USERS.deref_mut();
        us.get_mut(&fingerprint.clone())
    }
}