post.rs (2947B)
1 use crate::error::Error; 2 use crate::event::Event; 3 4 pub(crate) fn with_retry<F, T, C>(f: F, on_rate_limited: C) -> Result<T, Error> 5 where 6 F: Fn() -> Result<T, ureq::Error>, 7 C: Fn(), 8 { 9 loop { 10 match f() { 11 Ok(val) => return Ok(val), 12 Err(ureq::Error::StatusCode(429)) => { 13 on_rate_limited(); 14 std::thread::sleep(std::time::Duration::from_secs(60)); 15 } 16 Err(e) => return Err(e.into()), 17 } 18 } 19 } 20 21 pub struct Post { 22 pub name: String, 23 pub filename: String, 24 pub streaming_url: String, 25 } 26 27 impl Post { 28 pub fn from_json(json: &serde_json::Value) -> Result<Self, Error> { 29 let name = json["name"] 30 .as_str() 31 .ok_or_else(|| Error::Http("name not found".to_string()))? 32 .to_string(); 33 let claim_id = json["claim_id"] 34 .as_str() 35 .ok_or_else(|| Error::Http("claim_id not found".to_string()))?; 36 let sd_hash = json["value"]["source"]["sd_hash"] 37 .as_str() 38 .ok_or_else(|| Error::Http("sd_hash not found".to_string()))?; 39 // URL format: confirmed by inspecting the LBRY `get` API response. 40 // CDN only checks Referer: https://odysee.com/ — no auth token required. 41 let streaming_url = format!( 42 "https://player.odycdn.com/v6/streams/{}/{}.mp4", 43 claim_id, &sd_hash[..6] 44 ); 45 let filename = json["value"]["source"]["name"] 46 .as_str() 47 .unwrap_or(&name) 48 .to_string(); 49 Ok(Post { name, filename, streaming_url }) 50 } 51 52 pub fn content_length(&self, tx: &std::sync::mpsc::Sender<Event>) -> Option<u64> { 53 with_retry( 54 || { 55 ureq::head(&self.streaming_url) 56 .header("Referer", "https://odysee.com/") 57 .header("Origin", "https://odysee.com") 58 .call() 59 }, 60 || { tx.send(Event::RateLimited(self.name.clone())).ok(); }, 61 ) 62 .ok() 63 .and_then(|r| { 64 r.headers() 65 .get("content-length")? 66 .to_str() 67 .ok()? 68 .parse() 69 .ok() 70 }) 71 } 72 73 pub fn download(&self, dir: &std::path::Path, tx: &std::sync::mpsc::Sender<Event>) -> Result<(), Error> { 74 let path = dir.join(&self.filename); 75 let mut response = with_retry( 76 || { 77 ureq::get(&self.streaming_url) 78 .header("Referer", "https://odysee.com/") 79 .header("Origin", "https://odysee.com") 80 .call() 81 }, 82 || { tx.send(Event::RateLimited(self.name.clone())).ok(); }, 83 )?; 84 let mut file = std::fs::File::create(&path)?; 85 let mut reader = response.body_mut().as_reader(); 86 std::io::copy(&mut reader, &mut file)?; 87 Ok(()) 88 } 89 }