remove temp. data storage (e.g. date), and look directly at data, don't re-download (e.g. on sundays) if url has already been downloaded
Some checks failed
CI/CD Pipeline / deploy (push) Has been cancelled
CI/CD Pipeline / test (push) Has been cancelled

This commit is contained in:
Philipp Hofer
2025-10-13 11:07:18 +02:00
parent 8e774a7e23
commit 352b86edad
3 changed files with 50 additions and 27 deletions

View File

@@ -1,14 +1,23 @@
use chrono::NaiveDate;
use serde_json::Value; use serde_json::Value;
pub async fn newest_morning_journal_streaming_url() -> Result<String, Box<dyn std::error::Error>> { #[derive(Clone)]
let url = get_newest_morning_journal().await?; pub struct Episode {
get_streaming_url(url).await pub url: String,
pub date: NaiveDate,
}
pub async fn newest_morning_journal_streaming_url() -> Result<Episode, Box<dyn std::error::Error>> {
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 // 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 // ^ contains link, e.g. https://audioapi.orf.at/oe1/api/json/4.0/broadcast/797577/20250611
async fn get_newest_morning_journal() -> Result<String, Box<dyn std::error::Error>> { async fn get_newest_morning_journal() -> Result<(NaiveDate, String), Box<dyn std::error::Error>> {
let url = "https://audioapi.orf.at/oe1/api/json/current/broadcasts"; let url = "https://audioapi.orf.at/oe1/api/json/current/broadcasts";
let data: Value = reqwest::get(url).await?.json().await?; let data: Value = reqwest::get(url).await?.json().await?;
@@ -19,7 +28,11 @@ async fn get_newest_morning_journal() -> Result<String, Box<dyn std::error::Erro
if broadcast["title"] == "Ö1 Morgenjournal" if broadcast["title"] == "Ö1 Morgenjournal"
&& let Some(href) = broadcast["href"].as_str() && let Some(href) = broadcast["href"].as_str()
{ {
return Ok(href.into()); 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()));
} }
} }
} }

View File

@@ -1,38 +1,45 @@
use chrono::{Local, NaiveDate}; use chrono::Local;
use player::Episode;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
pub struct AppState { pub struct AppState {
pub urls: RwLock<Vec<String>>, pub episodes: RwLock<Vec<Episode>>,
pub last_download_on_day: RwLock<Option<NaiveDate>>,
} }
impl AppState { impl AppState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
urls: RwLock::new(Vec::new()), episodes: RwLock::new(Vec::new()),
last_download_on_day: RwLock::new(None),
} }
} }
pub async fn check_update(self: Arc<Self>) { pub async fn check_update(self: Arc<Self>) {
let today = Local::now().date_naive(); if self.already_downloaded_today().await {
if let Some(downloaded_on_day) = *self.last_download_on_day.read().await
&& today == downloaded_on_day
{
return; return;
} }
*self.last_download_on_day.write().await = Some(today); let latest_episode = player::newest_morning_journal_streaming_url()
let latest_url = player::newest_morning_journal_streaming_url()
.await .await
.unwrap(); .unwrap();
let mut old = self.urls.read().await.clone(); if self.already_downloaded_url(&latest_episode.url).await {
old.push(latest_url); return;
}
let mut old = self.episodes.read().await.clone();
old.push(latest_episode);
let new = old.into_iter().rev().take(10).collect(); // only keep last 10 let new = old.into_iter().rev().take(10).collect(); // only keep last 10
*self.urls.write().await = new; *self.episodes.write().await = new;
}
async fn already_downloaded_today(self: &Arc<Self>) -> bool {
let today = Local::now().date_naive();
self.episodes.read().await.iter().any(|x| x.date == today)
}
async fn already_downloaded_url(self: &Arc<Self>, url: &str) -> bool {
self.episodes.read().await.iter().any(|x| x.url == url)
} }
} }

View File

@@ -1,19 +1,20 @@
use crate::state::AppState; use crate::state::AppState;
use axum::{extract::State, http::HeaderMap, response::IntoResponse}; use axum::{extract::State, http::HeaderMap, response::IntoResponse};
use player::Episode;
use reqwest::header; use reqwest::header;
use std::sync::Arc; use std::sync::Arc;
pub async fn stream_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse { pub async fn stream_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
state.clone().check_update().await; state.clone().check_update().await;
let content = feed(&state.urls.read().await.to_vec()); let content = feed(&state.episodes.read().await.to_vec());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::CONTENT_TYPE, "application/rss+xml".parse().unwrap()); headers.insert(header::CONTENT_TYPE, "application/rss+xml".parse().unwrap());
(headers, content) (headers, content)
} }
fn feed(urls: &Vec<String>) -> String { fn feed(episodes: &Vec<Episode>) -> String {
let mut ret = String::new(); let mut ret = String::new();
ret.push_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#); ret.push_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
ret.push_str(r#"<rss version="2.0">"#); ret.push_str(r#"<rss version="2.0">"#);
@@ -22,15 +23,17 @@ fn feed(urls: &Vec<String>) -> String {
ret.push_str("<link>https://news.hofer.link</link>"); ret.push_str("<link>https://news.hofer.link</link>");
ret.push_str("<description>Feed für Ö1 Morgenjournal. Live.</description>"); ret.push_str("<description>Feed für Ö1 Morgenjournal. Live.</description>");
for url in urls { for episode in episodes {
ret.push_str("<item>"); ret.push_str("<item>");
ret.push_str(&format!("<title>Morgenjournal</title>")); ret.push_str(&format!(
ret.push_str(&format!("<link>{}</link>", quick_xml::escape::escape(url))); "<title>Morgenjournal {}</title>",
&episode.date.format("%d. %m.")
));
ret.push_str(&format!( ret.push_str(&format!(
"<enclosure url=\"{}\" length=\"0\" type=\"audio/mpeg\"/>\n", "<enclosure url=\"{}\" length=\"0\" type=\"audio/mpeg\"/>\n",
quick_xml::escape::escape(url) quick_xml::escape::escape(&episode.url)
)); ));
ret.push_str(&format!("<description>Morgenjournal</description>")); ret.push_str("<description>Morgenjournal</description>");
ret.push_str("</item>"); ret.push_str("</item>");
} }