use std::hash::{DefaultHasher, Hash, Hasher}; use itertools::Itertools; use nostr_sdk::prelude::*; pub trait EventExt { fn uniq_id(&self) -> u64; fn extract_public_keys(&self) -> Vec; } impl EventExt for Event { fn uniq_id(&self) -> u64 { let mut hasher = DefaultHasher::new(); let mut pubkeys: Vec = self.extract_public_keys(); pubkeys.sort(); pubkeys.hash(&mut hasher); hasher.finish() } fn extract_public_keys(&self) -> Vec { let mut public_keys: Vec = self.tags.public_keys().collect(); public_keys.push(self.pubkey); public_keys.into_iter().unique().collect() } } impl EventExt for UnsignedEvent { fn uniq_id(&self) -> u64 { let mut hasher = DefaultHasher::new(); let mut pubkeys: Vec = vec![]; // Add all public keys from event pubkeys.push(self.pubkey); pubkeys.extend(self.tags.public_keys().collect::>()); // Generate unique hash pubkeys .into_iter() .unique() .sorted() .collect::>() .hash(&mut hasher); hasher.finish() } fn extract_public_keys(&self) -> Vec { let mut public_keys: Vec = self.tags.public_keys().collect(); public_keys.push(self.pubkey); public_keys.into_iter().unique().sorted().collect() } } /// Custom emoji (NIP-30) declared by `emoji` tags, first per shortcode wins pub fn emojis_from_tags(tags: &Tags) -> Vec<(String, Url)> { let mut emojis: Vec<(String, Url)> = Vec::new(); for tag in tags.iter() { let Ok(Nip30Tag::Emoji { shortcode, image_url, .. }) = Nip30Tag::try_from(tag) else { continue; }; if !emojis.iter().any(|(existing, _)| *existing == shortcode) { emojis.push((shortcode, image_url)); } } emojis } #[cfg(test)] mod tests { use super::*; #[test] fn emojis_from_tags_parses_and_deduplicates() { let tags = Tags::from_list(vec![ Tag::parse(vec!["emoji", "soapbox", "https://example.com/soapbox.png"]).unwrap(), Tag::parse(vec!["emoji", "soapbox", "https://example.com/other.png"]).unwrap(), Tag::parse(vec![ "emoji", "bad shortcode", "https://example.com/bad.png", ]) .unwrap(), ]); let emojis = emojis_from_tags(&tags); assert_eq!(emojis.len(), 1); assert_eq!(emojis[0].0, "soapbox"); assert_eq!( emojis[0].1, Url::parse("https://example.com/soapbox.png").unwrap() ); } }