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 std::time::Duration;
use thiserror::Error;
use tokio::sync::mpsc::{Receiver, Sender};
use crate::config::{BaseRelaysConfig, PrivateBookmarks, UIConfig};
use crate::storage::Storage;
#[derive(Error, Debug)]
pub enum UserError {
#[error("Nostr client error: {0}")]
NostrClientError(#[from] nostr_sdk::client::Error),
#[error("Nostr database error: {0}")]
NostrDatabaseError(#[from] DatabaseError),
#[error("Nostr signer error: {0}")]
NostrSignerError(#[from] SignerError),
#[error("Blossom client error: {0}")]
BlossomError(#[from] nostr_blossom::error::Error),
#[error("IO error: {0}")]
IOError(#[from] std::io::Error),
}
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 fn path_ui(&self) -> PathBuf {
self.identity_path.join("ui.json")
}
pub fn path_bookmarks(&self) -> PathBuf {
self.identity_path.join("private_bookmarks.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 !self.path_bookmarks().is_file() {
let config = PrivateBookmarks::default();
if let Err(_) =
config.save(self.path_bookmarks(), fast_config::Format::JSON)
{
eprintln!("Failed to init bookmarks");
}
}
if !self.path_ui().is_file() {
let config = UIConfig::default();
if let Err(_) =
config.save(self.path_ui(), fast_config::Format::JSON)
{
eprintln!("Failed to save UI config");
}
}
let ui_cfg = self.ui_config();
rust_i18n::set_locale(&ui_cfg.locale);
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 save_ui_config(
&self,
config: &UIConfig,
) -> Result<(), fast_config::Error> {
config.save(self.path_ui(), 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 fn ui_config(&self) -> UIConfig {
let mut config = UIConfig::default();
let _ = config.load(self.path_ui(), fast_config::Format::JSON);
config
}
pub fn priv_bookmarks(&self) -> PrivateBookmarks {
let mut config = PrivateBookmarks::default();
let _ = config.load(self.path_bookmarks(), fast_config::Format::JSON);
config
}
pub async fn contact_list_pubkeys(
&self,
) -> Result<Vec<PublicKey>, UserError> {
let mut pubkeys = Vec::new();
let signer_pubk = self.signer.get_public_key().await?;
if let Ok(event) = self
.db_query_first(
Filter::new().kind(Kind::ContactList).author(signer_pubk),
)
.await
{
pubkeys.extend(event.tags.public_keys());
}
Ok(pubkeys)
}
pub async fn db_query(
&self,
filter: Filter,
) -> Result<Events, DatabaseError> {
self.client.database().query(filter).await
}
pub async fn db_query_first(
&self,
filter: Filter,
) -> Result<Event, DatabaseError> {
self.db_query(filter).await.and_then(|events| {
events.first_owned().ok_or(DatabaseError::NotSupported)
})
}
pub async fn mute_list(&self) -> MuteList {
let mut mute_list = MuteList::default();
if let Ok(signer_pubk) = self.signer.get_public_key().await {
if let Ok(event) = self
.db_query_first(
Filter::new().kind(Kind::MuteList).author(signer_pubk),
)
.await
{
mute_list.public_keys.extend(event.tags.public_keys());
mute_list
.hashtags
.extend(event.tags.hashtags().map(str::to_string));
}
}
mute_list
}
pub async fn fetch_auto(
&self,
filter: Filter,
) -> Result<Events, nostr_sdk::client::Error> {
self.client
.fetch_events(filter, Duration::from_secs(5))
.await
}
pub async fn send_builder(
&self,
event_builder: EventBuilder,
) -> Result<Output<EventId>, nostr_sdk::client::Error> {
self.client.send_event_builder(event_builder).await
}
pub async fn public_key(&self) -> Result<PublicKey, SignerError> {
self.signer.get_public_key().await
}
}
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)
}
}
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)
}
}