From 65881f717476b60bf7b566bb1148a96f157926de Mon Sep 17 00:00:00 2001 From: "Dr. Matthias Ratajczak" Date: Thu, 5 Jan 2023 13:31:51 +0100 Subject: [PATCH] add point finder from args --- src/args.rs | 10 ++++++++++ src/lib.rs | 4 +++- src/main.rs | 5 +++-- src/query.rs | 48 +++++++++++++++++++++++++++++++++++++++++------ src/response.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 src/args.rs diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..8840ed4 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,10 @@ +use std::env::args; + +use anyhow::{Context, Result}; + +pub fn get_station_name_from_args() -> Result { + args() + .into_iter() + .nth(1) + .context("getting station name from args") +} diff --git a/src/lib.rs b/src/lib.rs index 850a4f8..7814c44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ +mod args; mod query; pub mod response; -pub use query::query_server; +pub use args::get_station_name_from_args; +pub use query::get_departures_for_station; pub use response::format_departures; diff --git a/src/main.rs b/src/main.rs index 78ff66c..e0a1a2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ use anyhow::Result; -use departure::{format_departures, query_server}; +use departure::{format_departures, get_departures_for_station, get_station_name_from_args}; fn main() -> Result<()> { - let response = query_server()?; + let station_name = get_station_name_from_args()?; + let response = get_departures_for_station(station_name)?; let formatted = format_departures(response); println!("{formatted}"); Ok(()) diff --git a/src/query.rs b/src/query.rs index 0679836..5bf3ece 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,16 +1,52 @@ use anyhow::Result; +use ureq::Request; -use crate::response::Response; +use crate::response::{PointFinderResponse, Response}; const DEPARTURE_MONITOR: &str = "https://webapi.vvo-online.de/dm"; +const POINT_FINDER: &str = "https://webapi.vvo-online.de/tr/pointfinder"; -pub fn query_server() -> Result { - let json_string = ureq::post(DEPARTURE_MONITOR) - .set("Content-Type", "application/json") - .set("charset", "UTF-8") - .send_string("{\"stopid\":\"33006765\",\"limit\":5}")? +pub fn get_departures_for_station(station_name: String) -> Result { + let stopid = get_stopid_from_station_name(station_name)?; + + let json_string = create_json_request(DEPARTURE_MONITOR) + .send_string(&format!( + "{{ + \"stopid\":\"{stopid}\", + \"limit\":5 + }}" + ))? .into_string()?; let result = serde_json::from_str(&json_string)?; Ok(result) } + +fn get_stopid_from_station_name(station_name: String) -> Result { + let json_string = create_json_request(POINT_FINDER) + .send_string(&format!( + "{{ + \"query\":\"{station_name}\", + \"limit\":1, + \"regionalOnly\":\"true\", + \"stopsOnly\":\"true\" + }}" + ))? + .into_string()?; + + let response: PointFinderResponse = serde_json::from_str(&json_string)?; + print!("{}", response.point.name); + if let Some(area) = response.point.area { + print!(", {area}"); + } + println!("\n"); + + let result = response.point.stopid; + Ok(result) +} + +fn create_json_request(url: &str) -> Request { + ureq::post(url) + .set("Content-Type", "application/json") + .set("charset", "UTF-8") +} diff --git a/src/response.rs b/src/response.rs index d3bd135..e5b4783 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,6 +1,6 @@ use std::fmt::{Debug, Display}; -use anyhow::Result; +use anyhow::{Context, Result}; use chrono::{DateTime, Local, TimeZone, Utc}; use serde::{de, Deserialize, Deserializer}; @@ -72,3 +72,51 @@ pub fn format_departures(response: Response) -> String { let departures: Vec = response.departures.iter().map(|d| d.to_string()).collect(); departures.join("\n\n") } + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct PointFinderResponse { + #[serde(deserialize_with = "deserialize_points", rename = "Points")] + pub point: Point, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct Point { + pub stopid: String, + pub name: String, + pub area: Option, +} + +impl From<(&str, &str, Option<&str>)> for Point { + fn from(value: (&str, &str, Option<&str>)) -> Self { + Self { + stopid: value.0.into(), + name: value.1.into(), + area: value.2.map(|s| s.into()), + } + } +} + +/// Deserialize a Point Response in the form of +/// '\"33000742|||Helmholtzstraße|5655904|4621157|0||\"' +fn extract_data_from_response(line: &str) -> Result { + let mut iter = line.trim_start_matches("\\\"").split_terminator('|'); + + let stopid = iter.next().context("getting stopid from line")?; + let area = iter.nth(1).context("getting area from line").ok(); + let name = iter.next().context("getting stop name from line")?; + Ok((stopid, name, area).into()) +} + +fn deserialize_points<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let stops: Vec = Vec::deserialize(deserializer)?; + let first_stop_line = stops + .first() + .ok_or_else(|| de::Error::custom("could not get first stop"))?; + let point = extract_data_from_response(first_stop_line).map_err(de::Error::custom)?; + Ok(point) +}