lib

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

decode.rs (5182B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{string::ToString, vec::Vec};
      3 
      4 use radroots_events::{
      5     RadrootsNostrEvent,
      6     file_metadata::RadrootsFileMetadata,
      7     kinds::KIND_PUBLIC_FILE_METADATA,
      8     social::RadrootsSocialMediaThumbnail,
      9     tags::{
     10         TAG_ALT, TAG_BLURHASH, TAG_DIMENSIONS, TAG_FALLBACK, TAG_MAGNET, TAG_MIME,
     11         TAG_ORIGINAL_SHA256, TAG_SERVICE, TAG_SHA256, TAG_SIZE, TAG_SUMMARY, TAG_THUMB, TAG_URL,
     12     },
     13 };
     14 
     15 use crate::error::EventParseError;
     16 use crate::field_helpers::{
     17     optional_tag_value, required_tag_value, tag_values, validate_lowercase_hex_64_tag,
     18 };
     19 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     20 use crate::social_helpers::{first_tag_value, parse_dimensions_tag};
     21 
     22 const EXPECTED_KIND: &str = "1063";
     23 const TAG_RADROOTS_OWNER_DOCUMENT: &str = "radroots:owner_document";
     24 
     25 pub fn file_metadata_from_event(
     26     kind: u32,
     27     tags: &[Vec<String>],
     28     content: &str,
     29 ) -> Result<RadrootsFileMetadata, EventParseError> {
     30     if kind != KIND_PUBLIC_FILE_METADATA {
     31         return Err(EventParseError::InvalidKind {
     32             expected: EXPECTED_KIND,
     33             got: kind,
     34         });
     35     }
     36     reject_private_farm_file_tags(tags)?;
     37     let url = required_tag_value(tags, TAG_URL)?;
     38     let mime_type = required_tag_value(tags, TAG_MIME)?;
     39     let sha256 = required_tag_value(tags, TAG_SHA256)?;
     40     validate_lowercase_hex_64_tag(&sha256, TAG_SHA256)?;
     41     let original_sha256 = optional_hash_tag(tags, TAG_ORIGINAL_SHA256)?;
     42     let size = optional_tag_value(tags, TAG_SIZE)?
     43         .map(|value| {
     44             value
     45                 .parse::<u64>()
     46                 .map_err(|err| EventParseError::InvalidNumber(TAG_SIZE, err))
     47         })
     48         .transpose()?;
     49     let dimensions = optional_tag_value(tags, TAG_DIMENSIONS)?
     50         .map(|value| parse_dimensions_tag(&value, TAG_DIMENSIONS))
     51         .transpose()?;
     52 
     53     Ok(RadrootsFileMetadata {
     54         url,
     55         mime_type,
     56         sha256,
     57         original_sha256,
     58         size,
     59         dimensions,
     60         blurhash: first_tag_value(tags, TAG_BLURHASH),
     61         thumbnails: parse_thumbnails(tags)?,
     62         summary: first_tag_value(tags, TAG_SUMMARY),
     63         alt: first_tag_value(tags, TAG_ALT),
     64         fallback: first_tag_value(tags, TAG_FALLBACK),
     65         magnet: first_tag_value(tags, TAG_MAGNET),
     66         content_hashes: non_empty_vec(tag_values(tags, "i")?),
     67         services: non_empty_vec(tag_values(tags, TAG_SERVICE)?),
     68         content: if content.is_empty() {
     69             None
     70         } else {
     71             Some(content.to_string())
     72         },
     73     })
     74 }
     75 
     76 fn reject_private_farm_file_tags(tags: &[Vec<String>]) -> Result<(), EventParseError> {
     77     if tags
     78         .iter()
     79         .any(|tag| tag.first().map(|value| value.as_str()) == Some(TAG_RADROOTS_OWNER_DOCUMENT))
     80     {
     81         Err(EventParseError::InvalidTag(TAG_RADROOTS_OWNER_DOCUMENT))
     82     } else {
     83         Ok(())
     84     }
     85 }
     86 
     87 pub fn data_from_event(
     88     id: String,
     89     author: String,
     90     published_at: u32,
     91     kind: u32,
     92     content: String,
     93     tags: Vec<Vec<String>>,
     94 ) -> Result<RadrootsParsedData<RadrootsFileMetadata>, EventParseError> {
     95     let metadata = file_metadata_from_event(kind, &tags, &content)?;
     96     Ok(RadrootsParsedData::new(
     97         id,
     98         author,
     99         published_at,
    100         kind,
    101         metadata,
    102     ))
    103 }
    104 
    105 pub fn parsed_from_event(
    106     id: String,
    107     author: String,
    108     published_at: u32,
    109     kind: u32,
    110     content: String,
    111     tags: Vec<Vec<String>>,
    112     sig: String,
    113 ) -> Result<RadrootsParsedEvent<RadrootsFileMetadata>, EventParseError> {
    114     let data = data_from_event(
    115         id.clone(),
    116         author.clone(),
    117         published_at,
    118         kind,
    119         content.clone(),
    120         tags.clone(),
    121     )?;
    122     Ok(RadrootsParsedEvent {
    123         event: RadrootsNostrEvent {
    124             id,
    125             author,
    126             created_at: published_at,
    127             kind,
    128             content,
    129             tags,
    130             sig,
    131         },
    132         data,
    133     })
    134 }
    135 
    136 fn optional_hash_tag(
    137     tags: &[Vec<String>],
    138     key: &'static str,
    139 ) -> Result<Option<String>, EventParseError> {
    140     let Some(value) = optional_tag_value(tags, key)? else {
    141         return Ok(None);
    142     };
    143     validate_lowercase_hex_64_tag(&value, key)?;
    144     Ok(Some(value))
    145 }
    146 
    147 fn parse_thumbnails(
    148     tags: &[Vec<String>],
    149 ) -> Result<Option<Vec<RadrootsSocialMediaThumbnail>>, EventParseError> {
    150     let thumbnails = tags
    151         .iter()
    152         .filter(|tag| tag.first().map(|value| value.as_str()) == Some(TAG_THUMB))
    153         .map(|tag| {
    154             let url = tag
    155                 .get(1)
    156                 .cloned()
    157                 .ok_or(EventParseError::InvalidTag(TAG_THUMB))?;
    158             let dimensions = tag
    159                 .get(2)
    160                 .filter(|value| !value.trim().is_empty())
    161                 .map(|value| parse_dimensions_tag(value, TAG_THUMB))
    162                 .transpose()?;
    163             Ok(RadrootsSocialMediaThumbnail { url, dimensions })
    164         })
    165         .collect::<Result<Vec<_>, EventParseError>>()?;
    166     Ok(non_empty_vec(thumbnails))
    167 }
    168 
    169 fn non_empty_vec<T>(values: Vec<T>) -> Option<Vec<T>> {
    170     if values.is_empty() {
    171         None
    172     } else {
    173         Some(values)
    174     }
    175 }