caracal 0.4.2

Nostr client for Gemini
use emojis_rs::EMOJI_MEGAPHONE;
use http::Uri;
use nostr_sdk::prelude::*;
use std::collections::HashSet;
use std::str::FromStr;
use std::time::Duration;
use url::Url;

pub async fn get_user_metadata(
    client: &Client,
    pubkey: PublicKey,
) -> Option<Metadata> {
    let filter_meta = Filter::new().author(pubkey).kind(Kind::Metadata);

    match client.database().query(filter_meta).await {
        Ok(mevents) => {
            if !mevents.is_empty() {
                match Metadata::from_json(&mevents.first().unwrap().content) {
                    Ok(usermeta) => return Some(usermeta.clone()),
                    Err(_err) => return None,
                }
            }
        }
        Err(_err) => {
            return None;
        }
    }

    None
}

pub async fn fetch_user_metadata(
    client: &Client,
    pubk: PublicKey,
) -> Option<Metadata> {
    if let Ok(Some(metadata)) =
        client.fetch_metadata(pubk, Duration::from_secs(5)).await
    {
        Some(metadata)
    } else {
        None
    }
}

pub fn notes_filter() -> Filter {
    Filter::new()
        .kind(Kind::TextNote)
        .kind(Kind::LongFormTextNote)
}

pub fn parse_note_urls(event: &Event) -> Vec<(Uri, Uri)> {
    let mut vec: Vec<(Uri, Uri)> = Vec::new();

    for elem in event.content.split_whitespace() {
        if let Ok(url) = Url::parse(elem) {
            let scheme = url.scheme();
            let Ok(orig_uri) = Uri::from_str(url.as_str()) else {
                continue;
            };

            match scheme {
                "nostr" => {
                    let path = url.path();

                    if path.is_empty() {
                        continue;
                    }

                    let uri: Uri = if path.starts_with("nevent") {
                        Uri::builder()
                            .path_and_query(format!("/note/{}/thread", path))
                            .build()
                            .unwrap()
                    } else {
                        Uri::from_str(url.as_str()).unwrap()
                    };

                    vec.push((orig_uri, uri));
                }
                _ => {
                    vec.push((orig_uri.clone(), orig_uri.clone()));
                    continue;
                }
            }
        }
    }

    vec
}

pub fn parse_note_ids(event: &Event) -> (Vec<EventId>, Vec<PublicKey>) {
    let mut vec_evid: Vec<EventId> = Vec::new();
    let mut vec_pk: Vec<PublicKey> = Vec::new();

    for elem in event.content.split_whitespace() {
        let Ok(url) = Url::parse(elem) else { continue };

        let scheme = url.scheme();

        match scheme {
            "nostr" => {
                let urls = url.as_str();
                let path = url.path();

                if path.starts_with("nevent") {
                    let Ok(res) = Nip19Event::from_nostr_uri(urls) else {
                        continue;
                    };

                    vec_evid.push(res.event_id);
                }
                if path.starts_with("npub") {
                    let Ok(pk) = PublicKey::parse(path) else {
                        continue;
                    };

                    vec_pk.push(pk);
                }
            }
            _ => {
                continue;
            }
        }
    }

    (vec_evid, vec_pk)
}

pub fn reactions_summary(reaction_events: &Events) -> String {
    let mut summary = String::new();
    let rlist: HashSet<_> =
        reaction_events.iter().map(|e| e.content.clone()).collect();

    for emoji in rlist {
        let count = reaction_events
            .iter()
            .filter(|e| e.content == emoji)
            .count();

        let emojis = if emoji == "+" {
            "\u{1F44D}"
        } else if let Some(e) = emojis::get_by_shortcode(&emoji) {
            e.as_str()
        } else {
            if emoji.starts_with(":") { "" } else { &emoji }
        };

        if count == 1 {
            summary.push_str(emojis);
        } else {
            if count > 16 {
                summary.push_str(&format!("{} {} ", count, emojis));
            } else {
                for _ in 0..count {
                    summary.push_str(&format!("{} ", emojis));
                }
            }
        }
    }

    summary
}

pub fn reposts_counter(reposts_events: &Events) -> String {
    let mut summary = String::new();
    let count = reposts_events.iter().count();

    if count == 0 {
        return summary;
    } else if count > 16 {
        summary.push_str(&format!(
            "{} {} ",
            reposts_events.iter().count(),
            EMOJI_MEGAPHONE
        ));
    } else {
        for _ in 0..count {
            summary.push_str(EMOJI_MEGAPHONE);
        }
    }

    summary
}