lib

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

decode.rs (5824B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     string::{String, ToString},
      4     vec::Vec,
      5 };
      6 
      7 use radroots_events::{
      8     RadrootsNostrEvent,
      9     kinds::KIND_MESSAGE_FILE,
     10     message_file::{RadrootsMessageFile, RadrootsMessageFileDimensions},
     11 };
     12 
     13 use crate::error::EventParseError;
     14 use crate::message::tags::{parse_recipients, parse_reply_tag, parse_subject_tag};
     15 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     16 
     17 const DEFAULT_KIND: u32 = KIND_MESSAGE_FILE;
     18 
     19 fn required_tag_value(tags: &[Vec<String>], key: &'static str) -> Result<String, EventParseError> {
     20     let value = tags
     21         .iter()
     22         .find(|t| t.first().map(|s| s.as_str()) == Some(key))
     23         .and_then(|t| t.get(1));
     24     let value = value.ok_or(EventParseError::MissingTag(key))?;
     25     if value.trim().is_empty() {
     26         return Err(EventParseError::InvalidTag(key));
     27     }
     28     Ok(value.clone())
     29 }
     30 
     31 fn optional_tag_value(
     32     tags: &[Vec<String>],
     33     key: &'static str,
     34 ) -> Result<Option<String>, EventParseError> {
     35     let value = tags
     36         .iter()
     37         .find(|t| t.first().map(|s| s.as_str()) == Some(key))
     38         .and_then(|t| t.get(1));
     39     match value {
     40         Some(value) if value.trim().is_empty() => Err(EventParseError::InvalidTag(key)),
     41         Some(value) => Ok(Some(value.clone())),
     42         None => Ok(None),
     43     }
     44 }
     45 
     46 fn parse_dimensions(value: &str) -> Result<RadrootsMessageFileDimensions, EventParseError> {
     47     let (w, h) = value
     48         .split_once('x')
     49         .ok_or(EventParseError::InvalidTag("dim"))?;
     50     let w = w
     51         .parse::<u32>()
     52         .map_err(|_| EventParseError::InvalidTag("dim"))?;
     53     let h = h
     54         .parse::<u32>()
     55         .map_err(|_| EventParseError::InvalidTag("dim"))?;
     56     Ok(RadrootsMessageFileDimensions { w, h })
     57 }
     58 
     59 fn parse_size(tags: &[Vec<String>]) -> Result<Option<u64>, EventParseError> {
     60     let value = tags
     61         .iter()
     62         .find(|t| t.first().map(|s| s.as_str()) == Some("size"))
     63         .and_then(|t| t.get(1));
     64     let Some(value) = value else {
     65         return Ok(None);
     66     };
     67     if value.trim().is_empty() {
     68         return Err(EventParseError::InvalidTag("size"));
     69     }
     70     let size = value
     71         .parse::<u64>()
     72         .map_err(|e| EventParseError::InvalidNumber("size", e))?;
     73     Ok(Some(size))
     74 }
     75 
     76 fn parse_dimensions_tag(
     77     tags: &[Vec<String>],
     78 ) -> Result<Option<RadrootsMessageFileDimensions>, EventParseError> {
     79     let value = tags
     80         .iter()
     81         .find(|t| t.first().map(|s| s.as_str()) == Some("dim"))
     82         .and_then(|t| t.get(1));
     83     let Some(value) = value else {
     84         return Ok(None);
     85     };
     86     if value.trim().is_empty() {
     87         return Err(EventParseError::InvalidTag("dim"));
     88     }
     89     Ok(Some(parse_dimensions(value)?))
     90 }
     91 
     92 fn parse_fallbacks(tags: &[Vec<String>]) -> Result<Vec<String>, EventParseError> {
     93     let mut fallbacks = Vec::new();
     94     for tag in tags
     95         .iter()
     96         .filter(|t| t.first().map(|s| s.as_str()) == Some("fallback"))
     97     {
     98         let value = tag.get(1).ok_or(EventParseError::InvalidTag("fallback"))?;
     99         if value.trim().is_empty() {
    100             return Err(EventParseError::InvalidTag("fallback"));
    101         }
    102         fallbacks.push(value.clone());
    103     }
    104     Ok(fallbacks)
    105 }
    106 
    107 pub fn message_file_from_tags(
    108     kind: u32,
    109     tags: &[Vec<String>],
    110     content: &str,
    111 ) -> Result<RadrootsMessageFile, EventParseError> {
    112     if kind != DEFAULT_KIND {
    113         return Err(EventParseError::InvalidKind {
    114             expected: "15",
    115             got: kind,
    116         });
    117     }
    118     if content.trim().is_empty() {
    119         return Err(EventParseError::InvalidTag("content"));
    120     }
    121 
    122     let recipients = parse_recipients(tags)?;
    123     let reply_to = parse_reply_tag(tags)?;
    124     let subject = parse_subject_tag(tags)?;
    125     let file_type = required_tag_value(tags, "file-type")?;
    126     let encryption_algorithm = required_tag_value(tags, "encryption-algorithm")?;
    127     let decryption_key = required_tag_value(tags, "decryption-key")?;
    128     let decryption_nonce = required_tag_value(tags, "decryption-nonce")?;
    129     let encrypted_hash = required_tag_value(tags, "x")?;
    130     let original_hash = optional_tag_value(tags, "ox")?;
    131     let size = parse_size(tags)?;
    132     let dimensions = parse_dimensions_tag(tags)?;
    133     let blurhash = optional_tag_value(tags, "blurhash")?;
    134     let thumb = optional_tag_value(tags, "thumb")?;
    135     let fallbacks = parse_fallbacks(tags)?;
    136 
    137     Ok(RadrootsMessageFile {
    138         recipients,
    139         file_url: content.to_string(),
    140         reply_to,
    141         subject,
    142         file_type,
    143         encryption_algorithm,
    144         decryption_key,
    145         decryption_nonce,
    146         encrypted_hash,
    147         original_hash,
    148         size,
    149         dimensions,
    150         blurhash,
    151         thumb,
    152         fallbacks,
    153     })
    154 }
    155 
    156 pub fn data_from_event(
    157     id: String,
    158     author: String,
    159     published_at: u32,
    160     kind: u32,
    161     content: String,
    162     tags: Vec<Vec<String>>,
    163 ) -> Result<RadrootsParsedData<RadrootsMessageFile>, EventParseError> {
    164     let message_file = message_file_from_tags(kind, &tags, &content)?;
    165     Ok(RadrootsParsedData::new(
    166         id,
    167         author,
    168         published_at,
    169         kind,
    170         message_file,
    171     ))
    172 }
    173 
    174 pub fn parsed_from_event(
    175     id: String,
    176     author: String,
    177     published_at: u32,
    178     kind: u32,
    179     content: String,
    180     tags: Vec<Vec<String>>,
    181     sig: String,
    182 ) -> Result<RadrootsParsedEvent<RadrootsMessageFile>, EventParseError> {
    183     let data = data_from_event(
    184         id.clone(),
    185         author.clone(),
    186         published_at,
    187         kind,
    188         content.clone(),
    189         tags.clone(),
    190     )?;
    191     Ok(RadrootsParsedEvent {
    192         event: RadrootsNostrEvent {
    193             id,
    194             author,
    195             created_at: published_at,
    196             kind,
    197             content,
    198             tags,
    199             sig,
    200         },
    201         data,
    202     })
    203 }