decode.rs (5838B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 string::{String, ToString}, 4 vec::Vec, 5 }; 6 7 use radroots_event::{ 8 envelope::kind::KIND_REPORT, 9 post::report::Report, 10 social::{ReportFileTarget, ReportType, SocialTarget}, 11 tag::name::{TAG_A, TAG_E, TAG_MAGNET, TAG_P, TAG_SERVER, TAG_SHA256}, 12 }; 13 14 use crate::error::EventParseError; 15 use crate::field_helpers::{parse_address_tag, required_tag_value, validate_lowercase_hex_64_tag}; 16 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent}; 17 use crate::social_helpers::first_tag_value; 18 19 pub fn report_from_event( 20 kind: u32, 21 tags: &[Vec<String>], 22 content: &str, 23 ) -> Result<Report, EventParseError> { 24 if kind != KIND_REPORT { 25 return Err(EventParseError::InvalidKind { 26 expected: "1984", 27 got: kind, 28 }); 29 } 30 let p_tag = find_tag(tags, TAG_P).ok_or(EventParseError::MissingTag(TAG_P))?; 31 let reported_pubkey = required_tag_value(tags, TAG_P)?; 32 validate_lowercase_hex_64_tag(&reported_pubkey, TAG_P)?; 33 let report_type = parse_report_type( 34 p_tag 35 .get(2) 36 .map(|value| value.as_str()) 37 .ok_or(EventParseError::InvalidTag(TAG_P))?, 38 TAG_P, 39 )?; 40 let event = parse_event_target(tags, &report_type)?; 41 let file = parse_file_target(tags, &report_type)?; 42 Ok(Report { 43 reported_pubkey, 44 report_type, 45 event, 46 file, 47 content: if content.is_empty() { 48 None 49 } else { 50 Some(content.to_string()) 51 }, 52 }) 53 } 54 55 pub fn data_from_event( 56 id: String, 57 author: String, 58 published_at: u64, 59 kind: u32, 60 content: String, 61 tags: Vec<Vec<String>>, 62 ) -> Result<RadrootsParsedData<Report>, EventParseError> { 63 let report = report_from_event(kind, &tags, &content)?; 64 Ok(RadrootsParsedData::new( 65 id, 66 author, 67 published_at, 68 kind, 69 report, 70 )) 71 } 72 73 pub fn parsed_from_event( 74 id: String, 75 author: String, 76 published_at: u64, 77 kind: u32, 78 content: String, 79 tags: Vec<Vec<String>>, 80 sig: String, 81 ) -> Result<RadrootsParsedEvent<Report>, EventParseError> { 82 let data = data_from_event( 83 id.clone(), 84 author.clone(), 85 published_at, 86 kind, 87 content.clone(), 88 tags.clone(), 89 )?; 90 RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data) 91 } 92 93 fn parse_event_target( 94 tags: &[Vec<String>], 95 report_type: &ReportType, 96 ) -> Result<Option<SocialTarget>, EventParseError> { 97 if let Some(tag) = find_tag(tags, TAG_A) { 98 validate_optional_target_report_type(tag, 2, report_type, TAG_A)?; 99 let value = tag 100 .get(1) 101 .cloned() 102 .ok_or(EventParseError::InvalidTag(TAG_A))?; 103 let address = parse_address_tag(&value, TAG_A)?; 104 return Ok(Some(SocialTarget::Address { 105 address: value, 106 author: Some(address.pubkey), 107 event_kind: Some(address.kind), 108 relays: relays_from_tag(tag, 3), 109 })); 110 } 111 if let Some(tag) = find_tag(tags, TAG_E) { 112 validate_optional_target_report_type(tag, 2, report_type, TAG_E)?; 113 let id = tag 114 .get(1) 115 .cloned() 116 .ok_or(EventParseError::InvalidTag(TAG_E))?; 117 validate_lowercase_hex_64_tag(&id, TAG_E)?; 118 return Ok(Some(SocialTarget::Event { 119 id, 120 author: first_tag_value(tags, TAG_P), 121 event_kind: None, 122 relays: relays_from_tag(tag, 3), 123 })); 124 } 125 Ok(None) 126 } 127 128 fn parse_file_target( 129 tags: &[Vec<String>], 130 report_type: &ReportType, 131 ) -> Result<Option<ReportFileTarget>, EventParseError> { 132 let sha256 = if let Some(tag) = find_tag(tags, TAG_SHA256) { 133 validate_optional_target_report_type(tag, 2, report_type, TAG_SHA256)?; 134 let value = tag 135 .get(1) 136 .cloned() 137 .ok_or(EventParseError::InvalidTag(TAG_SHA256))?; 138 validate_lowercase_hex_64_tag(&value, TAG_SHA256)?; 139 Some(value) 140 } else { 141 None 142 }; 143 let url = first_tag_value(tags, TAG_SERVER); 144 let magnet = first_tag_value(tags, TAG_MAGNET); 145 if sha256.is_none() && url.is_none() && magnet.is_none() { 146 Ok(None) 147 } else { 148 Ok(Some(ReportFileTarget { 149 sha256, 150 url, 151 magnet, 152 })) 153 } 154 } 155 156 fn validate_optional_target_report_type( 157 tag: &[String], 158 index: usize, 159 expected: &ReportType, 160 tag_name: &'static str, 161 ) -> Result<(), EventParseError> { 162 let Some(value) = tag.get(index) else { 163 return Ok(()); 164 }; 165 if parse_report_type(value, tag_name)? == *expected { 166 Ok(()) 167 } else { 168 Err(EventParseError::InvalidTag(tag_name)) 169 } 170 } 171 172 fn parse_report_type(value: &str, tag_name: &'static str) -> Result<ReportType, EventParseError> { 173 match value { 174 "nudity" => Ok(ReportType::Nudity), 175 "malware" => Ok(ReportType::Malware), 176 "profanity" => Ok(ReportType::Profanity), 177 "illegal" => Ok(ReportType::Illegal), 178 "spam" => Ok(ReportType::Spam), 179 "impersonation" => Ok(ReportType::Impersonation), 180 "other" => Ok(ReportType::Other), 181 _ => Err(EventParseError::InvalidTag(tag_name)), 182 } 183 } 184 185 fn find_tag<'a>(tags: &'a [Vec<String>], key: &'static str) -> Option<&'a Vec<String>> { 186 tags.iter() 187 .find(|tag| tag.first().map(|value| value.as_str()) == Some(key)) 188 } 189 190 fn relays_from_tag(tag: &[String], start: usize) -> Option<Vec<String>> { 191 let relays = tag 192 .iter() 193 .skip(start) 194 .filter(|value| !value.trim().is_empty()) 195 .cloned() 196 .collect::<Vec<_>>(); 197 if relays.is_empty() { 198 None 199 } else { 200 Some(relays) 201 } 202 }