lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

decode.rs (6170B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{string::ToString, vec::Vec};
      3 
      4 use radroots_events::{
      5     RadrootsNostrEvent,
      6     kinds::KIND_REPORT,
      7     report::RadrootsReport,
      8     social::{RadrootsReportFileTarget, RadrootsReportType, RadrootsSocialTarget},
      9     tags::{TAG_A, TAG_E, TAG_MAGNET, TAG_P, TAG_SERVER, TAG_SHA256},
     10 };
     11 
     12 use crate::error::EventParseError;
     13 use crate::field_helpers::{parse_address_tag, required_tag_value, validate_lowercase_hex_64_tag};
     14 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     15 use crate::social_helpers::first_tag_value;
     16 
     17 pub fn report_from_event(
     18     kind: u32,
     19     tags: &[Vec<String>],
     20     content: &str,
     21 ) -> Result<RadrootsReport, EventParseError> {
     22     if kind != KIND_REPORT {
     23         return Err(EventParseError::InvalidKind {
     24             expected: "1984",
     25             got: kind,
     26         });
     27     }
     28     let p_tag = find_tag(tags, TAG_P).ok_or(EventParseError::MissingTag(TAG_P))?;
     29     let reported_pubkey = required_tag_value(tags, TAG_P)?;
     30     validate_lowercase_hex_64_tag(&reported_pubkey, TAG_P)?;
     31     let report_type = parse_report_type(
     32         p_tag
     33             .get(2)
     34             .map(|value| value.as_str())
     35             .ok_or(EventParseError::InvalidTag(TAG_P))?,
     36         TAG_P,
     37     )?;
     38     let event = parse_event_target(tags, &report_type)?;
     39     let file = parse_file_target(tags, &report_type)?;
     40     Ok(RadrootsReport {
     41         reported_pubkey,
     42         report_type,
     43         event,
     44         file,
     45         content: if content.is_empty() {
     46             None
     47         } else {
     48             Some(content.to_string())
     49         },
     50     })
     51 }
     52 
     53 pub fn data_from_event(
     54     id: String,
     55     author: String,
     56     published_at: u32,
     57     kind: u32,
     58     content: String,
     59     tags: Vec<Vec<String>>,
     60 ) -> Result<RadrootsParsedData<RadrootsReport>, EventParseError> {
     61     let report = report_from_event(kind, &tags, &content)?;
     62     Ok(RadrootsParsedData::new(
     63         id,
     64         author,
     65         published_at,
     66         kind,
     67         report,
     68     ))
     69 }
     70 
     71 pub fn parsed_from_event(
     72     id: String,
     73     author: String,
     74     published_at: u32,
     75     kind: u32,
     76     content: String,
     77     tags: Vec<Vec<String>>,
     78     sig: String,
     79 ) -> Result<RadrootsParsedEvent<RadrootsReport>, EventParseError> {
     80     let data = data_from_event(
     81         id.clone(),
     82         author.clone(),
     83         published_at,
     84         kind,
     85         content.clone(),
     86         tags.clone(),
     87     )?;
     88     Ok(RadrootsParsedEvent {
     89         event: RadrootsNostrEvent {
     90             id,
     91             author,
     92             created_at: published_at,
     93             kind,
     94             content,
     95             tags,
     96             sig,
     97         },
     98         data,
     99     })
    100 }
    101 
    102 fn parse_event_target(
    103     tags: &[Vec<String>],
    104     report_type: &RadrootsReportType,
    105 ) -> Result<Option<RadrootsSocialTarget>, EventParseError> {
    106     if let Some(tag) = find_tag(tags, TAG_A) {
    107         validate_optional_target_report_type(tag, 2, report_type, TAG_A)?;
    108         let value = tag
    109             .get(1)
    110             .cloned()
    111             .ok_or(EventParseError::InvalidTag(TAG_A))?;
    112         let address = parse_address_tag(&value, TAG_A)?;
    113         return Ok(Some(RadrootsSocialTarget::Address {
    114             address: value,
    115             author: Some(address.pubkey),
    116             event_kind: Some(address.kind),
    117             relays: relays_from_tag(tag, 3),
    118         }));
    119     }
    120     if let Some(tag) = find_tag(tags, TAG_E) {
    121         validate_optional_target_report_type(tag, 2, report_type, TAG_E)?;
    122         let id = tag
    123             .get(1)
    124             .cloned()
    125             .ok_or(EventParseError::InvalidTag(TAG_E))?;
    126         validate_lowercase_hex_64_tag(&id, TAG_E)?;
    127         return Ok(Some(RadrootsSocialTarget::Event {
    128             id,
    129             author: first_tag_value(tags, TAG_P),
    130             event_kind: None,
    131             relays: relays_from_tag(tag, 3),
    132         }));
    133     }
    134     Ok(None)
    135 }
    136 
    137 fn parse_file_target(
    138     tags: &[Vec<String>],
    139     report_type: &RadrootsReportType,
    140 ) -> Result<Option<RadrootsReportFileTarget>, EventParseError> {
    141     let sha256 = if let Some(tag) = find_tag(tags, TAG_SHA256) {
    142         validate_optional_target_report_type(tag, 2, report_type, TAG_SHA256)?;
    143         let value = tag
    144             .get(1)
    145             .cloned()
    146             .ok_or(EventParseError::InvalidTag(TAG_SHA256))?;
    147         validate_lowercase_hex_64_tag(&value, TAG_SHA256)?;
    148         Some(value)
    149     } else {
    150         None
    151     };
    152     let url = first_tag_value(tags, TAG_SERVER);
    153     let magnet = first_tag_value(tags, TAG_MAGNET);
    154     if sha256.is_none() && url.is_none() && magnet.is_none() {
    155         Ok(None)
    156     } else {
    157         Ok(Some(RadrootsReportFileTarget {
    158             sha256,
    159             url,
    160             magnet,
    161         }))
    162     }
    163 }
    164 
    165 fn validate_optional_target_report_type(
    166     tag: &[String],
    167     index: usize,
    168     expected: &RadrootsReportType,
    169     tag_name: &'static str,
    170 ) -> Result<(), EventParseError> {
    171     let Some(value) = tag.get(index) else {
    172         return Ok(());
    173     };
    174     if parse_report_type(value, tag_name)? == *expected {
    175         Ok(())
    176     } else {
    177         Err(EventParseError::InvalidTag(tag_name))
    178     }
    179 }
    180 
    181 fn parse_report_type(
    182     value: &str,
    183     tag_name: &'static str,
    184 ) -> Result<RadrootsReportType, EventParseError> {
    185     match value {
    186         "nudity" => Ok(RadrootsReportType::Nudity),
    187         "malware" => Ok(RadrootsReportType::Malware),
    188         "profanity" => Ok(RadrootsReportType::Profanity),
    189         "illegal" => Ok(RadrootsReportType::Illegal),
    190         "spam" => Ok(RadrootsReportType::Spam),
    191         "impersonation" => Ok(RadrootsReportType::Impersonation),
    192         "other" => Ok(RadrootsReportType::Other),
    193         _ => Err(EventParseError::InvalidTag(tag_name)),
    194     }
    195 }
    196 
    197 fn find_tag<'a>(tags: &'a [Vec<String>], key: &'static str) -> Option<&'a Vec<String>> {
    198     tags.iter()
    199         .find(|tag| tag.first().map(|value| value.as_str()) == Some(key))
    200 }
    201 
    202 fn relays_from_tag(tag: &[String], start: usize) -> Option<Vec<String>> {
    203     let relays = tag
    204         .iter()
    205         .skip(start)
    206         .filter(|value| !value.trim().is_empty())
    207         .cloned()
    208         .collect::<Vec<_>>();
    209     if relays.is_empty() {
    210         None
    211     } else {
    212         Some(relays)
    213     }
    214 }