Skip to main content

dialog_lib/
note.rs

1use crate::{Dialog, Result};
2use nostr_sdk::prelude::*;
3
4#[derive(Debug, Clone)]
5pub struct Note {
6    pub id: EventId,
7    pub text: String,
8    pub tags: Vec<String>,
9    pub created_at: Timestamp,
10    pub is_read: bool,
11    pub is_synced: bool,
12}
13
14impl Dialog {
15    pub async fn create_note(&self, text: &str) -> Result<EventId> {
16        eprintln!("[lib] create_note: building event (len={})", text.len());
17        // Parse hashtags from text
18        let tags = parse_hashtags(text);
19
20        // Create encrypted content for self-DM using NIP-44
21        let encrypted = nip44::encrypt(
22            self.keys.secret_key(),
23            &self.keys.public_key(), // Encrypt to self
24            text,
25            nip44::Version::default(),
26        )?;
27
28        // Build event with NIP-44 encrypted content
29        // Using Kind 1059 for encrypted direct messages
30        let mut builder = EventBuilder::new(Kind::from(1059), encrypted);
31
32        // Add t tags for topics (lowercase)
33        for tag in &tags {
34            builder = builder.tag(Tag::hashtag(tag.to_lowercase()));
35        }
36
37        // Add p tag pointing to self (for self-DM)
38        builder = builder.tag(Tag::public_key(self.keys.public_key()));
39
40        // Send the event (this also saves to local db)
41        let output = self.client.send_event_builder(builder).await?;
42        eprintln!("[lib] create_note: sent; id={}", output.id());
43        Ok(*output.id())
44    }
45
46    pub(crate) fn decrypt_event(&self, event: &Event) -> Result<String> {
47        let decrypted = nip44::decrypt(
48            self.keys.secret_key(),
49            &self.keys.public_key(),
50            &event.content,
51        )?;
52        Ok(decrypted)
53    }
54}
55
56fn parse_hashtags(text: &str) -> Vec<String> {
57    text.split_whitespace()
58        .filter(|word| word.starts_with('#') && word.len() > 1)
59        .map(|tag| tag[1..].to_lowercase())
60        .collect()
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_parse_hashtags() {
69        let text = "This is a #Test note with #Multiple #TAGS";
70        let tags = parse_hashtags(text);
71        assert_eq!(tags, vec!["test", "multiple", "tags"]);
72    }
73
74    #[test]
75    fn test_parse_hashtags_empty() {
76        let text = "This has no hashtags";
77        let tags = parse_hashtags(text);
78        assert!(tags.is_empty());
79    }
80
81    #[test]
82    fn test_parse_hashtags_just_hash() {
83        let text = "This has just # and nothing else";
84        let tags = parse_hashtags(text);
85        assert!(tags.is_empty());
86    }
87}