encode.rs (8574B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 format, 4 string::{String, ToString}, 5 vec, 6 vec::Vec, 7 }; 8 9 use radroots_events::{ 10 kinds::{KIND_FARM, KIND_POST}, 11 post::RadrootsPost, 12 social::{RadrootsSocialFarmAnchor, RadrootsSocialMediaMetadata, RadrootsSocialTarget}, 13 tags::{TAG_A, TAG_IMETA, TAG_Q, TAG_T}, 14 }; 15 16 use crate::error::EventEncodeError; 17 use crate::field_helpers::{parse_address_tag, validate_lowercase_hex_64}; 18 use crate::social_helpers::{dimensions_tag, push_location_tags}; 19 use crate::wire::WireEventParts; 20 21 const DEFAULT_KIND: u32 = KIND_POST; 22 23 pub fn post_build_tags(post: &RadrootsPost) -> Result<Vec<Vec<String>>, EventEncodeError> { 24 let mut tags = Vec::new(); 25 if let Some(farm) = post.farm.as_ref() { 26 push_farm_anchor(&mut tags, farm)?; 27 } 28 if let Some(refs) = post.address_refs.as_ref() { 29 for target in refs { 30 push_address_ref(&mut tags, target)?; 31 } 32 } 33 if let Some(location) = post.location.as_ref() { 34 push_location_tags(&mut tags, location); 35 } 36 if let Some(topics) = post.topics.as_ref() { 37 for topic in topics { 38 if !topic.trim().is_empty() { 39 tags.push(vec![TAG_T.to_string(), topic.clone()]); 40 } 41 } 42 } 43 if let Some(quote_refs) = post.quote_refs.as_ref() { 44 for target in quote_refs { 45 push_quote_ref(&mut tags, target)?; 46 } 47 } 48 if let Some(media) = post.media.as_ref() { 49 for item in media { 50 push_media_tags(&mut tags, item)?; 51 } 52 } 53 Ok(tags) 54 } 55 56 pub fn to_wire_parts(post: &RadrootsPost) -> Result<WireEventParts, EventEncodeError> { 57 to_wire_parts_with_kind(post, DEFAULT_KIND) 58 } 59 60 pub fn to_wire_parts_with_kind( 61 post: &RadrootsPost, 62 kind: u32, 63 ) -> Result<WireEventParts, EventEncodeError> { 64 if kind != DEFAULT_KIND { 65 return Err(EventEncodeError::InvalidKind(kind)); 66 } 67 if post.content.trim().is_empty() { 68 return Err(EventEncodeError::EmptyRequiredField("content")); 69 } 70 let tags = post_build_tags(post)?; 71 Ok(WireEventParts { 72 kind, 73 content: post.content.clone(), 74 tags, 75 }) 76 } 77 78 fn push_farm_anchor( 79 tags: &mut Vec<Vec<String>>, 80 farm: &RadrootsSocialFarmAnchor, 81 ) -> Result<(), EventEncodeError> { 82 if farm.farm.pubkey.trim().is_empty() { 83 return Err(EventEncodeError::EmptyRequiredField("farm.pubkey")); 84 } 85 if farm.farm.d_tag.trim().is_empty() { 86 return Err(EventEncodeError::EmptyRequiredField("farm.d_tag")); 87 } 88 let address = format!("{}:{}:{}", KIND_FARM, farm.farm.pubkey, farm.farm.d_tag); 89 parse_address_tag(&address, "farm").map_err(|_| EventEncodeError::InvalidField("farm"))?; 90 let mut tag = Vec::with_capacity(2 + farm.relays.as_ref().map_or(0, Vec::len)); 91 tag.push(TAG_A.to_string()); 92 tag.push(address); 93 if let Some(relays) = farm.relays.as_ref() { 94 tag.extend(relays.iter().cloned()); 95 } 96 tags.push(tag); 97 Ok(()) 98 } 99 100 fn push_address_ref( 101 tags: &mut Vec<Vec<String>>, 102 target: &RadrootsSocialTarget, 103 ) -> Result<(), EventEncodeError> { 104 let RadrootsSocialTarget::Address { 105 address, 106 author, 107 event_kind, 108 relays, 109 } = target 110 else { 111 return Err(EventEncodeError::InvalidField("address_refs")); 112 }; 113 let parsed = parse_address_tag(address, "address_refs") 114 .map_err(|_| EventEncodeError::InvalidField("address_refs"))?; 115 if parsed.kind == KIND_FARM { 116 return Err(EventEncodeError::InvalidField("address_refs")); 117 } 118 if let Some(kind) = event_kind 119 && *kind != parsed.kind 120 { 121 return Err(EventEncodeError::InvalidField("address_refs")); 122 } 123 if let Some(author) = author.as_deref() 124 && author != parsed.pubkey 125 { 126 return Err(EventEncodeError::InvalidField("address_refs")); 127 } 128 let mut tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); 129 tag.push(TAG_A.to_string()); 130 tag.push(format!( 131 "{}:{}:{}", 132 parsed.kind, parsed.pubkey, parsed.d_tag 133 )); 134 if let Some(relays) = relays { 135 tag.extend(relays.iter().cloned()); 136 } 137 tags.push(tag); 138 Ok(()) 139 } 140 141 fn push_quote_ref( 142 tags: &mut Vec<Vec<String>>, 143 target: &RadrootsSocialTarget, 144 ) -> Result<(), EventEncodeError> { 145 match target { 146 RadrootsSocialTarget::Event { id, relays, .. } => { 147 validate_lowercase_hex_64(id, "quote_refs")?; 148 let mut tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); 149 tag.push(TAG_Q.to_string()); 150 tag.push(id.clone()); 151 if let Some(relays) = relays { 152 tag.extend(relays.iter().cloned()); 153 } 154 tags.push(tag); 155 Ok(()) 156 } 157 RadrootsSocialTarget::Address { 158 address, 159 event_kind, 160 relays, 161 .. 162 } => { 163 let parsed = parse_address_tag(address, "quote_refs") 164 .map_err(|_| EventEncodeError::InvalidField("quote_refs"))?; 165 if let Some(kind) = event_kind 166 && *kind != parsed.kind 167 { 168 return Err(EventEncodeError::InvalidField("quote_refs")); 169 } 170 let mut tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len)); 171 tag.push(TAG_Q.to_string()); 172 tag.push(format!( 173 "{}:{}:{}", 174 parsed.kind, parsed.pubkey, parsed.d_tag 175 )); 176 if let Some(relays) = relays { 177 tag.extend(relays.iter().cloned()); 178 } 179 tags.push(tag); 180 Ok(()) 181 } 182 RadrootsSocialTarget::External { .. } => Err(EventEncodeError::InvalidField("quote_refs")), 183 } 184 } 185 186 fn push_media_tags( 187 tags: &mut Vec<Vec<String>>, 188 media: &RadrootsSocialMediaMetadata, 189 ) -> Result<(), EventEncodeError> { 190 if let Some(raw_tags) = media.imeta.as_ref() { 191 for raw in raw_tags { 192 if raw.is_empty() || raw.iter().any(|value| value.trim().is_empty()) { 193 return Err(EventEncodeError::InvalidField("imeta")); 194 } 195 let mut tag = Vec::with_capacity(1 + raw.len()); 196 tag.push(TAG_IMETA.to_string()); 197 tag.extend(raw.iter().cloned()); 198 tags.push(tag); 199 } 200 return Ok(()); 201 } 202 203 let mut fields = Vec::new(); 204 push_imeta_field(&mut fields, "url", media.url.as_deref()); 205 push_imeta_field(&mut fields, "m", media.mime_type.as_deref()); 206 push_imeta_field(&mut fields, "x", media.sha256.as_deref()); 207 push_imeta_field(&mut fields, "ox", media.original_sha256.as_deref()); 208 if let Some(size) = media.size { 209 fields.push(format!("size {size}")); 210 } 211 if let Some(dimensions) = media.dimensions.as_ref() { 212 fields.push(format!("dim {}", dimensions_tag(dimensions))); 213 } 214 push_imeta_field(&mut fields, "blurhash", media.blurhash.as_deref()); 215 if let Some(thumbnails) = media.thumbnails.as_ref() { 216 for thumbnail in thumbnails { 217 if thumbnail.url.trim().is_empty() { 218 return Err(EventEncodeError::InvalidField("imeta")); 219 } 220 fields.push(format!("thumb {}", thumbnail.url)); 221 if let Some(dimensions) = thumbnail.dimensions.as_ref() { 222 fields.push(format!("dim {}", dimensions_tag(dimensions))); 223 } 224 } 225 } 226 push_imeta_field(&mut fields, "image", media.image.as_deref()); 227 push_imeta_field(&mut fields, "summary", media.summary.as_deref()); 228 push_imeta_field(&mut fields, "alt", media.alt.as_deref()); 229 push_imeta_field(&mut fields, "fallback", media.fallback.as_deref()); 230 push_imeta_field(&mut fields, "magnet", media.magnet.as_deref()); 231 if let Some(values) = media.content_hashes.as_ref() { 232 for value in values { 233 push_imeta_field(&mut fields, "i", Some(value.as_str())); 234 } 235 } 236 if let Some(values) = media.services.as_ref() { 237 for value in values { 238 push_imeta_field(&mut fields, "service", Some(value.as_str())); 239 } 240 } 241 if !fields.is_empty() { 242 let mut tag = Vec::with_capacity(1 + fields.len()); 243 tag.push(TAG_IMETA.to_string()); 244 tag.extend(fields); 245 tags.push(tag); 246 } 247 Ok(()) 248 } 249 250 fn push_imeta_field(fields: &mut Vec<String>, key: &str, value: Option<&str>) { 251 if let Some(value) = value.filter(|value| !value.trim().is_empty()) { 252 fields.push(format!("{key} {value}")); 253 } 254 }