reaction.rs (6045B)
1 use thiserror::Error; 2 3 use radroots_events::{ 4 reaction::{ 5 RadrootsReaction, RadrootsReactionEventIndex, RadrootsReactionEventMetadata, 6 }, 7 RadrootsNostrEvent, RadrootsNostrEventRef, 8 }; 9 10 use crate::relay::event::RelayIndexerEvent; 11 12 #[derive(Debug, Error)] 13 pub enum RadrootsReactionEventIndexError { 14 #[error("Failed to parse reaction from tags")] 15 ParseError, 16 } 17 18 fn parse_reaction_from_tags( 19 tags: &[Vec<String>], 20 content: &str, 21 ) -> Result<RadrootsReaction, RadrootsReactionEventIndexError> { 22 let parse_address = |addr: &str| -> Option<(u32, String, Option<String>)> { 23 let mut parts = addr.splitn(3, ':'); 24 let kind = parts.next()?.parse::<u32>().ok()?; 25 let author = parts.next()?.to_lowercase(); 26 let d_tag = parts 27 .next() 28 .and_then(|d| if d.is_empty() { None } else { Some(d.to_string()) }); 29 Some((kind, author, d_tag)) 30 }; 31 32 let mut root_id: Option<String> = None; 33 let mut root_relays_e: Option<Vec<String>> = None; 34 let mut root_relays_a: Option<Vec<String>> = None; 35 let mut root_kind_tag: Option<u32> = None; 36 let mut root_kind_addr: Option<u32> = None; 37 let mut root_author_tag: Option<String> = None; 38 let mut root_author_addr: Option<String> = None; 39 let mut root_author_e: Option<String> = None; 40 let mut root_d: Option<String> = None; 41 42 for t in tags { 43 match t.first().map(|k| k.as_str()) { 44 Some("e") => { 45 if let Some(id) = t.get(1).cloned() { 46 root_id = Some(id); 47 } 48 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 49 root_relays_e = Some(vec![r]); 50 } 51 if let Some(pk) = t.get(3).filter(|s| !s.is_empty()) { 52 root_author_e = Some(pk.to_lowercase()); 53 } 54 } 55 Some("a") => { 56 if let Some(addr) = t.get(1).cloned() { 57 if let Some((kind, author, d_tag)) = parse_address(&addr) { 58 root_kind_addr = Some(kind); 59 root_author_addr = Some(author); 60 root_d = d_tag; 61 } 62 } 63 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 64 root_relays_a = Some(vec![r]); 65 } 66 } 67 Some("k") => { 68 if let Some(kind) = t.get(1).and_then(|v| v.parse::<u32>().ok()) { 69 root_kind_tag = Some(kind); 70 } 71 } 72 Some("p") => { 73 if let Some(pk) = t.get(1).filter(|s| !s.is_empty()) { 74 root_author_tag = Some(pk.to_lowercase()); 75 } 76 } 77 _ => {} 78 } 79 } 80 81 let id = root_id.ok_or(RadrootsReactionEventIndexError::ParseError)?; 82 let kind = root_kind_tag.or(root_kind_addr).unwrap_or(1); 83 let author = root_author_tag 84 .or(root_author_addr) 85 .or(root_author_e) 86 .unwrap_or_default(); 87 let relays = root_relays_e.or(root_relays_a); 88 89 let root = RadrootsNostrEventRef { 90 id, 91 author, 92 kind, 93 d_tag: root_d, 94 relays, 95 }; 96 97 Ok(RadrootsReaction { 98 root, 99 content: content.to_string(), 100 }) 101 } 102 103 fn create_radroots_reaction_event_metadata( 104 id: String, 105 author: String, 106 published_at: u32, 107 kind: u32, 108 content: &str, 109 tags: &[Vec<String>], 110 ) -> Result<RadrootsReactionEventMetadata, RadrootsReactionEventIndexError> { 111 let reaction = parse_reaction_from_tags(tags, content)?; 112 Ok(RadrootsReactionEventMetadata { 113 id, 114 author, 115 published_at, 116 kind, 117 reaction, 118 }) 119 } 120 121 pub trait ToRadrootsReactionEventIndex { 122 fn to_radroots_reaction_event( 123 &self, 124 ) -> Result<RadrootsReactionEventIndex, RadrootsReactionEventIndexError>; 125 } 126 127 impl ToRadrootsReactionEventIndex for RelayIndexerEvent { 128 fn to_radroots_reaction_event( 129 &self, 130 ) -> Result<RadrootsReactionEventIndex, RadrootsReactionEventIndexError> { 131 let kind_u32 = self.kind.as_u64() as u32; 132 let id = self.id.clone(); 133 let author = self.author.clone(); 134 135 let metadata = create_radroots_reaction_event_metadata( 136 id.clone(), 137 author.clone(), 138 self.created_at, 139 kind_u32, 140 &self.content, 141 &self.tags, 142 )?; 143 144 Ok(RadrootsReactionEventIndex { 145 event: RadrootsNostrEvent { 146 id, 147 author, 148 created_at: self.created_at, 149 kind: kind_u32, 150 tags: self.tags.clone(), 151 content: self.content.clone(), 152 sig: self.sig.clone(), 153 }, 154 metadata, 155 }) 156 } 157 } 158 159 #[cfg(test)] 160 mod tests { 161 use super::parse_reaction_from_tags; 162 163 #[test] 164 fn reaction_parses_event_reference() { 165 let tags = vec![ 166 vec!["e".to_string(), "root123".to_string()], 167 vec!["k".to_string(), "1".to_string()], 168 vec!["p".to_string(), "a".repeat(64)], 169 ]; 170 let reaction = parse_reaction_from_tags(&tags, "+").expect("parse reaction"); 171 assert_eq!(reaction.root.id, "root123"); 172 assert_eq!(reaction.root.kind, 1); 173 } 174 175 #[test] 176 fn reaction_parses_address_reference() { 177 let pubkey = "b".repeat(64); 178 let addr = format!("30023:{}:dtag", pubkey); 179 let tags = vec![ 180 vec!["e".to_string(), "root123".to_string()], 181 vec!["a".to_string(), addr.clone()], 182 vec!["k".to_string(), "30023".to_string()], 183 vec!["p".to_string(), pubkey.clone()], 184 ]; 185 let reaction = parse_reaction_from_tags(&tags, "+").expect("parse reaction"); 186 assert_eq!(reaction.root.kind, 30023); 187 assert_eq!(reaction.root.author, pubkey); 188 assert_eq!(reaction.root.d_tag.as_deref(), Some("dtag")); 189 } 190 }