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 let tags = parse_hashtags(text);
19
20 let encrypted = nip44::encrypt(
22 self.keys.secret_key(),
23 &self.keys.public_key(), text,
25 nip44::Version::default(),
26 )?;
27
28 let mut builder = EventBuilder::new(Kind::from(1059), encrypted);
31
32 for tag in &tags {
34 builder = builder.tag(Tag::hashtag(tag.to_lowercase()));
35 }
36
37 builder = builder.tag(Tag::public_key(self.keys.public_key()));
39
40 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}