use chrono::NaiveDate; use serde_json::Value; #[derive(Clone)] pub struct Episode { pub url: String, pub date: NaiveDate, } pub async fn newest_morning_journal_streaming_url() -> Result> { let (date, url) = get_newest_morning_journal().await?; let url = get_streaming_url(url).await?; Ok(Episode { url, date }) } // List of broadcasts: https://audioapi.orf.at/oe1/api/json/current/broadcasts // // ^ contains link, e.g. https://audioapi.orf.at/oe1/api/json/4.0/broadcast/797577/20250611 async fn get_newest_morning_journal() -> Result<(NaiveDate, String), Box> { let url = "https://audioapi.orf.at/oe1/api/json/current/broadcasts"; let data: Value = reqwest::get(url).await?.json().await?; if let Some(days) = data.as_array() { for day in days.iter().rev() { if let Some(broadcasts) = day["broadcasts"].as_array() { for broadcast in broadcasts.iter().rev() { if broadcast["title"] == "Ö1 Abendjournal" && let Some(href) = broadcast["href"].as_str() { let date = broadcast["broadcastDay"].as_number().unwrap_or_else(|| { panic!("There needs to be a broadcastDay! {}", &broadcast) }); let date = NaiveDate::parse_from_str(&date.to_string(), "%Y%m%d").expect("broadcastDay in https://audioapi.orf.at/oe1/api/json/current/broadcasts not in a valid format"); return Ok((date, href.into())); } } } } } Err(String::from("No Ö1 Morgenjournal found").into()) } async fn get_streaming_url(url: String) -> Result> { let data: Value = reqwest::get(url).await?.json().await?; let Some(streams) = data["streams"].as_array() else { return Err(String::from("No 'streams' found").into()); }; assert_eq!(streams.len(), 1); let Some(id) = streams[0]["loopStreamId"].as_str() else { return Err(String::from("No 'loopStreamId' found").into()); }; Ok(format!( "https://loopstream01.apa.at/?channel=oe1&shoutcast=0&id={id}" )) }