From 1d4ba2003924a45da49393943f84c9dfb8b0e9df Mon Sep 17 00:00:00 2001 From: mark <> Date: Fri, 18 Jul 2025 09:50:44 +0200 Subject: [PATCH] switch to using bahn.de api instead of bahnhof.de --- src/bahnhof.rs | 61 ++++++----- src/index.html | 6 +- src/main.rs | 281 +++++++++++++++++++++++++------------------------ 3 files changed, 179 insertions(+), 169 deletions(-) diff --git a/src/bahnhof.rs b/src/bahnhof.rs index 7cee4b5..a6ec1c3 100644 --- a/src/bahnhof.rs +++ b/src/bahnhof.rs @@ -1,33 +1,40 @@ -use std::collections::HashMap; - -use chrono::{DateTime, Local}; +use chrono::NaiveDateTime; use serde::Deserialize; #[derive(Deserialize)] pub struct DeparturesMerklingen { #[serde(rename = "entries")] - pub entries: Vec>, + pub entries: Vec, } #[derive(Deserialize)] pub struct DeparturesMerklingen1 { - #[serde(rename = "timeSchedule")] - pub time_schedule: Option>, - #[serde(rename = "timeDelayed")] - pub time_delayed: Option>, - #[serde(rename = "platform")] + #[serde(rename = "zeit")] + pub time_schedule: Option, + #[serde(rename = "ezZeit")] + pub time_delayed: Option, + #[serde(rename = "ezGleis")] pub platform: Option, - #[serde(rename = "platformSchedule")] + #[serde(rename = "gleis")] pub platform_schedule: Option, + // TODO: find this in api if it exists #[serde(rename = "canceled")] pub canceled: Option, - #[serde(rename = "lineName")] - pub line_name: Option, + #[serde(rename = "verkehrmittel")] + pub line_name: Option, + // TODO: find this in api if it exists #[serde(rename = "stopPlace")] pub stop_place: Option, - #[serde(rename = "destination")] - pub destination: Option, - #[serde(rename = "messages")] - pub messages: Option>>, + #[serde(rename = "terminus")] + pub destination: Option, + #[serde(rename = "meldungen")] + pub messages: Option>, +} +#[derive(Deserialize)] +pub struct DeparturesMerklingen4 { + #[serde(rename = "linienNummer")] + pub line_number: Option, + #[serde(rename = "kurzText")] + pub product: Option, } #[derive(Deserialize)] pub struct DeparturesMerklingen2 { @@ -40,22 +47,20 @@ pub struct DeparturesMerklingen2 { pub struct DeparturesMerklingen3 { #[serde(rename = "text")] pub text: Option, - #[serde(rename = "important")] - pub important: Option, + #[serde(rename = "prioritaet")] + pub priority: Option, } pub async fn departures_merklingen() -> Result { - let url = format!( - r#"https://www.bahnhof.de/api/boards/departures?evaNumbers=8003983&filterTransports=REGIONAL_TRAIN&duration=120&stationCategory=5&locale=de&sortBy=TIME_SCHEDULE"# - ); - match reqwest::get(&url).await { + let url = r#"https://www.bahn.de/web/api/reiseloesung/abfahrten?ortExtId=8003983&mitVias=true&maxVias=8&verkehrsmittel[]=ICE&verkehrsmittel[]=EC_IC&verkehrsmittel[]=IR&verkehrsmittel[]=REGIONAL&verkehrsmittel[]=SBAHN"#; + match reqwest::get(url).await { Ok(response) => match response.text().await { - Ok(response) => { - match serde_json::from_str::(&response) { - Ok(response) => Ok(response), - Err(e) => Err(format!("Couldn't parse HTTP response from URL {url:?}: {e}\nResponse was (raw):\n{response}"))?, - } - } + Ok(response) => match serde_json::from_str::(&response) { + Ok(response) => Ok(response), + Err(e) => Err(format!( + "Couldn't parse HTTP response from URL {url:?}: {e}\nResponse was (raw):\n{response}" + ))?, + }, Err(e) => Err(format!("Couldn't get HTTP response from URL {url:?}: {e}"))?, }, Err(e) => Err(format!("Couldn't make GET request to URL {url:?}: {e}"))?, diff --git a/src/index.html b/src/index.html index c7ea43c..5f373c9 100644 --- a/src/index.html +++ b/src/index.html @@ -36,7 +36,7 @@ async function con(change) { nmv('oberdrackenstein_rathaus', 26) break; case "967UDrackensteinKirche": - nmv('unterdrackenstain_kirche', 32) + nmv('unterdrackenstein_kirche', 32) break; case "967GosbachEinkaufszentrum": nmv('gosbach_einkaufszentrum', 37) @@ -72,7 +72,7 @@ async function con(change) { / / / - / + / / / / @@ -90,7 +90,7 @@ async function con(change) { Diese Seite soll eine einfache Möglichkeit darstellen, zu prüfen, ob der Bus und Zug von/nach Merklingen heute kommt, oder ob er mal wieder streikt oder aus sonstigen Gründen fehlt.
Das ganze sollte aber auch für andere Busse funktionieren, sofern der VVS den Bus kennt (→ vvs.de).
-Zug-Informationen kommen von bahnhof.de. +Zug-Informationen kommen von bahn.de. diff --git a/src/main.rs b/src/main.rs index 7845e9c..a546200 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use tokio::{ time::{Instant, sleep}, }; -use crate::vvs::VvsStopIdentifier; +use crate::{bahnhof::DeparturesMerklingen3, vvs::VvsStopIdentifier}; #[rocket::launch] async fn rocket() -> _ { @@ -103,164 +103,169 @@ async fn index( let mut table_rows = BTreeMap::<_, (String, String, usize, usize, bool)>::new(); for departure in &departures.entries { let mut messages = vec![]; - for departure in departure.iter() { - if let Some(message) = &departure.messages { - for (_message_type, message) in message { - for message in message { - if let Some(text) = message.text.clone() { - if message.important.is_some_and(|v| v) { - if !messages.contains(&text) { - messages.push(text); - } - } + if let Some(message) = &departure.messages { + for DeparturesMerklingen3 { priority, text } in message { + if let Some(text) = &text { + if priority + .as_ref() + .is_some_and(|v| ["HOCH"].contains(&v.to_uppercase().as_str())) + { + if !messages.contains(text) { + messages.push(text.to_owned()); } } } } } - if let Some(departure) = departure.first() { - if let Some(time) = departure.time_delayed.or(departure.time_schedule) { - let row = table_rows - .entry((time.date_naive(), time.hour(), time.minute() >= 30)) - .or_default(); - let line_name = departure - .line_name - .as_ref() - .map(|v| v.as_str()) - .unwrap_or("[RE?]"); - let is_ulm = { - if let Some(destination) = departure.destination.as_ref() { - if let Some(name) = &destination.name { - let name = name.to_lowercase(); - if name.starts_with("ulm") || name.contains(" ulm") { - Some(true) - } else if name.contains("wendlingen") - || name.contains("plochingen") - || name.contains("stuttgart") - { - Some(false) - } else { - None - } - } else { - None - } + if let Some(time) = departure.time_delayed.or(departure.time_schedule) { + let row = table_rows + .entry((time.date(), time.hour(), time.minute() >= 30)) + .or_default(); + let line_name = departure + .line_name + .as_ref() + .map(|v| { + v.product.as_ref().map(|prod| { + format!( + "{prod} {}", + v.line_number.as_ref().map(|v| v.as_str()).unwrap_or("?") + ) + }) + }) + .flatten() + .unwrap_or_else(|| "[RE ?]".to_owned()); + let is_ulm = { + if let Some(destination) = departure.destination.as_ref() { + let name = destination.to_lowercase(); + if name.starts_with("ulm") || name.contains(" ulm") { + Some(true) + } else if name.contains("wendlingen") + || name.contains("plochingen") + || name.contains("stuttgart") + { + Some(false) } else { None } + } else { + None } - .or_else(|| { - match departure.platform_schedule.as_ref().map(|v| v.as_str()) { - Some("1") => Some(true), - Some("4") => Some(false), - _ => None, - } - }); - let mut dep_str = "".to_owned(); - dep_str.push_str(html_escape::encode_safe(line_name).as_ref()); - let platform = departure - .platform - .as_ref() - .map(|v| v.as_str()) - .or(departure.platform_schedule.as_ref().map(|v| v.as_str())); - dep_str.push_str(&format!( - " {:0>2}:{:0>2}{}{}", - if let Some(schedule) = &departure.time_schedule { - let delay = ({ - if time > *schedule { - time - *schedule - } else { - TimeDelta::zero() - } - } - .num_minutes() as f64 - / 15.0) - .max(0.0) - .min(1.0); - format!( - r#" style="color: hsl({}, 60%, 60%)""#, - (120u8.saturating_sub((120.0 * delay) as u8)) - ) - } else { - String::new() - }, - time.hour(), - time.minute(), - if let Some(platform) = platform { - if platform.trim().is_empty() { - "" - } else if departure - .platform_schedule - .as_ref() - .is_some_and(|scheduled| platform != scheduled) - { - r#" auf Gleis "# + } + .or_else(|| { + match departure.platform_schedule.as_ref().map(|v| v.as_str()) { + Some("1") => Some(true), + Some("4") => Some(false), + _ => None, + } + }); + let mut dep_str = "".to_owned(); + dep_str.push_str(html_escape::encode_safe(&line_name).as_ref()); + let platform = departure + .platform + .as_ref() + .map(|v| v.as_str()) + .or(departure.platform_schedule.as_ref().map(|v| v.as_str())); + dep_str.push_str(&format!( + " {:0>2}:{:0>2}{}{}", + if let Some(schedule) = &departure.time_schedule { + let delay = ({ + if time > *schedule { + time - *schedule } else { - r#" auf Gleis "# + TimeDelta::zero() } - } else { + } + .num_minutes() as f64 + / 15.0) + .max(0.0) + .min(1.0); + format!( + r#" style="color: hsl({}, 60%, 60%)""#, + (120u8.saturating_sub((120.0 * delay) as u8)) + ) + } else { + String::new() + }, + time.hour(), + time.minute(), + if let Some(platform) = platform { + if platform.trim().is_empty() { "" - }, - if let Some(platform) = platform { - if platform.trim().is_empty() { - String::new() - } else { - format!("{platform}") - } + } else if departure + .platform_schedule + .as_ref() + .is_some_and(|scheduled| platform != scheduled) + { + r#" auf Gleis "# } else { - String::new() - }, - )); - if let Some(dest) = &departure.destination { - if let Some(name) = &dest.name { - dep_str.push_str(r#"
"#); - dep_str.push_str(html_escape::encode_safe(name).as_ref()); + r#" auf Gleis "# } + } else { + "" + }, + if let Some(platform) = platform { + if platform.trim().is_empty() { + String::new() + } else { + format!("{platform}") + } + } else { + String::new() + }, + )); + if let Some(dest) = &departure.destination { + dep_str.push_str(r#"
"#); + dep_str.push_str(html_escape::encode_safe(dest).as_ref()); + } + if departure.canceled.is_some_and(|canceled| canceled) { + dep_str.push_str( + r#"
▸ fällt aus / cancelled
"#, + ); + } else if let Some(stop_place) = departure + .stop_place + .as_ref() + .filter(|stop_place| stop_place.canceled.is_some_and(|canceled| canceled)) + { + dep_str.push_str(r#"
▸ stop/halt "#); + html_escape::encode_safe_to_string( + stop_place + .name + .as_ref() + .map(|v| v.as_str()) + .unwrap_or("[stopPlace.name]"), + &mut dep_str, + ); + dep_str.push_str(": fällt aus / cancelled
"); + } + for message in &messages { + dep_str.push_str(r#"
▹ "#); + html_escape::encode_safe_to_string(message, &mut dep_str); + dep_str.push_str("
"); + } + row.4 = is_ulm.is_some(); + if !row.0.is_empty() { + row.0.push_str("
"); + } + if !row.1.is_empty() { + row.1.push_str("
"); + } + match is_ulm { + Some(true) => { + row.0.push_str(&dep_str); + row.2 += 1; } - if departure.canceled.is_some_and(|canceled| canceled) { - dep_str.push_str( - r#"
▸ fällt aus / cancelled
"#, - ); - } else if let Some(stop_place) = - departure.stop_place.as_ref().filter(|stop_place| { - stop_place.canceled.is_some_and(|canceled| canceled) - }) - { - dep_str.push_str(r#"
▸ stop/halt "#); - html_escape::encode_safe_to_string( - stop_place - .name - .as_ref() - .map(|v| v.as_str()) - .unwrap_or("[stopPlace.name]"), - &mut dep_str, - ); - dep_str.push_str(": fällt aus / cancelled
"); + Some(false) => { + row.1.push_str(&dep_str); + row.3 += 1; } - for message in &messages { - dep_str.push_str(r#"
▹ "#); - html_escape::encode_safe_to_string(message, &mut dep_str); - dep_str.push_str("
"); - } - row.4 = is_ulm.is_some(); - match is_ulm { - Some(true) => { + None => { + if row.2 <= row.3 { row.0.push_str(&dep_str); row.2 += 1; - } - Some(false) => { + } else { row.1.push_str(&dep_str); row.3 += 1; } - None => { - if row.2 <= row.3 { - row.0.push_str(&dep_str); - row.2 += 1; - } else { - row.1.push_str(&dep_str); - row.3 += 1; - } - } } } }