Skip to main content

hashtree_cli/webrtc/
root_events.rs

1use nostr::{nips::nip19::FromBech32, Alphabet, Event, Filter, Kind, PublicKey, SingleLetterTag};
2
3#[derive(Debug, Clone)]
4pub struct PeerRootEvent {
5    pub hash: String,
6    pub key: Option<String>,
7    pub encrypted_key: Option<String>,
8    pub self_encrypted_key: Option<String>,
9    pub event_id: String,
10    pub created_at: u64,
11    pub peer_id: String,
12}
13
14pub const HASHTREE_KIND: u16 = 30078;
15pub const HASHTREE_LABEL: &str = "hashtree";
16
17pub fn build_root_filter(owner_pubkey: &str, tree_name: &str) -> Option<Filter> {
18    let author = PublicKey::from_hex(owner_pubkey)
19        .or_else(|_| PublicKey::from_bech32(owner_pubkey))
20        .ok()?;
21    Some(
22        Filter::new()
23            .kind(Kind::Custom(HASHTREE_KIND))
24            .author(author)
25            .custom_tag(
26                SingleLetterTag::lowercase(Alphabet::D),
27                vec![tree_name.to_string()],
28            )
29            .custom_tag(
30                SingleLetterTag::lowercase(Alphabet::L),
31                vec![HASHTREE_LABEL.to_string()],
32            )
33            .limit(50),
34    )
35}
36
37pub fn hashtree_event_identifier(event: &Event) -> Option<String> {
38    event.tags.iter().find_map(|tag| {
39        let slice = tag.as_slice();
40        if slice.len() >= 2 && slice[0].as_str() == "d" {
41            Some(slice[1].to_string())
42        } else {
43            None
44        }
45    })
46}
47
48pub fn is_hashtree_labeled_event(event: &Event) -> bool {
49    event.tags.iter().any(|tag| {
50        let slice = tag.as_slice();
51        slice.len() >= 2 && slice[0].as_str() == "l" && slice[1].as_str() == HASHTREE_LABEL
52    })
53}
54
55pub fn pick_latest_event<'a, I>(events: I) -> Option<&'a Event>
56where
57    I: IntoIterator<Item = &'a Event>,
58{
59    events.into_iter().max_by(|a, b| {
60        let ordering = a.created_at.cmp(&b.created_at);
61        if ordering == std::cmp::Ordering::Equal {
62            a.id.cmp(&b.id)
63        } else {
64            ordering
65        }
66    })
67}
68
69pub fn root_event_from_peer(
70    event: &Event,
71    peer_id: &str,
72    tree_name: &str,
73) -> Option<PeerRootEvent> {
74    if hashtree_event_identifier(event).as_deref() != Some(tree_name)
75        || !is_hashtree_labeled_event(event)
76    {
77        return None;
78    }
79
80    let mut key = None;
81    let mut encrypted_key = None;
82    let mut self_encrypted_key = None;
83    let mut hash_tag = None;
84
85    for tag in &event.tags {
86        let slice = tag.as_slice();
87        if slice.len() < 2 {
88            continue;
89        }
90        match slice[0].as_str() {
91            "hash" => hash_tag = Some(slice[1].to_string()),
92            "key" => key = Some(slice[1].to_string()),
93            "encryptedKey" => encrypted_key = Some(slice[1].to_string()),
94            "selfEncryptedKey" => self_encrypted_key = Some(slice[1].to_string()),
95            _ => {}
96        }
97    }
98
99    let hash = hash_tag.or_else(|| {
100        if event.content.is_empty() {
101            None
102        } else {
103            Some(event.content.clone())
104        }
105    })?;
106
107    Some(PeerRootEvent {
108        hash,
109        key,
110        encrypted_key,
111        self_encrypted_key,
112        event_id: event.id.to_hex(),
113        created_at: event.created_at.as_u64(),
114        peer_id: peer_id.to_string(),
115    })
116}