comment.rs (12095B)
1 use crate::relay::event::RelayIndexerEvent; 2 use radroots_events::{ 3 comment::{RadrootsComment, RadrootsCommentEventIndex, RadrootsCommentEventMetadata}, 4 RadrootsNostrEvent, RadrootsNostrEventRef, 5 }; 6 use thiserror::Error; 7 8 #[derive(Debug, Error)] 9 pub enum RadrootsCommentEventIndexError { 10 #[error("Failed to parse comment from tags")] 11 ParseError, 12 } 13 14 fn parse_address(addr: &str) -> Option<(u32, String, Option<String>)> { 15 let mut parts = addr.splitn(3, ':'); 16 let kind = parts.next()?.parse::<u32>().ok()?; 17 let author = parts.next()?.to_lowercase(); 18 let d_tag = parts 19 .next() 20 .and_then(|d| if d.is_empty() { None } else { Some(d.to_string()) }); 21 Some((kind, author, d_tag)) 22 } 23 24 fn parse_comment_from_tags( 25 tags: &[Vec<String>], 26 content: &str, 27 ) -> Result<RadrootsComment, RadrootsCommentEventIndexError> { 28 let mut root_id: Option<String> = None; 29 let mut root_addr: Option<String> = None; 30 let mut root_relays_e: Option<Vec<String>> = None; 31 let mut root_relays_a: Option<Vec<String>> = None; 32 let mut root_kind_tag: Option<u32> = None; 33 let mut root_kind_addr: Option<u32> = None; 34 let mut root_author_tag: Option<String> = None; 35 let mut root_author_addr: Option<String> = None; 36 let mut root_author_e: Option<String> = None; 37 let mut root_d: Option<String> = None; 38 39 let mut parent_id: Option<String> = None; 40 let mut parent_addr: Option<String> = None; 41 let mut parent_relays_e: Option<Vec<String>> = None; 42 let mut parent_relays_a: Option<Vec<String>> = None; 43 let mut parent_kind_tag: Option<u32> = None; 44 let mut parent_kind_addr: Option<u32> = None; 45 let mut parent_author_tag: Option<String> = None; 46 let mut parent_author_addr: Option<String> = None; 47 let mut parent_author_e: Option<String> = None; 48 let mut parent_d: Option<String> = None; 49 50 let mut legacy_root_id: Option<String> = None; 51 let mut legacy_root_relays: Option<Vec<String>> = None; 52 let mut legacy_parent_id: Option<String> = None; 53 let mut legacy_parent_relays: Option<Vec<String>> = None; 54 let mut legacy_root_kind: Option<u32> = None; 55 let mut legacy_root_author: Option<String> = None; 56 let mut legacy_root_d: Option<String> = None; 57 58 for t in tags { 59 match t.first().map(|k| k.as_str()) { 60 Some("E") => { 61 if let Some(id) = t.get(1).cloned() { 62 root_id = Some(id); 63 } 64 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 65 root_relays_e = Some(vec![r]); 66 } 67 if let Some(pk) = t.get(3).filter(|s| !s.is_empty()) { 68 root_author_e = Some(pk.to_lowercase()); 69 } 70 } 71 Some("A") => { 72 if let Some(addr) = t.get(1).cloned() { 73 root_addr = Some(addr.clone()); 74 if let Some((kind, author, d_tag)) = parse_address(&addr) { 75 root_kind_addr = Some(kind); 76 root_author_addr = Some(author); 77 root_d = d_tag; 78 } 79 } 80 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 81 root_relays_a = Some(vec![r]); 82 } 83 } 84 Some("K") => { 85 if let Some(kind) = t.get(1).and_then(|v| v.parse::<u32>().ok()) { 86 root_kind_tag = Some(kind); 87 } 88 } 89 Some("P") => { 90 if let Some(pk) = t.get(1).filter(|s| !s.is_empty()) { 91 root_author_tag = Some(pk.to_lowercase()); 92 } 93 } 94 Some("e_root") => { 95 if let Some(id) = t.get(1).cloned() { 96 legacy_root_id = Some(id); 97 } 98 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 99 legacy_root_relays = Some(vec![r]); 100 } 101 } 102 Some("e_prev") => { 103 if let Some(id) = t.get(1).cloned() { 104 legacy_parent_id = Some(id); 105 } 106 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 107 legacy_parent_relays = Some(vec![r]); 108 } 109 } 110 Some("e") => { 111 if let Some(id) = t.get(1).cloned() { 112 parent_id = Some(id.clone()); 113 if legacy_root_id.is_none() { 114 legacy_root_id = Some(id); 115 } 116 } 117 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 118 parent_relays_e = Some(vec![r.clone()]); 119 if legacy_root_relays.is_none() { 120 legacy_root_relays = Some(vec![r]); 121 } 122 } 123 if let Some(pk) = t.get(3).filter(|s| !s.is_empty()) { 124 parent_author_e = Some(pk.to_lowercase()); 125 } 126 } 127 Some("a") => { 128 if let Some(addr) = t.get(1).cloned() { 129 parent_addr = Some(addr.clone()); 130 if let Some((kind, author, d_tag)) = parse_address(&addr) { 131 parent_kind_addr = Some(kind); 132 parent_author_addr = Some(author.clone()); 133 parent_d = d_tag.clone(); 134 if legacy_root_kind.is_none() { 135 legacy_root_kind = Some(kind); 136 } 137 if legacy_root_author.is_none() { 138 legacy_root_author = Some(author); 139 } 140 if legacy_root_d.is_none() { 141 legacy_root_d = d_tag; 142 } 143 } 144 } 145 if let Some(r) = t.get(2).filter(|s| !s.is_empty()).cloned() { 146 parent_relays_a = Some(vec![r]); 147 } 148 } 149 Some("k") => { 150 if let Some(kind) = t.get(1).and_then(|v| v.parse::<u32>().ok()) { 151 parent_kind_tag = Some(kind); 152 } 153 } 154 Some("p") => { 155 if let Some(pk) = t.get(1).filter(|s| !s.is_empty()) { 156 parent_author_tag = Some(pk.to_lowercase()); 157 } 158 } 159 _ => {} 160 } 161 } 162 163 let has_nip22_root = root_id.is_some() || root_addr.is_some(); 164 if !has_nip22_root { 165 if root_id.is_none() { 166 root_id = legacy_root_id; 167 if root_relays_e.is_none() { 168 root_relays_e = legacy_root_relays; 169 } 170 } 171 if root_kind_tag.is_none() { 172 root_kind_tag = legacy_root_kind; 173 } 174 if root_author_tag.is_none() { 175 root_author_tag = legacy_root_author; 176 } 177 if root_d.is_none() { 178 root_d = legacy_root_d; 179 } 180 if parent_id.is_none() && parent_addr.is_none() { 181 parent_id = legacy_parent_id; 182 if parent_relays_e.is_none() { 183 parent_relays_e = legacy_parent_relays; 184 } 185 } 186 } 187 188 let root_id = root_id.or(root_addr.clone()).ok_or(RadrootsCommentEventIndexError::ParseError)?; 189 let root_kind = root_kind_tag.or(root_kind_addr).unwrap_or(1); 190 let root_author = root_author_tag 191 .or(root_author_addr) 192 .or(root_author_e) 193 .unwrap_or_default(); 194 let root_relays = root_relays_e.or(root_relays_a); 195 196 let mut parent_id = parent_id.or(parent_addr.clone()); 197 let parent_kind = parent_kind_tag 198 .or(parent_kind_addr) 199 .unwrap_or(root_kind); 200 let parent_author = parent_author_tag 201 .or(parent_author_addr) 202 .or(parent_author_e) 203 .unwrap_or_else(|| root_author.clone()); 204 let parent_relays = parent_relays_e 205 .or(parent_relays_a) 206 .or_else(|| root_relays.clone()); 207 let parent_d = parent_d.or(root_d.clone()); 208 209 if parent_id.is_none() { 210 parent_id = Some(root_id.clone()); 211 } 212 213 let root = RadrootsNostrEventRef { 214 id: root_id, 215 author: root_author, 216 kind: root_kind, 217 d_tag: root_d, 218 relays: root_relays, 219 }; 220 221 let parent = RadrootsNostrEventRef { 222 id: parent_id.ok_or(RadrootsCommentEventIndexError::ParseError)?, 223 author: parent_author, 224 kind: parent_kind, 225 d_tag: parent_d, 226 relays: parent_relays, 227 }; 228 229 Ok(RadrootsComment { 230 root, 231 parent, 232 content: content.to_string(), 233 }) 234 } 235 236 fn create_radroots_comment_event_metadata( 237 id: String, 238 author: String, 239 published_at: u32, 240 kind: u32, 241 content: &str, 242 tags: &[Vec<String>], 243 ) -> Result<RadrootsCommentEventMetadata, RadrootsCommentEventIndexError> { 244 let comment = parse_comment_from_tags(tags, content)?; 245 Ok(RadrootsCommentEventMetadata { 246 id, 247 author, 248 published_at, 249 kind, 250 comment, 251 }) 252 } 253 254 pub trait ToRadrootsCommentEventIndex { 255 fn to_radroots_comment_event( 256 &self, 257 ) -> Result<RadrootsCommentEventIndex, RadrootsCommentEventIndexError>; 258 } 259 260 impl ToRadrootsCommentEventIndex for RelayIndexerEvent { 261 fn to_radroots_comment_event( 262 &self, 263 ) -> Result<RadrootsCommentEventIndex, RadrootsCommentEventIndexError> { 264 let kind_u32 = self.kind.as_u64() as u32; 265 let id = self.id.clone(); 266 let author = self.author.clone(); 267 let metadata = create_radroots_comment_event_metadata( 268 id.clone(), 269 author.clone(), 270 self.created_at, 271 kind_u32, 272 &self.content, 273 &self.tags, 274 )?; 275 Ok(RadrootsCommentEventIndex { 276 event: RadrootsNostrEvent { 277 id, 278 author, 279 created_at: self.created_at, 280 kind: kind_u32, 281 tags: self.tags.clone(), 282 content: self.content.clone(), 283 sig: self.sig.clone(), 284 }, 285 metadata, 286 }) 287 } 288 } 289 290 #[cfg(test)] 291 mod tests { 292 use super::parse_comment_from_tags; 293 294 #[test] 295 fn comment_parses_address_only_root_and_parent() { 296 let pubkey = "f".repeat(64); 297 let addr = format!("30023:{}:dtag", pubkey); 298 let tags = vec![ 299 vec!["A".to_string(), addr.clone()], 300 vec!["K".to_string(), "30023".to_string()], 301 vec!["a".to_string(), addr.clone()], 302 vec!["k".to_string(), "30023".to_string()], 303 ]; 304 305 let comment = parse_comment_from_tags(&tags, "hello").expect("parse comment"); 306 assert_eq!(comment.root.id, addr); 307 assert_eq!(comment.root.kind, 30023); 308 assert_eq!(comment.root.author, pubkey); 309 assert_eq!(comment.root.d_tag.as_deref(), Some("dtag")); 310 assert_eq!(comment.parent.id, comment.root.id); 311 assert_eq!(comment.parent.kind, 30023); 312 } 313 314 #[test] 315 fn comment_defaults_parent_to_root_when_missing() { 316 let pubkey = "e".repeat(64); 317 let addr = format!("30023:{}:root", pubkey); 318 let tags = vec![ 319 vec!["A".to_string(), addr.clone()], 320 vec!["K".to_string(), "30023".to_string()], 321 ]; 322 323 let comment = parse_comment_from_tags(&tags, "hello").expect("parse comment"); 324 assert_eq!(comment.parent.id, comment.root.id); 325 assert_eq!(comment.parent.kind, comment.root.kind); 326 assert_eq!(comment.parent.author, comment.root.author); 327 } 328 329 #[test] 330 fn comment_parses_legacy_root_and_parent() { 331 let tags = vec![ 332 vec!["e_root".to_string(), "root123".to_string()], 333 vec!["e_prev".to_string(), "parent456".to_string()], 334 ]; 335 336 let comment = parse_comment_from_tags(&tags, "hello").expect("parse comment"); 337 assert_eq!(comment.root.id, "root123"); 338 assert_eq!(comment.parent.id, "parent456"); 339 } 340 }