Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
75
fork

Configure Feed

Select the types of activity you want to include in your feed.

walk record json and get links

phil f670a5d7 212ff2ee

+156 -4
+19 -4
src/lib.rs
··· 2 2 3 3 pub mod at_uri; 4 4 pub mod did; 5 + pub mod record; 6 + 7 + pub use record::collect_links; 5 8 6 9 #[derive(Debug, PartialEq)] 7 10 pub enum Link { ··· 10 13 Did(String), 11 14 } 12 15 16 + impl Link { 17 + pub fn into_string(self) -> String { 18 + match self { 19 + Link::AtUri(s) => s, 20 + Link::Uri(s) => s, 21 + Link::Did(s) => s, 22 + } 23 + } 24 + } 25 + 13 26 // normalizing is a bit opinionated but eh 14 27 pub fn parse_uri(s: &str) -> Option<String> { 15 28 Uri::parse(s).map(|u| u.normalize().into_string()).ok() 16 29 } 17 30 18 - pub fn parse_any(s: &str) -> Option<Link> { 31 + pub fn parse_any_link(s: &str) -> Option<Link> { 19 32 at_uri::parse_at_uri(s).map(Link::AtUri).or_else(|| { 20 33 did::parse_did(s) 21 34 .map(Link::Did) ··· 49 62 #[test] 50 63 fn test_any_parse() { 51 64 assert_eq!( 52 - parse_any("https://example.com"), 65 + parse_any_link("https://example.com"), 53 66 Some(Link::Uri("https://example.com".into())) 54 67 ); 55 68 56 69 assert_eq!( 57 - parse_any("at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26"), 70 + parse_any_link( 71 + "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26" 72 + ), 58 73 Some(Link::AtUri( 59 74 "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26".into() 60 75 )), 61 76 ); 62 77 63 78 assert_eq!( 64 - parse_any("did:plc:44ybard66vv44zksje25o7dz"), 79 + parse_any_link("did:plc:44ybard66vv44zksje25o7dz"), 65 80 Some(Link::Did("did:plc:44ybard66vv44zksje25o7dz".into())) 66 81 ) 67 82 }
+137
src/record.rs
··· 1 + use tinyjson::JsonValue; 2 + 3 + use crate::parse_any_link; 4 + 5 + pub fn walk_record(path: &str, v: &JsonValue, found: &mut Vec<(String, String)>) { 6 + match v { 7 + JsonValue::Object(o) => { 8 + for (key, child) in o { 9 + walk_record(&format!("{path}.{key}"), child, found) 10 + } 11 + } 12 + JsonValue::Array(a) => { 13 + let p = format!("{path}[]"); 14 + for child in a { 15 + walk_record(&p, child, found) 16 + } 17 + } 18 + JsonValue::String(s) => { 19 + if let Some(link) = parse_any_link(s) { 20 + found.push((path.to_string(), link.into_string())); 21 + } 22 + } 23 + _ => {} 24 + } 25 + } 26 + 27 + pub fn collect_links(v: JsonValue) -> Vec<(String, String)> { 28 + let mut found = vec![]; 29 + walk_record("", &v, &mut found); 30 + found 31 + } 32 + 33 + #[cfg(test)] 34 + mod tests { 35 + use super::*; 36 + 37 + #[test] 38 + fn test_collect_links() { 39 + let rec = r#"{"a": "https://example.com", "b": "not a link"}"#; 40 + let json = collect_links(rec.parse().unwrap()); 41 + assert_eq!(json, vec![(".a".into(), "https://example.com".into())]); 42 + } 43 + 44 + #[test] 45 + fn test_bsky_feed_post_record_reply() { 46 + let rec = r#"{ 47 + "$type": "app.bsky.feed.post", 48 + "createdAt": "2025-01-08T20:52:43.041Z", 49 + "langs": [ 50 + "en" 51 + ], 52 + "reply": { 53 + "parent": { 54 + "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 55 + "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 56 + }, 57 + "root": { 58 + "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 59 + "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 60 + } 61 + }, 62 + "text": "Yup!" 63 + }"#; 64 + let mut json = collect_links(rec.parse().unwrap()); 65 + json.sort(); 66 + assert_eq!( 67 + json, 68 + vec![ 69 + ( 70 + ".reply.parent.uri".into(), 71 + "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into() 72 + ), 73 + ( 74 + ".reply.root.uri".into(), 75 + "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into() 76 + ), 77 + ] 78 + ) 79 + } 80 + 81 + #[test] 82 + fn test_bsky_feed_post_record_embed() { 83 + let rec = r#"{ 84 + "$type": "app.bsky.feed.post", 85 + "createdAt": "2025-01-08T20:52:39.539Z", 86 + "embed": { 87 + "$type": "app.bsky.embed.external", 88 + "external": { 89 + "description": "YouTube video by More Perfect Union", 90 + "thumb": { 91 + "$type": "blob", 92 + "ref": { 93 + "$link": "bafkreifxuvkbqksq5usi4cryex37o4absjexuouvgenlb62ojsx443b2tm" 94 + }, 95 + "mimeType": "image/jpeg", 96 + "size": 477460 97 + }, 98 + "title": "Corporations & Wealthy Elites Are Coopting Our Government. Who Can Stop Them?", 99 + "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 100 + } 101 + }, 102 + "facets": [ 103 + { 104 + "features": [ 105 + { 106 + "$type": "app.bsky.richtext.facet#link", 107 + "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 108 + } 109 + ], 110 + "index": { 111 + "byteEnd": 24, 112 + "byteStart": 0 113 + } 114 + } 115 + ], 116 + "langs": [ 117 + "en" 118 + ], 119 + "text": "youtu.be/oKXm4szEP1Q?..." 120 + }"#; 121 + let mut json = collect_links(rec.parse().unwrap()); 122 + json.sort(); 123 + assert_eq!( 124 + json, 125 + vec![ 126 + ( 127 + ".embed.external.uri".into(), 128 + "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into() 129 + ), 130 + ( 131 + ".facets[].features[].uri".into(), 132 + "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into() 133 + ), 134 + ] 135 + ) 136 + } 137 + }