Skip to main content

hashtree_cli/
root_events.rs

1//! Nostr root-event parsing shared by the HTTP resolver.
2
3use git_remote_htree::nostr_client::hashtree_root_kinds;
4use nostr::{nips::nip19::FromBech32, Alphabet, Event, Filter, PublicKey, SingleLetterTag};
5
6#[derive(Debug, Clone)]
7pub struct PeerRootEvent {
8    pub hash: String,
9    pub key: Option<String>,
10    pub encrypted_key: Option<String>,
11    pub self_encrypted_key: Option<String>,
12    pub event_id: String,
13    pub created_at: u64,
14    pub peer_id: String,
15}
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            .kinds(hashtree_root_kinds())
24            .author(author)
25            .custom_tag(
26                SingleLetterTag::lowercase(Alphabet::D),
27                tree_name.to_string(),
28            )
29            .custom_tag(SingleLetterTag::lowercase(Alphabet::L), "hashtree")
30            .limit(50),
31    )
32}
33
34pub fn pick_latest_event<'a, I>(events: I) -> Option<&'a Event>
35where
36    I: IntoIterator<Item = &'a Event>,
37{
38    events.into_iter().max_by(|a, b| {
39        let ordering = a.created_at.cmp(&b.created_at);
40        if ordering == std::cmp::Ordering::Equal {
41            b.id.cmp(&a.id)
42        } else {
43            ordering
44        }
45    })
46}
47
48pub fn root_event_from_peer(
49    event: &Event,
50    peer_id: &str,
51    tree_name: &str,
52) -> Option<PeerRootEvent> {
53    let mut tree_match = false;
54    let mut labeled = false;
55    let mut key = None;
56    let mut encrypted_key = None;
57    let mut self_encrypted_key = None;
58    let mut hash_tag = None;
59
60    for tag in event.tags.iter() {
61        let slice = tag.as_slice();
62        if slice.len() < 2 {
63            continue;
64        }
65        match slice[0].as_str() {
66            "d" => tree_match = slice[1].as_str() == tree_name,
67            "l" => labeled |= slice[1].as_str() == "hashtree",
68            "hash" => hash_tag = Some(slice[1].to_string()),
69            "key" => key = Some(slice[1].to_string()),
70            "encryptedKey" => encrypted_key = Some(slice[1].to_string()),
71            "selfEncryptedKey" => self_encrypted_key = Some(slice[1].to_string()),
72            _ => {}
73        }
74    }
75
76    if !tree_match || !labeled {
77        return None;
78    }
79
80    let hash = hash_tag.or_else(|| {
81        if event.content.is_empty() {
82            None
83        } else {
84            Some(event.content.clone())
85        }
86    })?;
87
88    Some(PeerRootEvent {
89        hash,
90        key,
91        encrypted_key,
92        self_encrypted_key,
93        event_id: event.id.to_hex(),
94        created_at: event.created_at.as_secs(),
95        peer_id: peer_id.to_string(),
96    })
97}