tangle_indexer


git clone https://radroots.dev/git/tangle_indexer.git
Log | Files | Refs | Submodules | LICENSE

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 }