nostr_database/
ext.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Copyright (c) 2023-2025 Rust Nostr Developers
3// Distributed under the MIT software license
4
5//! Nostr database extension
6
7use std::collections::{BTreeSet, HashMap, HashSet};
8
9use nostr::prelude::*;
10
11use crate::{DatabaseError, Events, NostrDatabase, Profile, RelaysMap};
12
13/// Nostr Event Store Extension
14pub trait NostrDatabaseExt: NostrDatabase {
15    /// Get public key metadata
16    fn metadata(
17        &self,
18        public_key: PublicKey,
19    ) -> BoxedFuture<Result<Option<Metadata>, DatabaseError>> {
20        Box::pin(async move {
21            let filter = Filter::new()
22                .author(public_key)
23                .kind(Kind::Metadata)
24                .limit(1);
25            let events: Events = self.query(filter).await?;
26            match events.first_owned() {
27                Some(event) => Ok(Some(
28                    Metadata::from_json(event.content).map_err(DatabaseError::backend)?,
29                )),
30                None => Ok(None),
31            }
32        })
33    }
34
35    /// Get contact list public keys
36    fn contacts_public_keys(
37        &self,
38        public_key: PublicKey,
39    ) -> BoxedFuture<Result<HashSet<PublicKey>, DatabaseError>> {
40        Box::pin(async move {
41            let filter = Filter::new()
42                .author(public_key)
43                .kind(Kind::ContactList)
44                .limit(1);
45            let events: Events = self.query(filter).await?;
46            match events.first_owned() {
47                Some(event) => Ok(event.tags.public_keys().copied().collect()),
48                None => Ok(HashSet::new()),
49            }
50        })
51    }
52
53    /// Get contact list with metadata of [`PublicKey`]
54    fn contacts(
55        &self,
56        public_key: PublicKey,
57    ) -> BoxedFuture<Result<BTreeSet<Profile>, DatabaseError>> {
58        Box::pin(async move {
59            let filter = Filter::new()
60                .author(public_key)
61                .kind(Kind::ContactList)
62                .limit(1);
63            let events: Events = self.query(filter).await?;
64            match events.first_owned() {
65                Some(event) => {
66                    // Get contacts metadata
67                    let filter = Filter::new()
68                        .authors(event.tags.public_keys().copied())
69                        .kind(Kind::Metadata);
70                    let mut contacts: HashSet<Profile> = self
71                        .query(filter)
72                        .await?
73                        .into_iter()
74                        .map(|e| {
75                            let metadata: Metadata =
76                                Metadata::from_json(&e.content).unwrap_or_default();
77                            Profile::new(e.pubkey, metadata)
78                        })
79                        .collect();
80
81                    // Extend with missing public keys
82                    contacts.extend(event.tags.public_keys().copied().map(Profile::from));
83
84                    Ok(contacts.into_iter().collect())
85                }
86                None => Ok(BTreeSet::new()),
87            }
88        })
89    }
90
91    /// Get relays list for [PublicKey]
92    ///
93    /// <https://github.com/nostr-protocol/nips/blob/master/65.md>
94    fn relay_list(&self, public_key: PublicKey) -> BoxedFuture<Result<RelaysMap, DatabaseError>> {
95        Box::pin(async move {
96            // Query
97            let filter: Filter = Filter::default()
98                .author(public_key)
99                .kind(Kind::RelayList)
100                .limit(1);
101            let events: Events = self.query(filter).await?;
102
103            // Extract relay list (NIP65)
104            match events.first_owned() {
105                Some(event) => Ok(nip65::extract_owned_relay_list(event).collect()),
106                None => Ok(HashMap::new()),
107            }
108        })
109    }
110
111    /// Get relays list for public keys
112    ///
113    /// <https://github.com/nostr-protocol/nips/blob/master/65.md>
114    fn relay_lists<'a, I>(
115        &'a self,
116        public_keys: I,
117    ) -> BoxedFuture<'a, Result<HashMap<PublicKey, RelaysMap>, DatabaseError>>
118    where
119        I: IntoIterator<Item = PublicKey> + Send + 'a,
120    {
121        Box::pin(async move {
122            // Query
123            let filter: Filter = Filter::default().authors(public_keys).kind(Kind::RelayList);
124            let events: Events = self.query(filter).await?;
125
126            let mut map = HashMap::with_capacity(events.len());
127
128            for event in events.into_iter() {
129                map.insert(
130                    event.pubkey,
131                    nip65::extract_owned_relay_list(event).collect(),
132                );
133            }
134
135            Ok(map)
136        })
137    }
138}
139
140impl<T: NostrDatabase + ?Sized> NostrDatabaseExt for T {}