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