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 }