lib

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

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 }